Browse Source
Merge pull request #2888 from FernandoS27/decompiler2
Merge pull request #2888 from FernandoS27/decompiler2
Shader_IR: Implement a full control flow decompiler for the shader IR.nce_cpp
committed by
GitHub
17 changed files with 2299 additions and 160 deletions
-
6CMakeModules/GenerateSCMRev.cmake
-
6src/common/CMakeLists.txt
-
6src/video_core/CMakeLists.txt
-
272src/video_core/renderer_opengl/gl_shader_decompiler.cpp
-
16src/video_core/renderer_opengl/gl_shader_gen.cpp
-
359src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
-
766src/video_core/shader/ast.cpp
-
391src/video_core/shader/ast.h
-
26src/video_core/shader/compiler_settings.cpp
-
26src/video_core/shader/compiler_settings.h
-
154src/video_core/shader/control_flow.cpp
-
14src/video_core/shader/control_flow.h
-
170src/video_core/shader/decode.cpp
-
82src/video_core/shader/expr.cpp
-
120src/video_core/shader/expr.h
-
12src/video_core/shader/shader_ir.cpp
-
33src/video_core/shader/shader_ir.h
@ -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
|
||||
@ -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 |
||||
@ -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
|
||||
@ -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 |
||||
@ -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
|
||||
@ -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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue