Merge pull request #2888 from FernandoS27/decompiler2

Shader_IR: Implement a full control flow decompiler for the shader IR.
This commit is contained in:
David 2019-10-05 21:52:20 +10:00 committed by GitHub
commit 3728bbc22a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 2299 additions and 160 deletions

View file

@ -83,9 +83,15 @@ set(HASH_FILES
"${VIDEO_CORE}/shader/decode/video.cpp" "${VIDEO_CORE}/shader/decode/video.cpp"
"${VIDEO_CORE}/shader/decode/warp.cpp" "${VIDEO_CORE}/shader/decode/warp.cpp"
"${VIDEO_CORE}/shader/decode/xmad.cpp" "${VIDEO_CORE}/shader/decode/xmad.cpp"
"${VIDEO_CORE}/shader/ast.cpp"
"${VIDEO_CORE}/shader/ast.h"
"${VIDEO_CORE}/shader/control_flow.cpp" "${VIDEO_CORE}/shader/control_flow.cpp"
"${VIDEO_CORE}/shader/control_flow.h" "${VIDEO_CORE}/shader/control_flow.h"
"${VIDEO_CORE}/shader/compiler_settings.cpp"
"${VIDEO_CORE}/shader/compiler_settings.h"
"${VIDEO_CORE}/shader/decode.cpp" "${VIDEO_CORE}/shader/decode.cpp"
"${VIDEO_CORE}/shader/expr.cpp"
"${VIDEO_CORE}/shader/expr.h"
"${VIDEO_CORE}/shader/node.h" "${VIDEO_CORE}/shader/node.h"
"${VIDEO_CORE}/shader/node_helper.cpp" "${VIDEO_CORE}/shader/node_helper.cpp"
"${VIDEO_CORE}/shader/node_helper.h" "${VIDEO_CORE}/shader/node_helper.h"

View file

@ -60,9 +60,15 @@ add_custom_command(OUTPUT scm_rev.cpp
"${VIDEO_CORE}/shader/decode/video.cpp" "${VIDEO_CORE}/shader/decode/video.cpp"
"${VIDEO_CORE}/shader/decode/warp.cpp" "${VIDEO_CORE}/shader/decode/warp.cpp"
"${VIDEO_CORE}/shader/decode/xmad.cpp" "${VIDEO_CORE}/shader/decode/xmad.cpp"
"${VIDEO_CORE}/shader/ast.cpp"
"${VIDEO_CORE}/shader/ast.h"
"${VIDEO_CORE}/shader/control_flow.cpp" "${VIDEO_CORE}/shader/control_flow.cpp"
"${VIDEO_CORE}/shader/control_flow.h" "${VIDEO_CORE}/shader/control_flow.h"
"${VIDEO_CORE}/shader/compiler_settings.cpp"
"${VIDEO_CORE}/shader/compiler_settings.h"
"${VIDEO_CORE}/shader/decode.cpp" "${VIDEO_CORE}/shader/decode.cpp"
"${VIDEO_CORE}/shader/expr.cpp"
"${VIDEO_CORE}/shader/expr.h"
"${VIDEO_CORE}/shader/node.h" "${VIDEO_CORE}/shader/node.h"
"${VIDEO_CORE}/shader/node_helper.cpp" "${VIDEO_CORE}/shader/node_helper.cpp"
"${VIDEO_CORE}/shader/node_helper.h" "${VIDEO_CORE}/shader/node_helper.h"

View file

@ -105,9 +105,15 @@ add_library(video_core STATIC
shader/decode/warp.cpp shader/decode/warp.cpp
shader/decode/xmad.cpp shader/decode/xmad.cpp
shader/decode/other.cpp shader/decode/other.cpp
shader/ast.cpp
shader/ast.h
shader/control_flow.cpp shader/control_flow.cpp
shader/control_flow.h shader/control_flow.h
shader/compiler_settings.cpp
shader/compiler_settings.h
shader/decode.cpp shader/decode.cpp
shader/expr.cpp
shader/expr.h
shader/node_helper.cpp shader/node_helper.cpp
shader/node_helper.h shader/node_helper.h
shader/node.h shader/node.h

View file

@ -19,6 +19,7 @@
#include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/shader/ast.h"
#include "video_core/shader/node.h" #include "video_core/shader/node.h"
#include "video_core/shader/shader_ir.h" #include "video_core/shader/shader_ir.h"
@ -334,39 +335,24 @@ constexpr bool IsVertexShader(ProgramType stage) {
return stage == ProgramType::VertexA || stage == ProgramType::VertexB; return stage == ProgramType::VertexA || stage == ProgramType::VertexB;
} }
class ASTDecompiler;
class ExprDecompiler;
class GLSLDecompiler final { class GLSLDecompiler final {
public: public:
explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ProgramType stage, explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ProgramType stage,
std::string suffix) std::string suffix)
: device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {} : device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {}
void Decompile() { void DecompileBranchMode() {
DeclareVertex();
DeclareGeometry();
DeclareRegisters();
DeclarePredicates();
DeclareLocalMemory();
DeclareSharedMemory();
DeclareInternalFlags();
DeclareInputAttributes();
DeclareOutputAttributes();
DeclareConstantBuffers();
DeclareGlobalMemory();
DeclareSamplers();
DeclarePhysicalAttributeReader();
DeclareImages();
code.AddLine("void execute_{}() {{", suffix);
++code.scope;
// VM's program counter // VM's program counter
const auto first_address = ir.GetBasicBlocks().begin()->first; const auto first_address = ir.GetBasicBlocks().begin()->first;
code.AddLine("uint jmp_to = {}U;", first_address); code.AddLine("uint jmp_to = {}U;", first_address);
// TODO(Subv): Figure out the actual depth of the flow stack, for now it seems // 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. // unlikely that shaders will use 20 nested SSYs and PBKs.
if (!ir.IsFlowStackDisabled()) {
constexpr u32 FLOW_STACK_SIZE = 20; constexpr u32 FLOW_STACK_SIZE = 20;
if (!ir.IsFlowStackDisabled()) {
for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) {
code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE);
code.AddLine("uint {} = 0U;", FlowStackTopName(stack)); code.AddLine("uint {} = 0U;", FlowStackTopName(stack));
@ -392,10 +378,37 @@ public:
code.AddLine("default: return;"); code.AddLine("default: return;");
code.AddLine("}}"); code.AddLine("}}");
for (std::size_t i = 0; i < 2; ++i) {
--code.scope; --code.scope;
code.AddLine("}}"); 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() { std::string GetResult() {
@ -424,6 +437,9 @@ public:
} }
private: private:
friend class ASTDecompiler;
friend class ExprDecompiler;
void DeclareVertex() { void DeclareVertex() {
if (!IsVertexShader(stage)) if (!IsVertexShader(stage))
return; return;
@ -1821,10 +1837,9 @@ private:
return {}; return {};
} }
Expression Exit(Operation operation) { void PreExit() {
if (stage != ProgramType::Fragment) { if (stage != ProgramType::Fragment) {
code.AddLine("return;"); return;
return {};
} }
const auto& used_registers = ir.GetRegisters(); const auto& used_registers = ir.GetRegisters();
const auto SafeGetRegister = [&](u32 reg) -> Expression { const auto SafeGetRegister = [&](u32 reg) -> Expression {
@ -1856,7 +1871,10 @@ private:
// already contains one past the last color register. // already contains one past the last color register.
code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat()); code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat());
} }
}
Expression Exit(Operation operation) {
PreExit();
code.AddLine("return;"); code.AddLine("return;");
return {}; return {};
} }
@ -2253,6 +2271,208 @@ private:
ShaderWriter code; ShaderWriter code;
}; };
static constexpr std::string_view flow_var = "flow_var_";
std::string GetFlowVariable(u32 i) {
return fmt::format("{}{}", flow_var, i);
}
class ExprDecompiler {
public:
explicit 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) {
const auto pred = static_cast<Tegra::Shader::Pred>(expr.predicate);
inner += decomp.GetPredicate(pred);
}
void operator()(VideoCommon::Shader::ExprCondCode& expr) {
const Node cc = decomp.ir.GetConditionCode(expr.cc);
std::string target;
if (const auto pred = std::get_if<PredicateNode>(&*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<InternalFlagNode>(&*cc)) {
target = decomp.GetInternalFlag(flag->GetFlag());
} else {
UNREACHABLE();
}
inner += target;
}
void operator()(VideoCommon::Shader::ExprVar& expr) {
inner += GetFlowVariable(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:
explicit 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("{} = {};", GetFlowVariable(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) {
const 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.PreExit();
decomp.code.AddLine("return;");
}
if (!is_true) {
decomp.code.scope--;
decomp.code.AddLine("}}");
}
}
void operator()(VideoCommon::Shader::ASTBreak& ast) {
const 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() {
const u32 num_flow_variables = ir.GetASTNumVariables();
for (u32 i = 0; i < num_flow_variables; i++) {
code.AddLine("bool {} = false;", GetFlowVariable(i));
}
ASTDecompiler decompiler{*this};
VideoCommon::Shader::ASTNode program = ir.GetASTProgram();
decompiler.Visit(program);
}
} // Anonymous namespace } // Anonymous namespace
std::string GetCommonDeclarations() { std::string GetCommonDeclarations() {

View file

@ -11,12 +11,16 @@
namespace OpenGL::GLShader { namespace OpenGL::GLShader {
using Tegra::Engines::Maxwell3D; using Tegra::Engines::Maxwell3D;
using VideoCommon::Shader::CompileDepth;
using VideoCommon::Shader::CompilerSettings;
using VideoCommon::Shader::ProgramCode; using VideoCommon::Shader::ProgramCode;
using VideoCommon::Shader::ShaderIR; using VideoCommon::Shader::ShaderIR;
static constexpr u32 PROGRAM_OFFSET = 10; static constexpr u32 PROGRAM_OFFSET = 10;
static constexpr u32 COMPUTE_OFFSET = 0; static constexpr u32 COMPUTE_OFFSET = 0;
static constexpr CompilerSettings settings{CompileDepth::NoFlowStack, true};
ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) { ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) {
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
@ -31,13 +35,14 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
)"; )";
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a); const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
const auto stage = setup.IsDualProgram() ? ProgramType::VertexA : ProgramType::VertexB; const auto stage = setup.IsDualProgram() ? ProgramType::VertexA : ProgramType::VertexB;
ProgramResult program = Decompile(device, program_ir, stage, "vertex"); ProgramResult program = Decompile(device, program_ir, stage, "vertex");
out += program.first; out += program.first;
if (setup.IsDualProgram()) { if (setup.IsDualProgram()) {
const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b); const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b,
settings);
ProgramResult program_b = Decompile(device, program_ir_b, ProgramType::VertexB, "vertex_b"); ProgramResult program_b = Decompile(device, program_ir_b, ProgramType::VertexB, "vertex_b");
out += program_b.first; out += program_b.first;
} }
@ -80,7 +85,7 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
)"; )";
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a); const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
ProgramResult program = Decompile(device, program_ir, ProgramType::Geometry, "geometry"); ProgramResult program = Decompile(device, program_ir, ProgramType::Geometry, "geometry");
out += program.first; out += program.first;
@ -114,7 +119,8 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform fs_config {
}; };
)"; )";
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a);
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
ProgramResult program = Decompile(device, program_ir, ProgramType::Fragment, "fragment"); ProgramResult program = Decompile(device, program_ir, ProgramType::Fragment, "fragment");
out += program.first; out += program.first;
@ -133,7 +139,7 @@ ProgramResult GenerateComputeShader(const Device& device, const ShaderSetup& set
std::string out = "// Shader Unique Id: CS" + id + "\n\n"; std::string out = "// Shader Unique Id: CS" + id + "\n\n";
out += GetCommonDeclarations(); out += GetCommonDeclarations();
const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a); const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a, settings);
ProgramResult program = Decompile(device, program_ir, ProgramType::Compute, "compute"); ProgramResult program = Decompile(device, program_ir, ProgramType::Compute, "compute");
out += program.first; out += program.first;

