shader_ir: Initial Decompile Setup
This commit is contained in:
parent
d633397883
commit
c17953978b
|
@ -105,9 +105,12 @@ add_library(video_core STATIC
|
|||
shader/decode/warp.cpp
|
||||
shader/decode/xmad.cpp
|
||||
shader/decode/other.cpp
|
||||
shader/ast.cpp
|
||||
shader/ast.h
|
||||
shader/control_flow.cpp
|
||||
shader/control_flow.h
|
||||
shader/decode.cpp
|
||||
shader/expr.h
|
||||
shader/node_helper.cpp
|
||||
shader/node_helper.h
|
||||
shader/node.h
|
||||
|
|
180
src/video_core/shader/ast.cpp
Normal file
180
src/video_core/shader/ast.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
// 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 {
|
||||
|
||||
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) {
|
||||
u32 pred = static_cast<u32>(expr.predicate);
|
||||
if (pred > 7) {
|
||||
inner += "!";
|
||||
pred -= 8;
|
||||
}
|
||||
inner += "P" + std::to_string(pred);
|
||||
}
|
||||
|
||||
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";
|
||||
for (ASTNode& node : ast.nodes) {
|
||||
Visit(node);
|
||||
}
|
||||
inner += "}\n";
|
||||
scope--;
|
||||
}
|
||||
|
||||
void operator()(ASTIf& ast) {
|
||||
ExprPrinter expr_parser{};
|
||||
std::visit(expr_parser, *ast.condition);
|
||||
inner += Ident() + "if (" + expr_parser.GetResult() + ") {\n";
|
||||
scope++;
|
||||
for (auto& node : ast.then_nodes) {
|
||||
Visit(node);
|
||||
}
|
||||
scope--;
|
||||
if (ast.else_nodes.size() > 0) {
|
||||
inner += Ident() + "} else {\n";
|
||||
scope++;
|
||||
for (auto& node : ast.else_nodes) {
|
||||
Visit(node);
|
||||
}
|
||||
scope--;
|
||||
} else {
|
||||
inner += Ident() + "}\n";
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(ASTBlockEncoded& ast) {
|
||||
inner += Ident() + "Block(" + std::to_string(ast.start) + ", " + std::to_string(ast.end) +
|
||||
");\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++;
|
||||
for (auto& node : ast.loop_nodes) {
|
||||
Visit(node);
|
||||
}
|
||||
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";
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
184
src/video_core/shader/ast.h
Normal file
184
src/video_core/shader/ast.h
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#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 ASTIf;
|
||||
class ASTBlockEncoded;
|
||||
class ASTVarSet;
|
||||
class ASTGoto;
|
||||
class ASTLabel;
|
||||
class ASTDoWhile;
|
||||
class ASTReturn;
|
||||
|
||||
using ASTData = std::variant<ASTProgram, ASTIf, ASTBlockEncoded, ASTVarSet, ASTGoto, ASTLabel,
|
||||
ASTDoWhile, ASTReturn>;
|
||||
|
||||
using ASTNode = std::shared_ptr<ASTBase>;
|
||||
|
||||
class ASTProgram {
|
||||
public:
|
||||
ASTProgram() = default;
|
||||
std::list<ASTNode> nodes;
|
||||
};
|
||||
|
||||
class ASTIf {
|
||||
public:
|
||||
ASTIf(Expr condition, std::list<ASTNode> then_nodes, std::list<ASTNode> else_nodes)
|
||||
: condition(condition), then_nodes{then_nodes}, else_nodes{then_nodes} {}
|
||||
Expr condition;
|
||||
std::list<ASTNode> then_nodes;
|
||||
std::list<ASTNode> else_nodes;
|
||||
};
|
||||
|
||||
class ASTBlockEncoded {
|
||||
public:
|
||||
ASTBlockEncoded(u32 start, u32 end) : start{start}, end{end} {}
|
||||
u32 start;
|
||||
u32 end;
|
||||
};
|
||||
|
||||
class ASTVarSet {
|
||||
public:
|
||||
ASTVarSet(u32 index, Expr condition) : index{index}, condition{condition} {}
|
||||
u32 index;
|
||||
Expr condition;
|
||||
};
|
||||
|
||||
class ASTLabel {
|
||||
public:
|
||||
ASTLabel(u32 index) : index{index} {}
|
||||
u32 index;
|
||||
};
|
||||
|
||||
class ASTGoto {
|
||||
public:
|
||||
ASTGoto(Expr condition, u32 label) : condition{condition}, label{label} {}
|
||||
Expr condition;
|
||||
u32 label;
|
||||
};
|
||||
|
||||
class ASTDoWhile {
|
||||
public:
|
||||
ASTDoWhile(Expr condition, std::list<ASTNode> loop_nodes)
|
||||
: condition(condition), loop_nodes{loop_nodes} {}
|
||||
Expr condition;
|
||||
std::list<ASTNode> loop_nodes;
|
||||
};
|
||||
|
||||
class ASTReturn {
|
||||
public:
|
||||
ASTReturn(Expr condition, bool kills) : condition{condition}, kills{kills} {}
|
||||
Expr condition;
|
||||
bool kills;
|
||||
};
|
||||
|
||||
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;
|
||||
while (next) {
|
||||
next = next->GetParent();
|
||||
level++;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
ASTData* GetInnerData() {
|
||||
return &data;
|
||||
}
|
||||
|
||||
private:
|
||||
ASTData data;
|
||||
ASTNode parent;
|
||||
};
|
||||
|
||||
class ASTManager final {
|
||||
public:
|
||||
explicit ASTManager() {
|
||||
main_node = ASTBase::Make<ASTProgram>(nullptr);
|
||||
program = std::get_if<ASTProgram>(main_node->GetInnerData());
|
||||
}
|
||||
|
||||
void DeclareLabel(u32 address) {
|
||||
const auto pair = labels_map.emplace(address, labels_count);
|
||||
if (pair.second) {
|
||||
labels_count++;
|
||||
labels.resize(labels_count);
|
||||
}
|
||||
}
|
||||
|
||||
void InsertLabel(u32 address) {
|
||||
u32 index = labels_map[address];
|
||||
ASTNode label = ASTBase::Make<ASTLabel>(main_node, index);
|
||||
labels[index] = label;
|
||||
program->nodes.push_back(label);
|
||||
}
|
||||
|
||||
void InsertGoto(Expr condition, u32 address) {
|
||||
u32 index = labels_map[address];
|
||||
ASTNode goto_node = ASTBase::Make<ASTGoto>(main_node, condition, index);
|
||||
gotos.push_back(goto_node);
|
||||
program->nodes.push_back(goto_node);
|
||||
}
|
||||
|
||||
void InsertBlock(u32 start_address, u32 end_address) {
|
||||
ASTNode block = ASTBase::Make<ASTBlockEncoded>(main_node, start_address, end_address);
|
||||
program->nodes.push_back(block);
|
||||
}
|
||||
|
||||
void InsertReturn(Expr condition, bool kills) {
|
||||
ASTNode node = ASTBase::Make<ASTReturn>(main_node, condition, kills);
|
||||
program->nodes.push_back(node);
|
||||
}
|
||||
|
||||
std::string Print();
|
||||
|
||||
void Decompile() {}
|
||||
|
||||
private:
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon::Shader
|
|
@ -4,13 +4,14 @@
|
|||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/shader/ast.h"
|
||||
#include "video_core/shader/control_flow.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
||||
|
@ -64,7 +65,7 @@ struct CFGRebuildState {
|
|||
std::list<u32> inspect_queries{};
|
||||
std::list<Query> queries{};
|
||||
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> pbk_labels{};
|
||||
std::unordered_map<u32, BlockStack> stacks{};
|
||||
|
@ -415,6 +416,54 @@ bool TryQuery(CFGRebuildState& state) {
|
|||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch) {
|
||||
const auto get_expr = ([&](const Condition& cond) -> Expr {
|
||||
Expr result{};
|
||||
if (cond.cc != ConditionCode::T) {
|
||||
result = MakeExpr<ExprCondCode>(cond.cc);
|
||||
}
|
||||
if (cond.predicate != Pred::UnusedIndex) {
|
||||
Expr extra = MakeExpr<ExprPredicate>(cond.predicate);
|
||||
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) {
|
||||
ASTManager manager{};
|
||||
for (auto label : state.labels) {
|
||||
manager.DeclareLabel(label);
|
||||
}
|
||||
for (auto& block : state.block_info) {
|
||||
if (state.labels.count(block.start) != 0) {
|
||||
manager.InsertLabel(block.start);
|
||||
}
|
||||
u32 end = block.branch.ignore ? block.end + 1 : block.end;
|
||||
manager.InsertBlock(block.start, end);
|
||||
if (!block.branch.ignore) {
|
||||
InsertBranch(manager, block.branch);
|
||||
}
|
||||
}
|
||||
manager.Decompile();
|
||||
LOG_CRITICAL(HW_GPU, "Decompiled Shader:\n{} \n", manager.Print());
|
||||
}
|
||||
|
||||
std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
||||
std::size_t program_size, u32 start_address) {
|
||||
CFGRebuildState state{program_code, program_size, start_address};
|
||||
|
@ -441,7 +490,10 @@ std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
|||
|
||||
// Sort and organize results
|
||||
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; });
|
||||
if (decompiled) {
|
||||
DecompileShader(state);
|
||||
}
|
||||
ShaderCharacteristics result_out{};
|
||||
result_out.decompilable = decompiled;
|
||||
result_out.start = start_address;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include <list>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include <set>
|
||||
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
@ -70,7 +70,7 @@ struct ShaderCharacteristics {
|
|||
bool decompilable{};
|
||||
u32 start{};
|
||||
u32 end{};
|
||||
std::unordered_set<u32> labels{};
|
||||
std::set<u32> labels{};
|
||||
};
|
||||
|
||||
std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
||||
|
|
86
src/video_core/shader/expr.h
Normal file
86
src/video_core/shader/expr.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
#include <memory>
|
||||
|
||||
#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:
|
||||
ExprAnd(Expr a, Expr b) : operand1{a}, operand2{b} {}
|
||||
|
||||
Expr operand1;
|
||||
Expr operand2;
|
||||
};
|
||||
|
||||
class ExprOr final {
|
||||
public:
|
||||
ExprOr(Expr a, Expr b) : operand1{a}, operand2{b} {}
|
||||
|
||||
Expr operand1;
|
||||
Expr operand2;
|
||||
};
|
||||
|
||||
class ExprNot final {
|
||||
public:
|
||||
ExprNot(Expr a) : operand1{a} {}
|
||||
|
||||
Expr operand1;
|
||||
};
|
||||
|
||||
class ExprVar final {
|
||||
public:
|
||||
ExprVar(u32 index) : var_index{index} {}
|
||||
|
||||
u32 var_index;
|
||||
};
|
||||
|
||||
class ExprPredicate final {
|
||||
public:
|
||||
ExprPredicate(Pred predicate) : predicate{predicate} {}
|
||||
|
||||
Pred predicate;
|
||||
};
|
||||
|
||||
class ExprCondCode final {
|
||||
public:
|
||||
ExprCondCode(ConditionCode cc) : cc{cc} {}
|
||||
|
||||
ConditionCode cc;
|
||||
};
|
||||
|
||||
class ExprBoolean final {
|
||||
public:
|
||||
ExprBoolean(bool val) : value{val} {}
|
||||
|
||||
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)...));
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
Loading…
Reference in a new issue