View file

@ -88,6 +88,9 @@ bool IsPrecise(Operation operand) {
} // namespace } // namespace
class ASTDecompiler;
class ExprDecompiler;
class SPIRVDecompiler : public Sirit::Module { class SPIRVDecompiler : public Sirit::Module {
public: public:
explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderStage stage) explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderStage stage)
@ -97,27 +100,7 @@ public:
AddExtension("SPV_KHR_variable_pointers"); AddExtension("SPV_KHR_variable_pointers");
} }
void Decompile() { void DecompileBranchMode() {
AllocateBindings();
AllocateLabels();
DeclareVertex();
DeclareGeometry();
DeclareFragment();
DeclareRegisters();
DeclarePredicates();
DeclareLocalMemory();
DeclareInternalFlags();
DeclareInputAttributes();
DeclareOutputAttributes();
DeclareConstantBuffers();
DeclareGlobalBuffers();
DeclareSamplers();
execute_function =
Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
Emit(OpLabel());
const u32 first_address = ir.GetBasicBlocks().begin()->first; const u32 first_address = ir.GetBasicBlocks().begin()->first;
const Id loop_label = OpLabel("loop"); const Id loop_label = OpLabel("loop");
const Id merge_label = OpLabel("merge"); const Id merge_label = OpLabel("merge");
@ -174,6 +157,43 @@ public:
Emit(continue_label); Emit(continue_label);
Emit(OpBranch(loop_label)); Emit(OpBranch(loop_label));
Emit(merge_label); Emit(merge_label);
}
void DecompileAST();
void Decompile() {
const bool is_fully_decompiled = ir.IsDecompiled();
AllocateBindings();
if (!is_fully_decompiled) {
AllocateLabels();
}
DeclareVertex();
DeclareGeometry();
DeclareFragment();
DeclareRegisters();
DeclarePredicates();
if (is_fully_decompiled) {
DeclareFlowVariables();
}
DeclareLocalMemory();
DeclareInternalFlags();
DeclareInputAttributes();
DeclareOutputAttributes();
DeclareConstantBuffers();
DeclareGlobalBuffers();
DeclareSamplers();
execute_function =
Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
Emit(OpLabel());
if (is_fully_decompiled) {
DecompileAST();
} else {
DecompileBranchMode();
}
Emit(OpReturn()); Emit(OpReturn());
Emit(OpFunctionEnd()); Emit(OpFunctionEnd());
} }
@ -206,6 +226,9 @@ public:
} }
private: private:
friend class ASTDecompiler;
friend class ExprDecompiler;
static constexpr auto INTERNAL_FLAGS_COUNT = static_cast<std::size_t>(InternalFlag::Amount); static constexpr auto INTERNAL_FLAGS_COUNT = static_cast<std::size_t>(InternalFlag::Amount);
void AllocateBindings() { void AllocateBindings() {
@ -294,6 +317,14 @@ private:
} }
} }
void DeclareFlowVariables() {
for (u32 i = 0; i < ir.GetASTNumVariables(); i++) {
const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
Name(id, fmt::format("flow_var_{}", static_cast<u32>(i)));
flow_variables.emplace(i, AddGlobalVariable(id));
}
}
void DeclareLocalMemory() { void DeclareLocalMemory() {
if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) { if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) {
const auto element_count = static_cast<u32>(Common::AlignUp(local_memory_size, 4) / 4); const auto element_count = static_cast<u32>(Common::AlignUp(local_memory_size, 4) / 4);
@ -615,9 +646,15 @@ private:
Emit(OpBranchConditional(condition, true_label, skip_label)); Emit(OpBranchConditional(condition, true_label, skip_label));
Emit(true_label); Emit(true_label);
++conditional_nest_count;
VisitBasicBlock(conditional->GetCode()); VisitBasicBlock(conditional->GetCode());
--conditional_nest_count;
if (inside_branch == 0) {
Emit(OpBranch(skip_label)); Emit(OpBranch(skip_label));
} else {
inside_branch--;
}
Emit(skip_label); Emit(skip_label);
return {}; return {};
@ -980,7 +1017,11 @@ private:
UNIMPLEMENTED_IF(!target); UNIMPLEMENTED_IF(!target);
Emit(OpStore(jmp_to, Constant(t_uint, target->GetValue()))); Emit(OpStore(jmp_to, Constant(t_uint, target->GetValue())));
BranchingOp([&]() { Emit(OpBranch(continue_label)); }); Emit(OpBranch(continue_label));
inside_branch = conditional_nest_count;
if (conditional_nest_count == 0) {
Emit(OpLabel());
}
return {}; return {};
} }
@ -988,7 +1029,11 @@ private:
const Id op_a = VisitOperand<Type::Uint>(operation, 0); const Id op_a = VisitOperand<Type::Uint>(operation, 0);
Emit(OpStore(jmp_to, op_a)); Emit(OpStore(jmp_to, op_a));
BranchingOp([&]() { Emit(OpBranch(continue_label)); }); Emit(OpBranch(continue_label));
inside_branch = conditional_nest_count;
if (conditional_nest_count == 0) {
Emit(OpLabel());
}
return {}; return {};
} }
@ -1015,11 +1060,15 @@ private:
Emit(OpStore(flow_stack_top, previous)); Emit(OpStore(flow_stack_top, previous));
Emit(OpStore(jmp_to, target)); Emit(OpStore(jmp_to, target));
BranchingOp([&]() { Emit(OpBranch(continue_label)); }); Emit(OpBranch(continue_label));
inside_branch = conditional_nest_count;
if (conditional_nest_count == 0) {
Emit(OpLabel());
}
return {}; return {};
} }
Id Exit(Operation operation) { Id PreExit() {
switch (stage) { switch (stage) {
case ShaderStage::Vertex: { case ShaderStage::Vertex: {
// TODO(Rodrigo): We should use VK_EXT_depth_range_unrestricted instead, but it doesn't // TODO(Rodrigo): We should use VK_EXT_depth_range_unrestricted instead, but it doesn't
@ -1067,12 +1116,35 @@ private:
} }
} }
BranchingOp([&]() { Emit(OpReturn()); }); return {};
}
Id Exit(Operation operation) {
PreExit();
inside_branch = conditional_nest_count;
if (conditional_nest_count > 0) {
Emit(OpReturn());
} else {
const Id dummy = OpLabel();
Emit(OpBranch(dummy));
Emit(dummy);
Emit(OpReturn());
Emit(OpLabel());
}
return {}; return {};
} }
Id Discard(Operation operation) { Id Discard(Operation operation) {
BranchingOp([&]() { Emit(OpKill()); }); inside_branch = conditional_nest_count;
if (conditional_nest_count > 0) {
Emit(OpKill());
} else {
const Id dummy = OpLabel();
Emit(OpBranch(dummy));
Emit(dummy);
Emit(OpKill());
Emit(OpLabel());
}
return {}; return {};
} }
@ -1267,17 +1339,6 @@ private:
return {}; return {};
} }
void BranchingOp(std::function<void()> call) {
const Id true_label = OpLabel();
const Id skip_label = OpLabel();
Emit(OpSelectionMerge(skip_label, spv::SelectionControlMask::Flatten));
Emit(OpBranchConditional(v_true, true_label, skip_label, 1, 0));
Emit(true_label);
call();
Emit(skip_label);
}
std::tuple<Id, Id> CreateFlowStack() { std::tuple<Id, Id> CreateFlowStack() {
// TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely // TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely
// that shaders will use 20 nested SSYs and PBKs. // that shaders will use 20 nested SSYs and PBKs.
@ -1483,6 +1544,8 @@ private:
const ShaderIR& ir; const ShaderIR& ir;
const ShaderStage stage; const ShaderStage stage;
const Tegra::Shader::Header header; const Tegra::Shader::Header header;
u64 conditional_nest_count{};
u64 inside_branch{};
const Id t_void = Name(TypeVoid(), "void"); const Id t_void = Name(TypeVoid(), "void");
@ -1545,6 +1608,7 @@ private:
Id per_vertex{}; Id per_vertex{};
std::map<u32, Id> registers; std::map<u32, Id> registers;
std::map<Tegra::Shader::Pred, Id> predicates; std::map<Tegra::Shader::Pred, Id> predicates;
std::map<u32, Id> flow_variables;
Id local_memory{}; Id local_memory{};
std::array<Id, INTERNAL_FLAGS_COUNT> internal_flags{}; std::array<Id, INTERNAL_FLAGS_COUNT> internal_flags{};
std::map<Attribute::Index, Id> input_attributes; std::map<Attribute::Index, Id> input_attributes;
@ -1580,6 +1644,223 @@ private:
std::map<u32, Id> labels; std::map<u32, Id> labels;
}; };
class ExprDecompiler {
public:
explicit ExprDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {}
Id operator()(VideoCommon::Shader::ExprAnd& expr) {
const Id type_def = decomp.GetTypeDefinition(Type::Bool);
const Id op1 = Visit(expr.operand1);
const Id op2 = Visit(expr.operand2);
return decomp.Emit(decomp.OpLogicalAnd(type_def, op1, op2));
}
Id operator()(VideoCommon::Shader::ExprOr& expr) {
const Id type_def = decomp.GetTypeDefinition(Type::Bool);
const Id op1 = Visit(expr.operand1);
const Id op2 = Visit(expr.operand2);
return decomp.Emit(decomp.OpLogicalOr(type_def, op1, op2));
}
Id operator()(VideoCommon::Shader::ExprNot& expr) {
const Id type_def = decomp.GetTypeDefinition(Type::Bool);
const Id op1 = Visit(expr.operand1);
return decomp.Emit(decomp.OpLogicalNot(type_def, op1));
}
Id operator()(VideoCommon::Shader::ExprPredicate& expr) {
const auto pred = static_cast<Tegra::Shader::Pred>(expr.predicate);
return decomp.Emit(decomp.OpLoad(decomp.t_bool, decomp.predicates.at(pred)));
}
Id operator()(VideoCommon::Shader::ExprCondCode& expr) {
const Node cc = decomp.ir.GetConditionCode(expr.cc);
Id target;
if (const auto pred = std::get_if<PredicateNode>(&*cc)) {
const auto index = pred->GetIndex();
switch (index) {
case Tegra::Shader::Pred::NeverExecute:
target = decomp.v_false;
case Tegra::Shader::Pred::UnusedIndex:
target = decomp.v_true;
default:
target = decomp.predicates.at(index);
}
} else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) {
target = decomp.internal_flags.at(static_cast<u32>(flag->GetFlag()));
}
return decomp.Emit(decomp.OpLoad(decomp.t_bool, target));
}
Id operator()(VideoCommon::Shader::ExprVar& expr) {
return decomp.Emit(decomp.OpLoad(decomp.t_bool, decomp.flow_variables.at(expr.var_index)));
}
Id operator()(VideoCommon::Shader::ExprBoolean& expr) {
return expr.value ? decomp.v_true : decomp.v_false;
}
Id Visit(VideoCommon::Shader::Expr& node) {
return std::visit(*this, *node);
}
private:
SPIRVDecompiler& decomp;
};
class ASTDecompiler {
public:
explicit ASTDecompiler(SPIRVDecompiler& 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};
const Id condition = expr_parser.Visit(ast.condition);
const Id then_label = decomp.OpLabel();
const Id endif_label = decomp.OpLabel();
decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
decomp.Emit(then_label);
ASTNode current = ast.nodes.GetFirst();
while (current) {
Visit(current);
current = current->GetNext();
}
decomp.Emit(decomp.OpBranch(endif_label));
decomp.Emit(endif_label);
}
void operator()(VideoCommon::Shader::ASTIfElse& ast) {
UNREACHABLE();
}
void operator()(VideoCommon::Shader::ASTBlockEncoded& ast) {
UNREACHABLE();
}
void operator()(VideoCommon::Shader::ASTBlockDecoded& ast) {
decomp.VisitBasicBlock(ast.nodes);
}
void operator()(VideoCommon::Shader::ASTVarSet& ast) {
ExprDecompiler expr_parser{decomp};
const Id condition = expr_parser.Visit(ast.condition);
decomp.Emit(decomp.OpStore(decomp.flow_variables.at(ast.index), condition));
}
void operator()(VideoCommon::Shader::ASTLabel& ast) {
// Do nothing
}
void operator()(VideoCommon::Shader::ASTGoto& ast) {
UNREACHABLE();
}
void operator()(VideoCommon::Shader::ASTDoWhile& ast) {
const Id loop_label = decomp.OpLabel();
const Id endloop_label = decomp.OpLabel();
const Id loop_start_block = decomp.OpLabel();
const Id loop_end_block = decomp.OpLabel();
current_loop_exit = endloop_label;
decomp.Emit(decomp.OpBranch(loop_label));
decomp.Emit(loop_label);
decomp.Emit(
decomp.OpLoopMerge(endloop_label, loop_end_block, spv::LoopControlMask::MaskNone));
decomp.Emit(decomp.OpBranch(loop_start_block));
decomp.Emit(loop_start_block);
ASTNode current = ast.nodes.GetFirst();
while (current) {
Visit(current);
current = current->GetNext();
}
ExprDecompiler expr_parser{decomp};
const Id condition = expr_parser.Visit(ast.condition);
decomp.Emit(decomp.OpBranchConditional(condition, loop_label, endloop_label));
decomp.Emit(endloop_label);
}
void operator()(VideoCommon::Shader::ASTReturn& ast) {
if (!VideoCommon::Shader::ExprIsTrue(ast.condition)) {
ExprDecompiler expr_parser{decomp};
const Id condition = expr_parser.Visit(ast.condition);
const Id then_label = decomp.OpLabel();
const Id endif_label = decomp.OpLabel();
decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
decomp.Emit(then_label);
if (ast.kills) {
decomp.Emit(decomp.OpKill());
} else {
decomp.PreExit();
decomp.Emit(decomp.OpReturn());
}
decomp.Emit(endif_label);
} else {
const Id next_block = decomp.OpLabel();
decomp.Emit(decomp.OpBranch(next_block));
decomp.Emit(next_block);
if (ast.kills) {
decomp.Emit(decomp.OpKill());
} else {
decomp.PreExit();
decomp.Emit(decomp.OpReturn());
}
decomp.Emit(decomp.OpLabel());
}
}
void operator()(VideoCommon::Shader::ASTBreak& ast) {
if (!VideoCommon::Shader::ExprIsTrue(ast.condition)) {
ExprDecompiler expr_parser{decomp};
const Id condition = expr_parser.Visit(ast.condition);
const Id then_label = decomp.OpLabel();
const Id endif_label = decomp.OpLabel();
decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
decomp.Emit(then_label);
decomp.Emit(decomp.OpBranch(current_loop_exit));
decomp.Emit(endif_label);
} else {
const Id next_block = decomp.OpLabel();
decomp.Emit(decomp.OpBranch(next_block));
decomp.Emit(next_block);
decomp.Emit(decomp.OpBranch(current_loop_exit));
decomp.Emit(decomp.OpLabel());
}
}
void Visit(VideoCommon::Shader::ASTNode& node) {
std::visit(*this, *node->GetInnerData());
}
private:
SPIRVDecompiler& decomp;
Id current_loop_exit{};
};
void SPIRVDecompiler::DecompileAST() {
const u32 num_flow_variables = ir.GetASTNumVariables();
for (u32 i = 0; i < num_flow_variables; i++) {
const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
Name(id, fmt::format("flow_var_{}", i));
flow_variables.emplace(i, AddGlobalVariable(id));
}
ASTDecompiler decompiler{*this};
VideoCommon::Shader::ASTNode program = ir.GetASTProgram();
decompiler.Visit(program);
const Id next_block = OpLabel();
Emit(OpBranch(next_block));
Emit(next_block);
}
DecompilerResult Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir, DecompilerResult Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir,
Maxwell::ShaderStage stage) { Maxwell::ShaderStage stage) {
auto decompiler = std::make_unique<SPIRVDecompiler>(device, ir, stage); auto decompiler = std::make_unique<SPIRVDecompiler>(device, ir, stage);

View file

@ -0,0 +1,766 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <string>
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/shader/ast.h"
#include "video_core/shader/expr.h"
namespace VideoCommon::Shader {
ASTZipper::ASTZipper() = default;
void ASTZipper::Init(const ASTNode new_first, const ASTNode parent) {
ASSERT(new_first->manager == nullptr);
first = new_first;
last = new_first;
ASTNode current = first;
while (current) {
current->manager = this;
current->parent = parent;
last = current;
current = current->next;
}
}
void ASTZipper::PushBack(const ASTNode new_node) {
ASSERT(new_node->manager == nullptr);
new_node->previous = last;
if (last) {
last->next = new_node;
}
new_node->next.reset();
last = new_node;
if (!first) {
first = new_node;
}
new_node->manager = this;
}
void ASTZipper::PushFront(const ASTNode new_node) {
ASSERT(new_node->manager == nullptr);
new_node->previous.reset();
new_node->next = first;
if (first) {
first->previous = new_node;
}
if (last == first) {
last = new_node;
}
first = new_node;
new_node->manager = this;
}
void ASTZipper::InsertAfter(const ASTNode new_node, const ASTNode at_node) {
ASSERT(new_node->manager == nullptr);
if (!at_node) {
PushFront(new_node);
return;
}
const ASTNode next = at_node->next;
if (next) {
next->previous = new_node;
}
new_node->previous = at_node;
if (at_node == last) {
last = new_node;
}
new_node->next = next;
at_node->next = new_node;
new_node->manager = this;
}
void ASTZipper::InsertBefore(const ASTNode new_node, const ASTNode at_node) {
ASSERT(new_node->manager == nullptr);
if (!at_node) {
PushBack(new_node);
return;
}
const ASTNode previous = at_node->previous;
if (previous) {
previous->next = new_node;
}
new_node->next = at_node;
if (at_node == first) {
first = new_node;
}
new_node->previous = previous;
at_node->previous = new_node;
new_node->manager = this;
}
void ASTZipper::DetachTail(const ASTNode node) {
ASSERT(node->manager == this);
if (node == first) {
first.reset();
last.reset();
return;
}
last = node->previous;
last->next.reset();
node->previous.reset();
ASTNode current = node;
while (current) {
current->manager = nullptr;
current->parent.reset();
current = current->next;
}
}
void ASTZipper::DetachSegment(const ASTNode start, const ASTNode end) {
ASSERT(start->manager == this && end->manager == this);
if (start == end) {
DetachSingle(start);
return;
}
const ASTNode prev = start->previous;
const ASTNode post = end->next;
if (!prev) {
first = post;
} else {
prev->next = post;
}
if (!post) {
last = prev;
} else {
post->previous = prev;
}
start->previous.reset();
end->next.reset();
ASTNode current = start;
bool found = false;
while (current) {
current->manager = nullptr;
current->parent.reset();
found |= current == end;
current = current->next;
}
ASSERT(found);
}
void ASTZipper::DetachSingle(const ASTNode node) {
ASSERT(node->manager == this);
const ASTNode prev = node->previous;
const ASTNode post = node->next;
node->previous.reset();
node->next.reset();
if (!prev) {
first = post;
} else {
prev->next = post;
}
if (!post) {
last = prev;
} else {
post->previous = prev;
}
node->manager = nullptr;
node->parent.reset();
}
void ASTZipper::Remove(const ASTNode node) {
ASSERT(node->manager == this);
const ASTNode next = node->next;
const ASTNode previous = node->previous;
if (previous) {
previous->next = next;
}
if (next) {
next->previous = previous;
}
node->parent.reset();
node->manager = nullptr;
if (node == last) {
last = previous;
}
if (node == first) {
first = next;
}
}
class ExprPrinter final {
public:
ExprPrinter() = default;
void operator()(ExprAnd const& expr) {
inner += "( ";
std::visit(*this, *expr.operand1);
inner += " && ";
std::visit(*this, *expr.operand2);
inner += ')';
}
void operator()(ExprOr const& expr) {
inner += "( ";
std::visit(*this, *expr.operand1);
inner += " || ";
std::visit(*this, *expr.operand2);
inner += ')';
}
void operator()(ExprNot const& expr) {
inner += "!";
std::visit(*this, *expr.operand1);
}
void operator()(ExprPredicate const& expr) {
inner += "P" + std::to_string(expr.predicate);
}
void operator()(ExprCondCode const& expr) {
u32 cc = static_cast<u32>(expr.cc);
inner += "CC" + std::to_string(cc);
}
void operator()(ExprVar const& expr) {
inner += "V" + std::to_string(expr.var_index);
}
void operator()(ExprBoolean const& expr) {
inner += expr.value ? "true" : "false";
}
std::string& GetResult() {
return inner;
}
std::string inner{};
};
class ASTPrinter {
public:
ASTPrinter() = default;
void operator()(ASTProgram& ast) {
scope++;
inner += "program {\n";
ASTNode current = ast.nodes.GetFirst();
while (current) {
Visit(current);
current = current->GetNext();
}
inner += "}\n";
scope--;
}
void operator()(ASTIfThen& ast) {
ExprPrinter expr_parser{};
std::visit(expr_parser, *ast.condition);
inner += Ident() + "if (" + expr_parser.GetResult() + ") {\n";
scope++;
ASTNode current = ast.nodes.GetFirst();
while (current) {
Visit(current);
current = current->GetNext();
}
scope--;
inner += Ident() + "}\n";
}
void operator()(ASTIfElse& ast) {
inner += Ident() + "else {\n";
scope++;
ASTNode current = ast.nodes.GetFirst();
while (current) {
Visit(current);
current = current->GetNext();
}
scope--;
inner += Ident() + "}\n";
}
void operator()(ASTBlockEncoded& ast) {
inner += Ident() + "Block(" + std::to_string(ast.start) + ", " + std::to_string(ast.end) +
");\n";
}
void operator()(ASTBlockDecoded& ast) {
inner += Ident() + "Block;\n";
}
void operator()(ASTVarSet& ast) {
ExprPrinter expr_parser{};
std::visit(expr_parser, *ast.condition);
inner +=
Ident() + "V" + std::to_string(ast.index) + " := " + expr_parser.GetResult() + ";\n";
}
void operator()(ASTLabel& ast) {
inner += "Label_" + std::to_string(ast.index) + ":\n";
}
void operator()(ASTGoto& ast) {
ExprPrinter expr_parser{};
std::visit(expr_parser, *ast.condition);
inner += Ident() + "(" + expr_parser.GetResult() + ") -> goto Label_" +
std::to_string(ast.label) + ";\n";
}
void operator()(ASTDoWhile& ast) {
ExprPrinter expr_parser{};
std::visit(expr_parser, *ast.condition);
inner += Ident() + "do {\n";
scope++;
ASTNode current = ast.nodes.GetFirst();
while (current) {
Visit(current);
current = current->GetNext();
}
scope--;
inner += Ident() + "} while (" + expr_parser.GetResult() + ");\n";
}
void operator()(ASTReturn& ast) {
ExprPrinter expr_parser{};
std::visit(expr_parser, *ast.condition);
inner += Ident() + "(" + expr_parser.GetResult() + ") -> " +
(ast.kills ? "discard" : "exit") + ";\n";
}
void operator()(ASTBreak& ast) {
ExprPrinter expr_parser{};
std::visit(expr_parser, *ast.condition);
inner += Ident() + "(" + expr_parser.GetResult() + ") -> break;\n";
}
std::string& Ident() {
if (memo_scope == scope) {
return tabs_memo;
}
tabs_memo = tabs.substr(0, scope * 2);
memo_scope = scope;
return tabs_memo;
}
void Visit(ASTNode& node) {
std::visit(*this, *node->GetInnerData());
}
std::string& GetResult() {
return inner;
}
private:
std::string inner{};
u32 scope{};
std::string tabs_memo{};
u32 memo_scope{};
static std::string tabs;
};
std::string ASTPrinter::tabs = " ";
std::string ASTManager::Print() {
ASTPrinter printer{};
printer.Visit(main_node);
return printer.GetResult();
}
ASTManager::ASTManager(bool full_decompile, bool disable_else_derivation)
: full_decompile{full_decompile}, disable_else_derivation{disable_else_derivation} {};
ASTManager::~ASTManager() {
Clear();
}
void ASTManager::Init() {
main_node = ASTBase::Make<ASTProgram>(ASTNode{});
program = std::get_if<ASTProgram>(main_node->GetInnerData());
false_condition = MakeExpr<ExprBoolean>(false);
}
ASTManager::ASTManager(ASTManager&& other) noexcept
: 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}, false_condition{other.false_condition},
disable_else_derivation{other.disable_else_derivation} {
other.main_node.reset();
}
ASTManager& ASTManager::operator=(ASTManager&& other) noexcept {
full_decompile = other.full_decompile;
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;
false_condition = other.false_condition;
disable_else_derivation = other.disable_else_derivation;
other.main_node.reset();
return *this;
}
void ASTManager::DeclareLabel(u32 address) {
const auto pair = labels_map.emplace(address, labels_count);
if (pair.second) {
labels_count++;
labels.resize(labels_count);
}
}
void ASTManager::InsertLabel(u32 address) {
const u32 index = labels_map[address];
const ASTNode label = ASTBase::Make<ASTLabel>(main_node, index);
labels[index] = label;
program->nodes.PushBack(label);
}
void ASTManager::InsertGoto(Expr condition, u32 address) {
const u32 index = labels_map[address];
const ASTNode goto_node = ASTBase::Make<ASTGoto>(main_node, condition, index);
gotos.push_back(goto_node);
program->nodes.PushBack(goto_node);
}
void ASTManager::InsertBlock(u32 start_address, u32 end_address) {
const ASTNode block = ASTBase::Make<ASTBlockEncoded>(main_node, start_address, end_address);
program->nodes.PushBack(block);
}
void ASTManager::InsertReturn(Expr condition, bool kills) {
const ASTNode node = ASTBase::Make<ASTReturn>(main_node, condition, kills);
program->nodes.PushBack(node);
}
// The decompile algorithm is based on
// "Taming control flow: A structured approach to eliminating goto statements"
// by AM Erosa, LJ Hendren 1994. In general, the idea is to get gotos to be
// on the same structured level as the label which they jump to. This is done,
// through outward/inward movements and lifting. Once they are at the same
// level, you can enclose them in an "if" structure or a "do-while" structure.
void ASTManager::Decompile() {
auto it = gotos.begin();
while (it != gotos.end()) {
const ASTNode goto_node = *it;
const auto label_index = goto_node->GetGotoLabel();
if (!label_index) {
return;
}
const ASTNode label = labels[*label_index];
if (!full_decompile) {
// We only decompile backward jumps
if (!IsBackwardsJump(goto_node, label)) {
it++;
continue;
}
}
if (IndirectlyRelated(goto_node, label)) {
while (!DirectlyRelated(goto_node, label)) {
MoveOutward(goto_node);
}
}
if (DirectlyRelated(goto_node, label)) {
u32 goto_level = goto_node->GetLevel();
const u32 label_level = label->GetLevel();
while (label_level < goto_level) {
MoveOutward(goto_node);
goto_level--;
}
// TODO(Blinkhawk): Implement Lifting and Inward Movements
}
if (label->GetParent() == goto_node->GetParent()) {
bool is_loop = false;
ASTNode current = goto_node->GetPrevious();
while (current) {
if (current == label) {
is_loop = true;
break;
}
current = current->GetPrevious();
}
if (is_loop) {
EncloseDoWhile(goto_node, label);
} else {
EncloseIfThen(goto_node, label);
}
it = gotos.erase(it);
continue;
}
it++;
}
if (full_decompile) {
for (const ASTNode& label : labels) {
auto& manager = label->GetManager();
manager.Remove(label);
}
labels.clear();
} else {
auto it = labels.begin();
while (it != labels.end()) {
bool can_remove = true;
ASTNode label = *it;
for (const ASTNode& goto_node : gotos) {
const auto label_index = goto_node->GetGotoLabel();
if (!label_index) {
return;
}
ASTNode& glabel = labels[*label_index];
if (glabel == label) {
can_remove = false;
break;
}
}
if (can_remove) {
label->MarkLabelUnused();
}
}
}
}
bool ASTManager::IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const {
u32 goto_level = goto_node->GetLevel();
u32 label_level = label_node->GetLevel();
while (goto_level > label_level) {
goto_level--;
goto_node = goto_node->GetParent();
}
while (label_level > goto_level) {
label_level--;
label_node = label_node->GetParent();
}
while (goto_node->GetParent() != label_node->GetParent()) {
goto_node = goto_node->GetParent();
label_node = label_node->GetParent();
}
ASTNode current = goto_node->GetPrevious();
while (current) {
if (current == label_node) {
return true;
}
current = current->GetPrevious();
}
return false;
}
bool ASTManager::IndirectlyRelated(ASTNode first, ASTNode second) {
return !(first->GetParent() == second->GetParent() || DirectlyRelated(first, second));
}
bool ASTManager::DirectlyRelated(ASTNode first, ASTNode second) {
if (first->GetParent() == second->GetParent()) {
return false;
}
const u32 first_level = first->GetLevel();
const u32 second_level = second->GetLevel();
u32 min_level;
u32 max_level;
ASTNode max;
ASTNode min;
if (first_level > second_level) {
min_level = second_level;
min = second;
max_level = first_level;
max = first;
} else {
min_level = first_level;
min = first;
max_level = second_level;
max = second;
}
while (max_level > min_level) {
max_level--;
max = max->GetParent();
}
return min->GetParent() == max->GetParent();
}
void ASTManager::ShowCurrentState(std::string state) {
LOG_CRITICAL(HW_GPU, "\nState {}:\n\n{}\n", state, Print());
SanityCheck();
}
void ASTManager::SanityCheck() {
for (auto& label : labels) {
if (!label->GetParent()) {
LOG_CRITICAL(HW_GPU, "Sanity Check Failed");
}
}
}
void ASTManager::EncloseDoWhile(ASTNode goto_node, ASTNode label) {
ASTZipper& zipper = goto_node->GetManager();
const ASTNode loop_start = label->GetNext();
if (loop_start == goto_node) {
zipper.Remove(goto_node);
return;
}
const ASTNode parent = label->GetParent();
const Expr condition = goto_node->GetGotoCondition();
zipper.DetachSegment(loop_start, goto_node);
const ASTNode do_while_node = ASTBase::Make<ASTDoWhile>(parent, condition);
ASTZipper* sub_zipper = do_while_node->GetSubNodes();
sub_zipper->Init(loop_start, do_while_node);
zipper.InsertAfter(do_while_node, label);
sub_zipper->Remove(goto_node);
}
void ASTManager::EncloseIfThen(ASTNode goto_node, ASTNode label) {
ASTZipper& zipper = goto_node->GetManager();
const ASTNode if_end = label->GetPrevious();
if (if_end == goto_node) {
zipper.Remove(goto_node);
return;
}
const ASTNode prev = goto_node->GetPrevious();
const Expr condition = goto_node->GetGotoCondition();
bool do_else = false;
if (!disable_else_derivation && prev->IsIfThen()) {
const Expr if_condition = prev->GetIfCondition();
do_else = ExprAreEqual(if_condition, condition);
}
const ASTNode parent = label->GetParent();
zipper.DetachSegment(goto_node, if_end);
ASTNode if_node;
if (do_else) {
if_node = ASTBase::Make<ASTIfElse>(parent);
} else {
Expr neg_condition = MakeExprNot(condition);
if_node = ASTBase::Make<ASTIfThen>(parent, neg_condition);
}
ASTZipper* sub_zipper = if_node->GetSubNodes();
sub_zipper->Init(goto_node, if_node);
zipper.InsertAfter(if_node, prev);
sub_zipper->Remove(goto_node);
}
void ASTManager::MoveOutward(ASTNode goto_node) {
ASTZipper& zipper = goto_node->GetManager();
const ASTNode parent = goto_node->GetParent();
ASTZipper& zipper2 = parent->GetManager();
const ASTNode grandpa = parent->GetParent();
const bool is_loop = parent->IsLoop();
const bool is_else = parent->IsIfElse();
const bool is_if = parent->IsIfThen();
const ASTNode prev = goto_node->GetPrevious();
const ASTNode post = goto_node->GetNext();
const Expr condition = goto_node->GetGotoCondition();
zipper.DetachSingle(goto_node);
if (is_loop) {
const u32 var_index = NewVariable();
const Expr var_condition = MakeExpr<ExprVar>(var_index);
const ASTNode var_node = ASTBase::Make<ASTVarSet>(parent, var_index, condition);
const ASTNode var_node_init = ASTBase::Make<ASTVarSet>(parent, var_index, false_condition);
zipper2.InsertBefore(var_node_init, parent);
zipper.InsertAfter(var_node, prev);
goto_node->SetGotoCondition(var_condition);
const ASTNode break_node = ASTBase::Make<ASTBreak>(parent, var_condition);
zipper.InsertAfter(break_node, var_node);
} else if (is_if || is_else) {
const u32 var_index = NewVariable();
const Expr var_condition = MakeExpr<ExprVar>(var_index);
const ASTNode var_node = ASTBase::Make<ASTVarSet>(parent, var_index, condition);
const ASTNode var_node_init = ASTBase::Make<ASTVarSet>(parent, var_index, false_condition);
if (is_if) {
zipper2.InsertBefore(var_node_init, parent);
} else {
zipper2.InsertBefore(var_node_init, parent->GetPrevious());
}
zipper.InsertAfter(var_node, prev);
goto_node->SetGotoCondition(var_condition);
if (post) {
zipper.DetachTail(post);
const ASTNode if_node = ASTBase::Make<ASTIfThen>(parent, MakeExprNot(var_condition));
ASTZipper* sub_zipper = if_node->GetSubNodes();
sub_zipper->Init(post, if_node);
zipper.InsertAfter(if_node, var_node);
}
} else {
UNREACHABLE();
}
const ASTNode next = parent->GetNext();
if (is_if && next && next->IsIfElse()) {
zipper2.InsertAfter(goto_node, next);
goto_node->SetParent(grandpa);
return;
}
zipper2.InsertAfter(goto_node, parent);
goto_node->SetParent(grandpa);
}
class ASTClearer {
public:
ASTClearer() = default;
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) {
ast.nodes.clear();
}
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());
node->Clear();
}
};
void ASTManager::Clear() {
if (!main_node) {
return;
}
ASTClearer clearer{};
clearer.Visit(main_node);
main_node.reset();
program = nullptr;
labels_map.clear();
labels.clear();
gotos.clear();
}
} // namespace VideoCommon::Shader

391
src/video_core/shader/ast.h Normal file
View file

@ -0,0 +1,391 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <list>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "video_core/shader/expr.h"
#include "video_core/shader/node.h"
namespace VideoCommon::Shader {
class ASTBase;
class ASTProgram;
class ASTIfThen;
class ASTIfElse;
class ASTBlockEncoded;
class ASTBlockDecoded;
class ASTVarSet;
class ASTGoto;
class ASTLabel;
class ASTDoWhile;
class ASTReturn;
class ASTBreak;
using ASTData = std::variant<ASTProgram, ASTIfThen, ASTIfElse, ASTBlockEncoded, ASTBlockDecoded,
ASTVarSet, ASTGoto, ASTLabel, ASTDoWhile, ASTReturn, ASTBreak>;
using ASTNode = std::shared_ptr<ASTBase>;
enum class ASTZipperType : u32 {
Program,
IfThen,
IfElse,
Loop,
};
class ASTZipper final {
public:
explicit ASTZipper();
void Init(ASTNode first, ASTNode parent);
ASTNode GetFirst() {
return first;
}
ASTNode GetLast() {
return last;
}
void PushBack(ASTNode new_node);
void PushFront(ASTNode new_node);
void InsertAfter(ASTNode new_node, ASTNode at_node);
void InsertBefore(ASTNode new_node, ASTNode at_node);
void DetachTail(ASTNode node);
void DetachSingle(ASTNode node);
void DetachSegment(ASTNode start, ASTNode end);
void Remove(ASTNode node);
ASTNode first{};
ASTNode last{};
};
class ASTProgram {
public:
explicit ASTProgram() = default;
ASTZipper nodes{};
};
class ASTIfThen {
public:
explicit ASTIfThen(Expr condition) : condition(condition) {}
Expr condition;
ASTZipper nodes{};
};
class ASTIfElse {
public:
explicit ASTIfElse() = default;
ASTZipper nodes{};
};
class ASTBlockEncoded {
public:
explicit ASTBlockEncoded(u32 start, u32 end) : start{start}, end{end} {}
u32 start;
u32 end;
};
class ASTBlockDecoded {
public:
explicit ASTBlockDecoded(NodeBlock&& new_nodes) : nodes(std::move(new_nodes)) {}
NodeBlock nodes;
};
class ASTVarSet {
public:
explicit ASTVarSet(u32 index, Expr condition) : index{index}, condition{condition} {}
u32 index;
Expr condition;
};
class ASTLabel {
public:
explicit ASTLabel(u32 index) : index{index} {}
u32 index;
bool unused{};
};
class ASTGoto {
public:
explicit ASTGoto(Expr condition, u32 label) : condition{condition}, label{label} {}
Expr condition;
u32 label;
};
class ASTDoWhile {
public:
explicit ASTDoWhile(Expr condition) : condition(condition) {}
Expr condition;
ASTZipper nodes{};
};
class ASTReturn {
public:
explicit ASTReturn(Expr condition, bool kills) : condition{condition}, kills{kills} {}
Expr condition;
bool kills;
};
class ASTBreak {
public:
explicit ASTBreak(Expr condition) : condition{condition} {}
Expr condition;
};
class ASTBase {
public:
explicit ASTBase(ASTNode parent, ASTData data) : parent{parent}, data{data} {}
template <class U, class... Args>
static ASTNode Make(ASTNode parent, Args&&... args) {
return std::make_shared<ASTBase>(parent, ASTData(U(std::forward<Args>(args)...)));
}
void SetParent(ASTNode new_parent) {
parent = new_parent;
}
ASTNode& GetParent() {
return parent;
}
const ASTNode& GetParent() const {
return parent;
}
u32 GetLevel() const {
u32 level = 0;
auto next_parent = parent;
while (next_parent) {
next_parent = next_parent->GetParent();
level++;
}
return level;
}
ASTData* GetInnerData() {
return &data;
}
ASTNode GetNext() const {
return next;
}
ASTNode GetPrevious() const {
return previous;
}
ASTZipper& GetManager() {
return *manager;
}
std::optional<u32> GetGotoLabel() const {
auto inner = std::get_if<ASTGoto>(&data);
if (inner) {
return {inner->label};
}
return {};
}
Expr GetGotoCondition() const {
auto inner = std::get_if<ASTGoto>(&data);
if (inner) {
return inner->condition;
}
return nullptr;
}
void MarkLabelUnused() {
auto inner = std::get_if<ASTLabel>(&data);
if (inner) {
inner->unused = true;
}
}
bool IsLabelUnused() const {
auto inner = std::get_if<ASTLabel>(&data);
if (inner) {
return inner->unused;
}
return true;
}
std::optional<u32> GetLabelIndex() const {
auto inner = std::get_if<ASTLabel>(&data);
if (inner) {
return {inner->index};
}
return {};
}
Expr GetIfCondition() const {
auto inner = std::get_if<ASTIfThen>(&data);
if (inner) {
return inner->condition;
}
return nullptr;
}
void SetGotoCondition(Expr new_condition) {
auto inner = std::get_if<ASTGoto>(&data);
if (inner) {
inner->condition = new_condition;
}
}
bool IsIfThen() const {
return std::holds_alternative<ASTIfThen>(data);
}
bool IsIfElse() const {
return std::holds_alternative<ASTIfElse>(data);
}
bool IsBlockEncoded() const {
return std::holds_alternative<ASTBlockEncoded>(data);
}
void TransformBlockEncoded(NodeBlock&& nodes) {
data = ASTBlockDecoded(std::move(nodes));
}
bool IsLoop() const {
return std::holds_alternative<ASTDoWhile>(data);
}
ASTZipper* GetSubNodes() {
if (std::holds_alternative<ASTProgram>(data)) {
return &std::get_if<ASTProgram>(&data)->nodes;
}
if (std::holds_alternative<ASTIfThen>(data)) {
return &std::get_if<ASTIfThen>(&data)->nodes;
}
if (std::holds_alternative<ASTIfElse>(data)) {
return &std::get_if<ASTIfElse>(&data)->nodes;
}
if (std::holds_alternative<ASTDoWhile>(data)) {
return &std::get_if<ASTDoWhile>(&data)->nodes;
}
return nullptr;
}
void Clear() {
next.reset();
previous.reset();
parent.reset();
manager = nullptr;
}
private:
friend class ASTZipper;
ASTData data;
ASTNode parent{};
ASTNode next{};
ASTNode previous{};
ASTZipper* manager{};
};
class ASTManager final {
public:
ASTManager(bool full_decompile, bool disable_else_derivation);
~ASTManager();
ASTManager(const ASTManager& o) = delete;
ASTManager& operator=(const ASTManager& other) = delete;
ASTManager(ASTManager&& other) noexcept;
ASTManager& operator=(ASTManager&& other) noexcept;
void Init();
void DeclareLabel(u32 address);
void InsertLabel(u32 address);
void InsertGoto(Expr condition, u32 address);
void InsertBlock(u32 start_address, u32 end_address);
void InsertReturn(Expr condition, bool kills);
std::string Print();
void Decompile();
void ShowCurrentState(std::string state);
void SanityCheck();
void Clear();
bool IsFullyDecompiled() const {
if (full_decompile) {
return gotos.size() == 0;
} else {
for (ASTNode goto_node : gotos) {
auto label_index = goto_node->GetGotoLabel();
if (!label_index) {
return false;
}
ASTNode glabel = labels[*label_index];
if (IsBackwardsJump(goto_node, glabel)) {
return false;
}
}
return true;
}
}
ASTNode GetProgram() const {
return main_node;
}
u32 GetVariables() const {
return variables;
}
const std::vector<ASTNode>& GetLabels() const {
return labels;
}
private:
bool IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const;
bool IndirectlyRelated(ASTNode first, ASTNode second);
bool DirectlyRelated(ASTNode first, ASTNode second);
void EncloseDoWhile(ASTNode goto_node, ASTNode label);
void EncloseIfThen(ASTNode goto_node, ASTNode label);
void MoveOutward(ASTNode goto_node);
u32 NewVariable() {
return variables++;
}
bool full_decompile{};
bool disable_else_derivation{};
std::unordered_map<u32, u32> labels_map{};
u32 labels_count{};
std::vector<ASTNode> labels{};
std::list<ASTNode> gotos{};
u32 variables{};
ASTProgram* program{};
ASTNode main_node{};
Expr false_condition{};
};
} // namespace VideoCommon::Shader

View file

@ -0,0 +1,26 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/shader/compiler_settings.h"
namespace VideoCommon::Shader {
std::string CompileDepthAsString(const CompileDepth cd) {
switch (cd) {
case CompileDepth::BruteForce:
return "Brute Force Compile";
case CompileDepth::FlowStack:
return "Simple Flow Stack Mode";
case CompileDepth::NoFlowStack:
return "Remove Flow Stack";
case CompileDepth::DecompileBackwards:
return "Decompile Backward Jumps";
case CompileDepth::FullDecompile:
return "Full Decompilation";
default:
return "Unknown Compiler Process";
}
}
} // namespace VideoCommon::Shader

View file

@ -0,0 +1,26 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/engines/shader_bytecode.h"
namespace VideoCommon::Shader {
enum class CompileDepth : u32 {
BruteForce = 0,
FlowStack = 1,
NoFlowStack = 2,
DecompileBackwards = 3,
FullDecompile = 4,
};
std::string CompileDepthAsString(CompileDepth cd);
struct CompilerSettings {
CompileDepth depth{CompileDepth::NoFlowStack};
bool disable_else_derivation{true};
};
} // namespace VideoCommon::Shader

View file

@ -4,13 +4,14 @@
#include <list> #include <list>
#include <map> #include <map>
#include <set>
#include <stack> #include <stack>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <vector> #include <vector>
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "video_core/shader/ast.h"
#include "video_core/shader/control_flow.h" #include "video_core/shader/control_flow.h"
#include "video_core/shader/shader_ir.h" #include "video_core/shader/shader_ir.h"
@ -64,12 +65,13 @@ struct CFGRebuildState {
std::list<u32> inspect_queries{}; std::list<u32> inspect_queries{};
std::list<Query> queries{}; std::list<Query> queries{};
std::unordered_map<u32, u32> registered{}; std::unordered_map<u32, u32> registered{};
std::unordered_set<u32> labels{}; std::set<u32> labels{};
std::map<u32, u32> ssy_labels{}; std::map<u32, u32> ssy_labels{};
std::map<u32, u32> pbk_labels{}; std::map<u32, u32> pbk_labels{};
std::unordered_map<u32, BlockStack> stacks{}; std::unordered_map<u32, BlockStack> stacks{};
const ProgramCode& program_code; const ProgramCode& program_code;
const std::size_t program_size; const std::size_t program_size;
ASTManager* manager;
}; };
enum class BlockCollision : u32 { None, Found, Inside }; enum class BlockCollision : u32 { None, Found, Inside };
@ -415,38 +417,132 @@ bool TryQuery(CFGRebuildState& state) {
} }
} // Anonymous namespace } // Anonymous namespace
std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch) {
std::size_t program_size, u32 start_address) { const auto get_expr = ([&](const Condition& cond) -> Expr {
CFGRebuildState state{program_code, program_size, start_address}; Expr result{};
if (cond.cc != ConditionCode::T) {
result = MakeExpr<ExprCondCode>(cond.cc);
}
if (cond.predicate != Pred::UnusedIndex) {
u32 pred = static_cast<u32>(cond.predicate);
bool negate = false;
if (pred > 7) {
negate = true;
pred -= 8;
}
Expr extra = MakeExpr<ExprPredicate>(pred);
if (negate) {
extra = MakeExpr<ExprNot>(extra);
}
if (result) {
return MakeExpr<ExprAnd>(extra, result);
}
return extra;
}
if (result) {
return result;
}
return MakeExpr<ExprBoolean>(true);
});
if (branch.address < 0) {
if (branch.kill) {
mm.InsertReturn(get_expr(branch.condition), true);
return;
}
mm.InsertReturn(get_expr(branch.condition), false);
return;
}
mm.InsertGoto(get_expr(branch.condition), branch.address);
}
void DecompileShader(CFGRebuildState& state) {
state.manager->Init();
for (auto label : state.labels) {
state.manager->DeclareLabel(label);
}
for (auto& block : state.block_info) {
if (state.labels.count(block.start) != 0) {
state.manager->InsertLabel(block.start);
}
u32 end = block.branch.ignore ? block.end + 1 : block.end;
state.manager->InsertBlock(block.start, end);
if (!block.branch.ignore) {
InsertBranch(*state.manager, block.branch);
}
}
state.manager->Decompile();
}
std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
u32 start_address,
const CompilerSettings& settings) {
auto result_out = std::make_unique<ShaderCharacteristics>();
if (settings.depth == CompileDepth::BruteForce) {
result_out->settings.depth = CompileDepth::BruteForce;
return std::move(result_out);
}
CFGRebuildState state{program_code, program_size, start_address};
// Inspect Code and generate blocks // Inspect Code and generate blocks
state.labels.clear(); state.labels.clear();
state.labels.emplace(start_address); state.labels.emplace(start_address);
state.inspect_queries.push_back(state.start); state.inspect_queries.push_back(state.start);
while (!state.inspect_queries.empty()) { while (!state.inspect_queries.empty()) {
if (!TryInspectAddress(state)) { if (!TryInspectAddress(state)) {
return {}; result_out->settings.depth = CompileDepth::BruteForce;
return std::move(result_out);
} }
} }
bool use_flow_stack = true;
bool decompiled = false;
if (settings.depth != CompileDepth::FlowStack) {
// Decompile Stacks // Decompile Stacks
state.queries.push_back(Query{state.start, {}, {}}); state.queries.push_back(Query{state.start, {}, {}});
bool decompiled = true; decompiled = true;
while (!state.queries.empty()) { while (!state.queries.empty()) {
if (!TryQuery(state)) { if (!TryQuery(state)) {
decompiled = false; decompiled = false;
break; break;
} }
} }
}
use_flow_stack = !decompiled;
// Sort and organize results // Sort and organize results
std::sort(state.block_info.begin(), state.block_info.end(), std::sort(state.block_info.begin(), state.block_info.end(),
[](const BlockInfo& a, const BlockInfo& b) { return a.start < b.start; }); [](const BlockInfo& a, const BlockInfo& b) -> bool { return a.start < b.start; });
ShaderCharacteristics result_out{}; if (decompiled && settings.depth != CompileDepth::NoFlowStack) {
result_out.decompilable = decompiled; ASTManager manager{settings.depth != CompileDepth::DecompileBackwards,
result_out.start = start_address; settings.disable_else_derivation};
result_out.end = start_address; state.manager = &manager;
for (const auto& block : state.block_info) { DecompileShader(state);
decompiled = state.manager->IsFullyDecompiled();
if (!decompiled) {
if (settings.depth == CompileDepth::FullDecompile) {
LOG_CRITICAL(HW_GPU, "Failed to remove all the gotos!:");
} else {
LOG_CRITICAL(HW_GPU, "Failed to remove all backward gotos!:");
}
state.manager->ShowCurrentState("Of Shader");
state.manager->Clear();
} else {
auto result_out = std::make_unique<ShaderCharacteristics>();
result_out->start = start_address;
result_out->settings.depth = settings.depth;
result_out->manager = std::move(manager);
result_out->end = state.block_info.back().end + 1;
return std::move(result_out);
}
}
result_out->start = start_address;
result_out->settings.depth =
use_flow_stack ? CompileDepth::FlowStack : CompileDepth::NoFlowStack;
result_out->blocks.clear();
for (auto& block : state.block_info) {
ShaderBlock new_block{}; ShaderBlock new_block{};
new_block.start = block.start; new_block.start = block.start;
new_block.end = block.end; new_block.end = block.end;
@ -456,26 +552,24 @@ std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
new_block.branch.kills = block.branch.kill; new_block.branch.kills = block.branch.kill;
new_block.branch.address = block.branch.address; new_block.branch.address = block.branch.address;
} }
result_out.end = std::max(result_out.end, block.end); result_out->end = std::max(result_out->end, block.end);
result_out.blocks.push_back(new_block); result_out->blocks.push_back(new_block);
} }
if (result_out.decompilable) { if (!use_flow_stack) {
result_out.labels = std::move(state.labels); result_out->labels = std::move(state.labels);
return {std::move(result_out)}; return std::move(result_out);
} }
auto back = result_out->blocks.begin();
// If it's not decompilable, merge the unlabelled blocks together
auto back = result_out.blocks.begin();
auto next = std::next(back); auto next = std::next(back);
while (next != result_out.blocks.end()) { while (next != result_out->blocks.end()) {
if (state.labels.count(next->start) == 0 && next->start == back->end + 1) { if (state.labels.count(next->start) == 0 && next->start == back->end + 1) {
back->end = next->end; back->end = next->end;
next = result_out.blocks.erase(next); next = result_out->blocks.erase(next);
continue; continue;
} }
back = next; back = next;
++next; ++next;
} }
return {std::move(result_out)}; return std::move(result_out);
} }
} // namespace VideoCommon::Shader } // namespace VideoCommon::Shader

View file

@ -6,9 +6,11 @@
#include <list> #include <list>
#include <optional> #include <optional>
#include <unordered_set> #include <set>
#include "video_core/engines/shader_bytecode.h" #include "video_core/engines/shader_bytecode.h"
#include "video_core/shader/ast.h"
#include "video_core/shader/compiler_settings.h"
#include "video_core/shader/shader_ir.h" #include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader { namespace VideoCommon::Shader {
@ -67,13 +69,15 @@ struct ShaderBlock {
struct ShaderCharacteristics { struct ShaderCharacteristics {
std::list<ShaderBlock> blocks{}; std::list<ShaderBlock> blocks{};
bool decompilable{}; std::set<u32> labels{};
u32 start{}; u32 start{};
u32 end{}; u32 end{};
std::unordered_set<u32> labels{}; ASTManager manager{true, true};
CompilerSettings settings{};
}; };
std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
std::size_t program_size, u32 start_address); u32 start_address,
const CompilerSettings& settings);
} // namespace VideoCommon::Shader } // namespace VideoCommon::Shader

View file

@ -35,16 +35,85 @@ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) {
} // namespace } // 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<ASTBlockEncoded>(node->GetInnerData());
NodeBlock bb = ir.DecodeRange(block->start, block->end);
node->TransformBlockEncoded(std::move(bb));
}
}
private:
ShaderIR& ir;
};
void ShaderIR::Decode() { void ShaderIR::Decode() {
std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header)); 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); auto info = ScanFlow(program_code, program_size, main_offset, settings);
if (info) { auto& shader_info = *info;
const auto& shader_info = *info;
coverage_begin = shader_info.start; coverage_begin = shader_info.start;
coverage_end = shader_info.end; coverage_end = shader_info.end;
if (shader_info.decompilable) { switch (shader_info.settings.depth) {
case CompileDepth::FlowStack: {
for (const auto& block : shader_info.blocks) {
basic_blocks.insert({block.start, DecodeRange(block.start, block.end + 1)});
}
break;
}
case CompileDepth::NoFlowStack: {
disable_flow_stack = true; disable_flow_stack = true;
const auto insert_block = [this](NodeBlock& nodes, u32 label) { const auto insert_block = [this](NodeBlock& nodes, u32 label) {
if (label == static_cast<u32>(exit_branch)) { if (label == static_cast<u32>(exit_branch)) {
@ -69,25 +138,36 @@ void ShaderIR::Decode() {
} }
} }
insert_block(current_block, current_label); insert_block(current_block, current_label);
return; break;
} }
LOG_WARNING(HW_GPU, "Flow Stack Removing Failed! Falling back to old method"); case CompileDepth::DecompileBackwards:
// we can't decompile it, fallback to standard method case CompileDepth::FullDecompile: {
for (const auto& block : shader_info.blocks) { program_manager = std::move(shader_info.manager);
basic_blocks.insert({block.start, DecodeRange(block.start, block.end + 1)}); disable_flow_stack = true;
decompiled = true;
ASTDecoder decoder{*this};
ASTNode program = GetASTProgram();
decoder.Visit(program);
break;
} }
return; default:
} LOG_CRITICAL(HW_GPU, "Unknown decompilation mode!");
LOG_WARNING(HW_GPU, "Flow Analysis Failed! Falling back to brute force compiling"); [[fallthrough]];
case CompileDepth::BruteForce: {
// Now we need to deal with an undecompilable shader. We need to brute force
// a shader that captures every position.
coverage_begin = main_offset; coverage_begin = main_offset;
const u32 shader_end = static_cast<u32>(program_size / sizeof(u64)); const u32 shader_end = static_cast<u32>(program_size / sizeof(u64));
coverage_end = shader_end; coverage_end = shader_end;
for (u32 label = main_offset; label < shader_end; label++) { for (u32 label = main_offset; label < shader_end; label++) {
basic_blocks.insert({label, DecodeRange(label, label + 1)}); basic_blocks.insert({label, DecodeRange(label, label + 1)});
} }
break;
}
}
if (settings.depth != shader_info.settings.depth) {
LOG_WARNING(
HW_GPU, "Decompiling to this setting \"{}\" failed, downgrading to this setting \"{}\"",
CompileDepthAsString(settings.depth), CompileDepthAsString(shader_info.settings.depth));
}
} }
NodeBlock ShaderIR::DecodeRange(u32 begin, u32 end) { NodeBlock ShaderIR::DecodeRange(u32 begin, u32 end) {

View file

@ -0,0 +1,82 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <variant>
#include "video_core/shader/expr.h"
namespace VideoCommon::Shader {
bool ExprAnd::operator==(const ExprAnd& b) const {
return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
}
bool ExprOr::operator==(const ExprOr& b) const {
return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
}
bool ExprNot::operator==(const ExprNot& b) const {
return (*operand1 == *b.operand1);
}
bool ExprIsBoolean(Expr expr) {
return std::holds_alternative<ExprBoolean>(*expr);
}
bool ExprBooleanGet(Expr expr) {
return std::get_if<ExprBoolean>(expr.get())->value;
}
Expr MakeExprNot(Expr first) {
if (std::holds_alternative<ExprNot>(*first)) {
return std::get_if<ExprNot>(first.get())->operand1;
}
return MakeExpr<ExprNot>(first);
}
Expr MakeExprAnd(Expr first, Expr second) {
if (ExprIsBoolean(first)) {
return ExprBooleanGet(first) ? second : first;
}
if (ExprIsBoolean(second)) {
return ExprBooleanGet(second) ? first : second;
}
return MakeExpr<ExprAnd>(first, second);
}
Expr MakeExprOr(Expr first, Expr second) {
if (ExprIsBoolean(first)) {
return ExprBooleanGet(first) ? first : second;
}
if (ExprIsBoolean(second)) {
return ExprBooleanGet(second) ? second : first;
}
return MakeExpr<ExprOr>(first, second);
}
bool ExprAreEqual(Expr first, Expr second) {
return (*first) == (*second);
}
bool ExprAreOpposite(Expr first, Expr second) {
if (std::holds_alternative<ExprNot>(*first)) {
return ExprAreEqual(std::get_if<ExprNot>(first.get())->operand1, second);
}
if (std::holds_alternative<ExprNot>(*second)) {
return ExprAreEqual(std::get_if<ExprNot>(second.get())->operand1, first);
}
return false;
}
bool ExprIsTrue(Expr first) {
if (ExprIsBoolean(first)) {
return ExprBooleanGet(first);
}
return false;
}
} // namespace VideoCommon::Shader

View file

@ -0,0 +1,120 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <variant>
#include "video_core/engines/shader_bytecode.h"
namespace VideoCommon::Shader {
using Tegra::Shader::ConditionCode;
using Tegra::Shader::Pred;
class ExprAnd;
class ExprOr;
class ExprNot;
class ExprPredicate;
class ExprCondCode;
class ExprVar;
class ExprBoolean;
using ExprData =
std::variant<ExprVar, ExprCondCode, ExprPredicate, ExprNot, ExprOr, ExprAnd, ExprBoolean>;
using Expr = std::shared_ptr<ExprData>;
class ExprAnd final {
public:
explicit ExprAnd(Expr a, Expr b) : operand1{a}, operand2{b} {}
bool operator==(const ExprAnd& b) const;
Expr operand1;
Expr operand2;
};
class ExprOr final {
public:
explicit ExprOr(Expr a, Expr b) : operand1{a}, operand2{b} {}
bool operator==(const ExprOr& b) const;
Expr operand1;
Expr operand2;
};
class ExprNot final {
public:
explicit ExprNot(Expr a) : operand1{a} {}
bool operator==(const ExprNot& b) const;
Expr operand1;
};
class ExprVar final {
public:
explicit ExprVar(u32 index) : var_index{index} {}
bool operator==(const ExprVar& b) const {
return var_index == b.var_index;
}
u32 var_index;
};
class ExprPredicate final {
public:
explicit ExprPredicate(u32 predicate) : predicate{predicate} {}
bool operator==(const ExprPredicate& b) const {
return predicate == b.predicate;
}
u32 predicate;
};
class ExprCondCode final {
public:
explicit ExprCondCode(ConditionCode cc) : cc{cc} {}
bool operator==(const ExprCondCode& b) const {
return cc == b.cc;
}
ConditionCode cc;
};
class ExprBoolean final {
public:
explicit ExprBoolean(bool val) : value{val} {}
bool operator==(const ExprBoolean& b) const {
return value == b.value;
}
bool value;
};
template <typename T, typename... Args>
Expr MakeExpr(Args&&... args) {
static_assert(std::is_convertible_v<T, ExprData>);
return std::make_shared<ExprData>(T(std::forward<Args>(args)...));
}
bool ExprAreEqual(Expr first, Expr second);
bool ExprAreOpposite(Expr first, Expr second);
Expr MakeExprNot(Expr first);
Expr MakeExprAnd(Expr first, Expr second);
Expr MakeExprOr(Expr first, Expr second);
bool ExprIsTrue(Expr first);
} // namespace VideoCommon::Shader

View file

@ -22,8 +22,10 @@ using Tegra::Shader::PredCondition;
using Tegra::Shader::PredOperation; using Tegra::Shader::PredOperation;
using Tegra::Shader::Register; using Tegra::Shader::Register;
ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size) ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size,
: program_code{program_code}, main_offset{main_offset}, program_size{size} { CompilerSettings settings)
: program_code{program_code}, main_offset{main_offset}, program_size{size}, basic_blocks{},
program_manager{true, true}, settings{settings} {
Decode(); Decode();
} }
@ -137,7 +139,7 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff
return MakeNode<AbufNode>(index, static_cast<u32>(element), std::move(buffer)); return MakeNode<AbufNode>(index, static_cast<u32>(element), std::move(buffer));
} }
Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) { Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) const {
const Node node = MakeNode<InternalFlagNode>(flag); const Node node = MakeNode<InternalFlagNode>(flag);
if (negated) { if (negated) {
return Operation(OperationCode::LogicalNegate, node); return Operation(OperationCode::LogicalNegate, node);
@ -367,13 +369,13 @@ OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) {
return op->second; return op->second;
} }
Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) { Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) const {
switch (cc) { switch (cc) {
case Tegra::Shader::ConditionCode::NEU: case Tegra::Shader::ConditionCode::NEU:
return GetInternalFlag(InternalFlag::Zero, true); return GetInternalFlag(InternalFlag::Zero, true);
default: default:
UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast<u32>(cc)); UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast<u32>(cc));
return GetPredicate(static_cast<u64>(Pred::NeverExecute)); return MakeNode<PredicateNode>(Pred::NeverExecute, false);
} }
} }

View file

@ -15,6 +15,8 @@
#include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/shader_bytecode.h" #include "video_core/engines/shader_bytecode.h"
#include "video_core/engines/shader_header.h" #include "video_core/engines/shader_header.h"
#include "video_core/shader/ast.h"
#include "video_core/shader/compiler_settings.h"
#include "video_core/shader/node.h" #include "video_core/shader/node.h"
namespace VideoCommon::Shader { namespace VideoCommon::Shader {
@ -64,7 +66,8 @@ struct GlobalMemoryUsage {
class ShaderIR final { class ShaderIR final {
public: public:
explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size); explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size,
CompilerSettings settings);
~ShaderIR(); ~ShaderIR();
const std::map<u32, NodeBlock>& GetBasicBlocks() const { const std::map<u32, NodeBlock>& GetBasicBlocks() const {
@ -144,11 +147,31 @@ public:
return disable_flow_stack; return disable_flow_stack;
} }
bool IsDecompiled() const {
return decompiled;
}
const ASTManager& GetASTManager() const {
return program_manager;
}
ASTNode GetASTProgram() const {
return program_manager.GetProgram();
}
u32 GetASTNumVariables() const {
return program_manager.GetVariables();
}
u32 ConvertAddressToNvidiaSpace(const u32 address) const { u32 ConvertAddressToNvidiaSpace(const u32 address) const {
return (address - main_offset) * sizeof(Tegra::Shader::Instruction); return (address - main_offset) * sizeof(Tegra::Shader::Instruction);
} }
/// Returns a condition code evaluated from internal flags
Node GetConditionCode(Tegra::Shader::ConditionCode cc) const;
private: private:
friend class ASTDecoder;
void Decode(); void Decode();
NodeBlock DecodeRange(u32 begin, u32 end); NodeBlock DecodeRange(u32 begin, u32 end);
@ -213,7 +236,7 @@ private:
/// Generates a node representing an output attribute. Keeps track of used attributes. /// Generates a node representing an output attribute. Keeps track of used attributes.
Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer); Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer);
/// Generates a node representing an internal flag /// 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 /// Generates a node representing a local memory address
Node GetLocalMemory(Node address); Node GetLocalMemory(Node address);
/// Generates a node representing a shared memory address /// Generates a node representing a shared memory address
@ -271,9 +294,6 @@ private:
/// Returns a predicate combiner operation /// Returns a predicate combiner operation
OperationCode GetPredicateCombiner(Tegra::Shader::PredOperation 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 /// Accesses a texture sampler
const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler, const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler,
Tegra::Shader::TextureType type, bool is_array, bool is_shadow); Tegra::Shader::TextureType type, bool is_array, bool is_shadow);
@ -357,6 +377,7 @@ private:
const ProgramCode& program_code; const ProgramCode& program_code;
const u32 main_offset; const u32 main_offset;
const std::size_t program_size; const std::size_t program_size;
bool decompiled{};
bool disable_flow_stack{}; bool disable_flow_stack{};
u32 coverage_begin{}; u32 coverage_begin{};
@ -364,6 +385,8 @@ private:
std::map<u32, NodeBlock> basic_blocks; std::map<u32, NodeBlock> basic_blocks;
NodeBlock global_code; NodeBlock global_code;
ASTManager program_manager;
CompilerSettings settings{};
std::set<u32> used_registers; std::set<u32> used_registers;
std::set<Tegra::Shader::Pred> used_predicates; std::set<Tegra::Shader::Pred> used_predicates;