Merge pull request #2944 from lioncash/ast
video_core/shader: Minor changes
This commit is contained in:
commit
47ccfabe18
@ -17,6 +17,7 @@ void ASTZipper::Init(const ASTNode new_first, const ASTNode parent) {
|
|||||||
ASSERT(new_first->manager == nullptr);
|
ASSERT(new_first->manager == nullptr);
|
||||||
first = new_first;
|
first = new_first;
|
||||||
last = new_first;
|
last = new_first;
|
||||||
|
|
||||||
ASTNode current = first;
|
ASTNode current = first;
|
||||||
while (current) {
|
while (current) {
|
||||||
current->manager = this;
|
current->manager = this;
|
||||||
@ -92,7 +93,7 @@ void ASTZipper::InsertBefore(const ASTNode new_node, const ASTNode at_node) {
|
|||||||
new_node->manager = this;
|
new_node->manager = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASTZipper::DetachTail(const ASTNode node) {
|
void ASTZipper::DetachTail(ASTNode node) {
|
||||||
ASSERT(node->manager == this);
|
ASSERT(node->manager == this);
|
||||||
if (node == first) {
|
if (node == first) {
|
||||||
first.reset();
|
first.reset();
|
||||||
@ -103,7 +104,8 @@ void ASTZipper::DetachTail(const ASTNode node) {
|
|||||||
last = node->previous;
|
last = node->previous;
|
||||||
last->next.reset();
|
last->next.reset();
|
||||||
node->previous.reset();
|
node->previous.reset();
|
||||||
ASTNode current = node;
|
|
||||||
|
ASTNode current = std::move(node);
|
||||||
while (current) {
|
while (current) {
|
||||||
current->manager = nullptr;
|
current->manager = nullptr;
|
||||||
current->parent.reset();
|
current->parent.reset();
|
||||||
@ -185,9 +187,7 @@ void ASTZipper::Remove(const ASTNode node) {
|
|||||||
|
|
||||||
class ExprPrinter final {
|
class ExprPrinter final {
|
||||||
public:
|
public:
|
||||||
ExprPrinter() = default;
|
void operator()(const ExprAnd& expr) {
|
||||||
|
|
||||||
void operator()(ExprAnd const& expr) {
|
|
||||||
inner += "( ";
|
inner += "( ";
|
||||||
std::visit(*this, *expr.operand1);
|
std::visit(*this, *expr.operand1);
|
||||||
inner += " && ";
|
inner += " && ";
|
||||||
@ -195,7 +195,7 @@ public:
|
|||||||
inner += ')';
|
inner += ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ExprOr const& expr) {
|
void operator()(const ExprOr& expr) {
|
||||||
inner += "( ";
|
inner += "( ";
|
||||||
std::visit(*this, *expr.operand1);
|
std::visit(*this, *expr.operand1);
|
||||||
inner += " || ";
|
inner += " || ";
|
||||||
@ -203,29 +203,29 @@ public:
|
|||||||
inner += ')';
|
inner += ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ExprNot const& expr) {
|
void operator()(const ExprNot& expr) {
|
||||||
inner += "!";
|
inner += "!";
|
||||||
std::visit(*this, *expr.operand1);
|
std::visit(*this, *expr.operand1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ExprPredicate const& expr) {
|
void operator()(const ExprPredicate& expr) {
|
||||||
inner += "P" + std::to_string(expr.predicate);
|
inner += "P" + std::to_string(expr.predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ExprCondCode const& expr) {
|
void operator()(const ExprCondCode& expr) {
|
||||||
u32 cc = static_cast<u32>(expr.cc);
|
u32 cc = static_cast<u32>(expr.cc);
|
||||||
inner += "CC" + std::to_string(cc);
|
inner += "CC" + std::to_string(cc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ExprVar const& expr) {
|
void operator()(const ExprVar& expr) {
|
||||||
inner += "V" + std::to_string(expr.var_index);
|
inner += "V" + std::to_string(expr.var_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ExprBoolean const& expr) {
|
void operator()(const ExprBoolean& expr) {
|
||||||
inner += expr.value ? "true" : "false";
|
inner += expr.value ? "true" : "false";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string& GetResult() {
|
const std::string& GetResult() const {
|
||||||
return inner;
|
return inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,9 +234,7 @@ public:
|
|||||||
|
|
||||||
class ASTPrinter {
|
class ASTPrinter {
|
||||||
public:
|
public:
|
||||||
ASTPrinter() = default;
|
void operator()(const ASTProgram& ast) {
|
||||||
|
|
||||||
void operator()(ASTProgram& ast) {
|
|
||||||
scope++;
|
scope++;
|
||||||
inner += "program {\n";
|
inner += "program {\n";
|
||||||
ASTNode current = ast.nodes.GetFirst();
|
ASTNode current = ast.nodes.GetFirst();
|
||||||
@ -248,7 +246,7 @@ public:
|
|||||||
scope--;
|
scope--;
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTIfThen& ast) {
|
void operator()(const ASTIfThen& ast) {
|
||||||
ExprPrinter expr_parser{};
|
ExprPrinter expr_parser{};
|
||||||
std::visit(expr_parser, *ast.condition);
|
std::visit(expr_parser, *ast.condition);
|
||||||
inner += Ident() + "if (" + expr_parser.GetResult() + ") {\n";
|
inner += Ident() + "if (" + expr_parser.GetResult() + ") {\n";
|
||||||
@ -262,7 +260,7 @@ public:
|
|||||||
inner += Ident() + "}\n";
|
inner += Ident() + "}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTIfElse& ast) {
|
void operator()(const ASTIfElse& ast) {
|
||||||
inner += Ident() + "else {\n";
|
inner += Ident() + "else {\n";
|
||||||
scope++;
|
scope++;
|
||||||
ASTNode current = ast.nodes.GetFirst();
|
ASTNode current = ast.nodes.GetFirst();
|
||||||
@ -274,34 +272,34 @@ public:
|
|||||||
inner += Ident() + "}\n";
|
inner += Ident() + "}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTBlockEncoded& ast) {
|
void operator()(const ASTBlockEncoded& ast) {
|
||||||
inner += Ident() + "Block(" + std::to_string(ast.start) + ", " + std::to_string(ast.end) +
|
inner += Ident() + "Block(" + std::to_string(ast.start) + ", " + std::to_string(ast.end) +
|
||||||
");\n";
|
");\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTBlockDecoded& ast) {
|
void operator()(const ASTBlockDecoded& ast) {
|
||||||
inner += Ident() + "Block;\n";
|
inner += Ident() + "Block;\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTVarSet& ast) {
|
void operator()(const ASTVarSet& ast) {
|
||||||
ExprPrinter expr_parser{};
|
ExprPrinter expr_parser{};
|
||||||
std::visit(expr_parser, *ast.condition);
|
std::visit(expr_parser, *ast.condition);
|
||||||
inner +=
|
inner +=
|
||||||
Ident() + "V" + std::to_string(ast.index) + " := " + expr_parser.GetResult() + ";\n";
|
Ident() + "V" + std::to_string(ast.index) + " := " + expr_parser.GetResult() + ";\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTLabel& ast) {
|
void operator()(const ASTLabel& ast) {
|
||||||
inner += "Label_" + std::to_string(ast.index) + ":\n";
|
inner += "Label_" + std::to_string(ast.index) + ":\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTGoto& ast) {
|
void operator()(const ASTGoto& ast) {
|
||||||
ExprPrinter expr_parser{};
|
ExprPrinter expr_parser{};
|
||||||
std::visit(expr_parser, *ast.condition);
|
std::visit(expr_parser, *ast.condition);
|
||||||
inner += Ident() + "(" + expr_parser.GetResult() + ") -> goto Label_" +
|
inner += Ident() + "(" + expr_parser.GetResult() + ") -> goto Label_" +
|
||||||
std::to_string(ast.label) + ";\n";
|
std::to_string(ast.label) + ";\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTDoWhile& ast) {
|
void operator()(const ASTDoWhile& ast) {
|
||||||
ExprPrinter expr_parser{};
|
ExprPrinter expr_parser{};
|
||||||
std::visit(expr_parser, *ast.condition);
|
std::visit(expr_parser, *ast.condition);
|
||||||
inner += Ident() + "do {\n";
|
inner += Ident() + "do {\n";
|
||||||
@ -315,14 +313,14 @@ public:
|
|||||||
inner += Ident() + "} while (" + expr_parser.GetResult() + ");\n";
|
inner += Ident() + "} while (" + expr_parser.GetResult() + ");\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTReturn& ast) {
|
void operator()(const ASTReturn& ast) {
|
||||||
ExprPrinter expr_parser{};
|
ExprPrinter expr_parser{};
|
||||||
std::visit(expr_parser, *ast.condition);
|
std::visit(expr_parser, *ast.condition);
|
||||||
inner += Ident() + "(" + expr_parser.GetResult() + ") -> " +
|
inner += Ident() + "(" + expr_parser.GetResult() + ") -> " +
|
||||||
(ast.kills ? "discard" : "exit") + ";\n";
|
(ast.kills ? "discard" : "exit") + ";\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTBreak& ast) {
|
void operator()(const ASTBreak& ast) {
|
||||||
ExprPrinter expr_parser{};
|
ExprPrinter expr_parser{};
|
||||||
std::visit(expr_parser, *ast.condition);
|
std::visit(expr_parser, *ast.condition);
|
||||||
inner += Ident() + "(" + expr_parser.GetResult() + ") -> break;\n";
|
inner += Ident() + "(" + expr_parser.GetResult() + ") -> break;\n";
|
||||||
@ -341,7 +339,7 @@ public:
|
|||||||
std::visit(*this, *node->GetInnerData());
|
std::visit(*this, *node->GetInnerData());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string& GetResult() {
|
const std::string& GetResult() const {
|
||||||
return inner;
|
return inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,11 +350,9 @@ private:
|
|||||||
std::string tabs_memo{};
|
std::string tabs_memo{};
|
||||||
u32 memo_scope{};
|
u32 memo_scope{};
|
||||||
|
|
||||||
static std::string tabs;
|
static constexpr std::string_view tabs{" "};
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string ASTPrinter::tabs = " ";
|
|
||||||
|
|
||||||
std::string ASTManager::Print() {
|
std::string ASTManager::Print() {
|
||||||
ASTPrinter printer{};
|
ASTPrinter printer{};
|
||||||
printer.Visit(main_node);
|
printer.Visit(main_node);
|
||||||
@ -376,30 +372,6 @@ void ASTManager::Init() {
|
|||||||
false_condition = MakeExpr<ExprBoolean>(false);
|
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) {
|
void ASTManager::DeclareLabel(u32 address) {
|
||||||
const auto pair = labels_map.emplace(address, labels_count);
|
const auto pair = labels_map.emplace(address, labels_count);
|
||||||
if (pair.second) {
|
if (pair.second) {
|
||||||
@ -417,19 +389,19 @@ void ASTManager::InsertLabel(u32 address) {
|
|||||||
|
|
||||||
void ASTManager::InsertGoto(Expr condition, u32 address) {
|
void ASTManager::InsertGoto(Expr condition, u32 address) {
|
||||||
const u32 index = labels_map[address];
|
const u32 index = labels_map[address];
|
||||||
const ASTNode goto_node = ASTBase::Make<ASTGoto>(main_node, condition, index);
|
const ASTNode goto_node = ASTBase::Make<ASTGoto>(main_node, std::move(condition), index);
|
||||||
gotos.push_back(goto_node);
|
gotos.push_back(goto_node);
|
||||||
program->nodes.PushBack(goto_node);
|
program->nodes.PushBack(goto_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASTManager::InsertBlock(u32 start_address, u32 end_address) {
|
void ASTManager::InsertBlock(u32 start_address, u32 end_address) {
|
||||||
const ASTNode block = ASTBase::Make<ASTBlockEncoded>(main_node, start_address, end_address);
|
ASTNode block = ASTBase::Make<ASTBlockEncoded>(main_node, start_address, end_address);
|
||||||
program->nodes.PushBack(block);
|
program->nodes.PushBack(std::move(block));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASTManager::InsertReturn(Expr condition, bool kills) {
|
void ASTManager::InsertReturn(Expr condition, bool kills) {
|
||||||
const ASTNode node = ASTBase::Make<ASTReturn>(main_node, condition, kills);
|
ASTNode node = ASTBase::Make<ASTReturn>(main_node, std::move(condition), kills);
|
||||||
program->nodes.PushBack(node);
|
program->nodes.PushBack(std::move(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The decompile algorithm is based on
|
// The decompile algorithm is based on
|
||||||
@ -496,10 +468,10 @@ void ASTManager::Decompile() {
|
|||||||
}
|
}
|
||||||
labels.clear();
|
labels.clear();
|
||||||
} else {
|
} else {
|
||||||
auto it = labels.begin();
|
auto label_it = labels.begin();
|
||||||
while (it != labels.end()) {
|
while (label_it != labels.end()) {
|
||||||
bool can_remove = true;
|
bool can_remove = true;
|
||||||
ASTNode label = *it;
|
ASTNode label = *label_it;
|
||||||
for (const ASTNode& goto_node : gotos) {
|
for (const ASTNode& goto_node : gotos) {
|
||||||
const auto label_index = goto_node->GetGotoLabel();
|
const auto label_index = goto_node->GetGotoLabel();
|
||||||
if (!label_index) {
|
if (!label_index) {
|
||||||
@ -543,11 +515,11 @@ bool ASTManager::IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ASTManager::IndirectlyRelated(ASTNode first, ASTNode second) {
|
bool ASTManager::IndirectlyRelated(const ASTNode& first, const ASTNode& second) const {
|
||||||
return !(first->GetParent() == second->GetParent() || DirectlyRelated(first, second));
|
return !(first->GetParent() == second->GetParent() || DirectlyRelated(first, second));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ASTManager::DirectlyRelated(ASTNode first, ASTNode second) {
|
bool ASTManager::DirectlyRelated(const ASTNode& first, const ASTNode& second) const {
|
||||||
if (first->GetParent() == second->GetParent()) {
|
if (first->GetParent() == second->GetParent()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -577,7 +549,7 @@ bool ASTManager::DirectlyRelated(ASTNode first, ASTNode second) {
|
|||||||
return min->GetParent() == max->GetParent();
|
return min->GetParent() == max->GetParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASTManager::ShowCurrentState(std::string state) {
|
void ASTManager::ShowCurrentState(std::string_view state) {
|
||||||
LOG_CRITICAL(HW_GPU, "\nState {}:\n\n{}\n", state, Print());
|
LOG_CRITICAL(HW_GPU, "\nState {}:\n\n{}\n", state, Print());
|
||||||
SanityCheck();
|
SanityCheck();
|
||||||
}
|
}
|
||||||
@ -696,7 +668,7 @@ class ASTClearer {
|
|||||||
public:
|
public:
|
||||||
ASTClearer() = default;
|
ASTClearer() = default;
|
||||||
|
|
||||||
void operator()(ASTProgram& ast) {
|
void operator()(const ASTProgram& ast) {
|
||||||
ASTNode current = ast.nodes.GetFirst();
|
ASTNode current = ast.nodes.GetFirst();
|
||||||
while (current) {
|
while (current) {
|
||||||
Visit(current);
|
Visit(current);
|
||||||
@ -704,7 +676,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTIfThen& ast) {
|
void operator()(const ASTIfThen& ast) {
|
||||||
ASTNode current = ast.nodes.GetFirst();
|
ASTNode current = ast.nodes.GetFirst();
|
||||||
while (current) {
|
while (current) {
|
||||||
Visit(current);
|
Visit(current);
|
||||||
@ -712,7 +684,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTIfElse& ast) {
|
void operator()(const ASTIfElse& ast) {
|
||||||
ASTNode current = ast.nodes.GetFirst();
|
ASTNode current = ast.nodes.GetFirst();
|
||||||
while (current) {
|
while (current) {
|
||||||
Visit(current);
|
Visit(current);
|
||||||
@ -720,19 +692,19 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTBlockEncoded& ast) {}
|
void operator()([[maybe_unused]] const ASTBlockEncoded& ast) {}
|
||||||
|
|
||||||
void operator()(ASTBlockDecoded& ast) {
|
void operator()(ASTBlockDecoded& ast) {
|
||||||
ast.nodes.clear();
|
ast.nodes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTVarSet& ast) {}
|
void operator()([[maybe_unused]] const ASTVarSet& ast) {}
|
||||||
|
|
||||||
void operator()(ASTLabel& ast) {}
|
void operator()([[maybe_unused]] const ASTLabel& ast) {}
|
||||||
|
|
||||||
void operator()(ASTGoto& ast) {}
|
void operator()([[maybe_unused]] const ASTGoto& ast) {}
|
||||||
|
|
||||||
void operator()(ASTDoWhile& ast) {
|
void operator()(const ASTDoWhile& ast) {
|
||||||
ASTNode current = ast.nodes.GetFirst();
|
ASTNode current = ast.nodes.GetFirst();
|
||||||
while (current) {
|
while (current) {
|
||||||
Visit(current);
|
Visit(current);
|
||||||
@ -740,11 +712,11 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(ASTReturn& ast) {}
|
void operator()([[maybe_unused]] const ASTReturn& ast) {}
|
||||||
|
|
||||||
void operator()(ASTBreak& ast) {}
|
void operator()([[maybe_unused]] const ASTBreak& ast) {}
|
||||||
|
|
||||||
void Visit(ASTNode& node) {
|
void Visit(const ASTNode& node) {
|
||||||
std::visit(*this, *node->GetInnerData());
|
std::visit(*this, *node->GetInnerData());
|
||||||
node->Clear();
|
node->Clear();
|
||||||
}
|
}
|
||||||
|
@ -18,17 +18,17 @@
|
|||||||
namespace VideoCommon::Shader {
|
namespace VideoCommon::Shader {
|
||||||
|
|
||||||
class ASTBase;
|
class ASTBase;
|
||||||
class ASTProgram;
|
|
||||||
class ASTIfThen;
|
|
||||||
class ASTIfElse;
|
|
||||||
class ASTBlockEncoded;
|
|
||||||
class ASTBlockDecoded;
|
class ASTBlockDecoded;
|
||||||
class ASTVarSet;
|
class ASTBlockEncoded;
|
||||||
class ASTGoto;
|
|
||||||
class ASTLabel;
|
|
||||||
class ASTDoWhile;
|
|
||||||
class ASTReturn;
|
|
||||||
class ASTBreak;
|
class ASTBreak;
|
||||||
|
class ASTDoWhile;
|
||||||
|
class ASTGoto;
|
||||||
|
class ASTIfElse;
|
||||||
|
class ASTIfThen;
|
||||||
|
class ASTLabel;
|
||||||
|
class ASTProgram;
|
||||||
|
class ASTReturn;
|
||||||
|
class ASTVarSet;
|
||||||
|
|
||||||
using ASTData = std::variant<ASTProgram, ASTIfThen, ASTIfElse, ASTBlockEncoded, ASTBlockDecoded,
|
using ASTData = std::variant<ASTProgram, ASTIfThen, ASTIfElse, ASTBlockEncoded, ASTBlockDecoded,
|
||||||
ASTVarSet, ASTGoto, ASTLabel, ASTDoWhile, ASTReturn, ASTBreak>;
|
ASTVarSet, ASTGoto, ASTLabel, ASTDoWhile, ASTReturn, ASTBreak>;
|
||||||
@ -48,11 +48,11 @@ public:
|
|||||||
|
|
||||||
void Init(ASTNode first, ASTNode parent);
|
void Init(ASTNode first, ASTNode parent);
|
||||||
|
|
||||||
ASTNode GetFirst() {
|
ASTNode GetFirst() const {
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
ASTNode GetLast() {
|
ASTNode GetLast() const {
|
||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,20 +71,18 @@ public:
|
|||||||
|
|
||||||
class ASTProgram {
|
class ASTProgram {
|
||||||
public:
|
public:
|
||||||
explicit ASTProgram() = default;
|
|
||||||
ASTZipper nodes{};
|
ASTZipper nodes{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ASTIfThen {
|
class ASTIfThen {
|
||||||
public:
|
public:
|
||||||
explicit ASTIfThen(Expr condition) : condition(condition) {}
|
explicit ASTIfThen(Expr condition) : condition{std::move(condition)} {}
|
||||||
Expr condition;
|
Expr condition;
|
||||||
ASTZipper nodes{};
|
ASTZipper nodes{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ASTIfElse {
|
class ASTIfElse {
|
||||||
public:
|
public:
|
||||||
explicit ASTIfElse() = default;
|
|
||||||
ASTZipper nodes{};
|
ASTZipper nodes{};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,7 +101,7 @@ public:
|
|||||||
|
|
||||||
class ASTVarSet {
|
class ASTVarSet {
|
||||||
public:
|
public:
|
||||||
explicit ASTVarSet(u32 index, Expr condition) : index{index}, condition{condition} {}
|
explicit ASTVarSet(u32 index, Expr condition) : index{index}, condition{std::move(condition)} {}
|
||||||
u32 index;
|
u32 index;
|
||||||
Expr condition;
|
Expr condition;
|
||||||
};
|
};
|
||||||
@ -117,42 +115,45 @@ public:
|
|||||||
|
|
||||||
class ASTGoto {
|
class ASTGoto {
|
||||||
public:
|
public:
|
||||||
explicit ASTGoto(Expr condition, u32 label) : condition{condition}, label{label} {}
|
explicit ASTGoto(Expr condition, u32 label) : condition{std::move(condition)}, label{label} {}
|
||||||
Expr condition;
|
Expr condition;
|
||||||
u32 label;
|
u32 label;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ASTDoWhile {
|
class ASTDoWhile {
|
||||||
public:
|
public:
|
||||||
explicit ASTDoWhile(Expr condition) : condition(condition) {}
|
explicit ASTDoWhile(Expr condition) : condition{std::move(condition)} {}
|
||||||
Expr condition;
|
Expr condition;
|
||||||
ASTZipper nodes{};
|
ASTZipper nodes{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ASTReturn {
|
class ASTReturn {
|
||||||
public:
|
public:
|
||||||
explicit ASTReturn(Expr condition, bool kills) : condition{condition}, kills{kills} {}
|
explicit ASTReturn(Expr condition, bool kills)
|
||||||
|
: condition{std::move(condition)}, kills{kills} {}
|
||||||
Expr condition;
|
Expr condition;
|
||||||
bool kills;
|
bool kills;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ASTBreak {
|
class ASTBreak {
|
||||||
public:
|
public:
|
||||||
explicit ASTBreak(Expr condition) : condition{condition} {}
|
explicit ASTBreak(Expr condition) : condition{std::move(condition)} {}
|
||||||
Expr condition;
|
Expr condition;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ASTBase {
|
class ASTBase {
|
||||||
public:
|
public:
|
||||||
explicit ASTBase(ASTNode parent, ASTData data) : parent{parent}, data{data} {}
|
explicit ASTBase(ASTNode parent, ASTData data)
|
||||||
|
: data{std::move(data)}, parent{std::move(parent)} {}
|
||||||
|
|
||||||
template <class U, class... Args>
|
template <class U, class... Args>
|
||||||
static ASTNode Make(ASTNode parent, Args&&... args) {
|
static ASTNode Make(ASTNode parent, Args&&... args) {
|
||||||
return std::make_shared<ASTBase>(parent, ASTData(U(std::forward<Args>(args)...)));
|
return std::make_shared<ASTBase>(std::move(parent),
|
||||||
|
ASTData(U(std::forward<Args>(args)...)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetParent(ASTNode new_parent) {
|
void SetParent(ASTNode new_parent) {
|
||||||
parent = new_parent;
|
parent = std::move(new_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
ASTNode& GetParent() {
|
ASTNode& GetParent() {
|
||||||
@ -177,6 +178,10 @@ public:
|
|||||||
return &data;
|
return &data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ASTData* GetInnerData() const {
|
||||||
|
return &data;
|
||||||
|
}
|
||||||
|
|
||||||
ASTNode GetNext() const {
|
ASTNode GetNext() const {
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
@ -189,6 +194,10 @@ public:
|
|||||||
return *manager;
|
return *manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ASTZipper& GetManager() const {
|
||||||
|
return *manager;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<u32> GetGotoLabel() const {
|
std::optional<u32> GetGotoLabel() const {
|
||||||
auto inner = std::get_if<ASTGoto>(&data);
|
auto inner = std::get_if<ASTGoto>(&data);
|
||||||
if (inner) {
|
if (inner) {
|
||||||
@ -239,7 +248,7 @@ public:
|
|||||||
void SetGotoCondition(Expr new_condition) {
|
void SetGotoCondition(Expr new_condition) {
|
||||||
auto inner = std::get_if<ASTGoto>(&data);
|
auto inner = std::get_if<ASTGoto>(&data);
|
||||||
if (inner) {
|
if (inner) {
|
||||||
inner->condition = new_condition;
|
inner->condition = std::move(new_condition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,8 +313,8 @@ public:
|
|||||||
ASTManager(const ASTManager& o) = delete;
|
ASTManager(const ASTManager& o) = delete;
|
||||||
ASTManager& operator=(const ASTManager& other) = delete;
|
ASTManager& operator=(const ASTManager& other) = delete;
|
||||||
|
|
||||||
ASTManager(ASTManager&& other) noexcept;
|
ASTManager(ASTManager&& other) noexcept = default;
|
||||||
ASTManager& operator=(ASTManager&& other) noexcept;
|
ASTManager& operator=(ASTManager&& other) noexcept = default;
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
@ -323,7 +332,7 @@ public:
|
|||||||
|
|
||||||
void Decompile();
|
void Decompile();
|
||||||
|
|
||||||
void ShowCurrentState(std::string state);
|
void ShowCurrentState(std::string_view state);
|
||||||
|
|
||||||
void SanityCheck();
|
void SanityCheck();
|
||||||
|
|
||||||
@ -331,8 +340,9 @@ public:
|
|||||||
|
|
||||||
bool IsFullyDecompiled() const {
|
bool IsFullyDecompiled() const {
|
||||||
if (full_decompile) {
|
if (full_decompile) {
|
||||||
return gotos.size() == 0;
|
return gotos.empty();
|
||||||
} else {
|
}
|
||||||
|
|
||||||
for (ASTNode goto_node : gotos) {
|
for (ASTNode goto_node : gotos) {
|
||||||
auto label_index = goto_node->GetGotoLabel();
|
auto label_index = goto_node->GetGotoLabel();
|
||||||
if (!label_index) {
|
if (!label_index) {
|
||||||
@ -345,7 +355,6 @@ public:
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ASTNode GetProgram() const {
|
ASTNode GetProgram() const {
|
||||||
return main_node;
|
return main_node;
|
||||||
@ -362,9 +371,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
bool IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const;
|
bool IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const;
|
||||||
|
|
||||||
bool IndirectlyRelated(ASTNode first, ASTNode second);
|
bool IndirectlyRelated(const ASTNode& first, const ASTNode& second) const;
|
||||||
|
|
||||||
bool DirectlyRelated(ASTNode first, ASTNode second);
|
bool DirectlyRelated(const ASTNode& first, const ASTNode& second) const;
|
||||||
|
|
||||||
void EncloseDoWhile(ASTNode goto_node, ASTNode label);
|
void EncloseDoWhile(ASTNode goto_node, ASTNode label);
|
||||||
|
|
||||||
|
@ -479,7 +479,7 @@ std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
|||||||
auto result_out = std::make_unique<ShaderCharacteristics>();
|
auto result_out = std::make_unique<ShaderCharacteristics>();
|
||||||
if (settings.depth == CompileDepth::BruteForce) {
|
if (settings.depth == CompileDepth::BruteForce) {
|
||||||
result_out->settings.depth = CompileDepth::BruteForce;
|
result_out->settings.depth = CompileDepth::BruteForce;
|
||||||
return std::move(result_out);
|
return result_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
CFGRebuildState state{program_code, program_size, start_address};
|
CFGRebuildState state{program_code, program_size, start_address};
|
||||||
@ -490,7 +490,7 @@ std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
|||||||
while (!state.inspect_queries.empty()) {
|
while (!state.inspect_queries.empty()) {
|
||||||
if (!TryInspectAddress(state)) {
|
if (!TryInspectAddress(state)) {
|
||||||
result_out->settings.depth = CompileDepth::BruteForce;
|
result_out->settings.depth = CompileDepth::BruteForce;
|
||||||
return std::move(result_out);
|
return result_out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,14 +530,15 @@ std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
|||||||
state.manager->ShowCurrentState("Of Shader");
|
state.manager->ShowCurrentState("Of Shader");
|
||||||
state.manager->Clear();
|
state.manager->Clear();
|
||||||
} else {
|
} else {
|
||||||
auto result_out = std::make_unique<ShaderCharacteristics>();
|
auto characteristics = std::make_unique<ShaderCharacteristics>();
|
||||||
result_out->start = start_address;
|
characteristics->start = start_address;
|
||||||
result_out->settings.depth = settings.depth;
|
characteristics->settings.depth = settings.depth;
|
||||||
result_out->manager = std::move(manager);
|
characteristics->manager = std::move(manager);
|
||||||
result_out->end = state.block_info.back().end + 1;
|
characteristics->end = state.block_info.back().end + 1;
|
||||||
return std::move(result_out);
|
return characteristics;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result_out->start = start_address;
|
result_out->start = start_address;
|
||||||
result_out->settings.depth =
|
result_out->settings.depth =
|
||||||
use_flow_stack ? CompileDepth::FlowStack : CompileDepth::NoFlowStack;
|
use_flow_stack ? CompileDepth::FlowStack : CompileDepth::NoFlowStack;
|
||||||
@ -557,8 +558,9 @@ std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
|||||||
}
|
}
|
||||||
if (!use_flow_stack) {
|
if (!use_flow_stack) {
|
||||||
result_out->labels = std::move(state.labels);
|
result_out->labels = std::move(state.labels);
|
||||||
return std::move(result_out);
|
return result_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto back = result_out->blocks.begin();
|
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()) {
|
||||||
@ -570,6 +572,7 @@ std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
|||||||
back = next;
|
back = next;
|
||||||
++next;
|
++next;
|
||||||
}
|
}
|
||||||
return std::move(result_out);
|
|
||||||
|
return result_out;
|
||||||
}
|
}
|
||||||
} // namespace VideoCommon::Shader
|
} // namespace VideoCommon::Shader
|
||||||
|
@ -2,40 +2,51 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include "video_core/shader/expr.h"
|
#include "video_core/shader/expr.h"
|
||||||
|
|
||||||
namespace VideoCommon::Shader {
|
namespace VideoCommon::Shader {
|
||||||
|
namespace {
|
||||||
|
bool ExprIsBoolean(const Expr& expr) {
|
||||||
|
return std::holds_alternative<ExprBoolean>(*expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExprBooleanGet(const Expr& expr) {
|
||||||
|
return std::get_if<ExprBoolean>(expr.get())->value;
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
bool ExprAnd::operator==(const ExprAnd& b) const {
|
bool ExprAnd::operator==(const ExprAnd& b) const {
|
||||||
return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
|
return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ExprAnd::operator!=(const ExprAnd& b) const {
|
||||||
|
return !operator==(b);
|
||||||
|
}
|
||||||
|
|
||||||
bool ExprOr::operator==(const ExprOr& b) const {
|
bool ExprOr::operator==(const ExprOr& b) const {
|
||||||
return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
|
return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ExprOr::operator!=(const ExprOr& b) const {
|
||||||
|
return !operator==(b);
|
||||||
|
}
|
||||||
|
|
||||||
bool ExprNot::operator==(const ExprNot& b) const {
|
bool ExprNot::operator==(const ExprNot& b) const {
|
||||||
return (*operand1 == *b.operand1);
|
return *operand1 == *b.operand1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExprIsBoolean(Expr expr) {
|
bool ExprNot::operator!=(const ExprNot& b) const {
|
||||||
return std::holds_alternative<ExprBoolean>(*expr);
|
return !operator==(b);
|
||||||
}
|
|
||||||
|
|
||||||
bool ExprBooleanGet(Expr expr) {
|
|
||||||
return std::get_if<ExprBoolean>(expr.get())->value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr MakeExprNot(Expr first) {
|
Expr MakeExprNot(Expr first) {
|
||||||
if (std::holds_alternative<ExprNot>(*first)) {
|
if (std::holds_alternative<ExprNot>(*first)) {
|
||||||
return std::get_if<ExprNot>(first.get())->operand1;
|
return std::get_if<ExprNot>(first.get())->operand1;
|
||||||
}
|
}
|
||||||
return MakeExpr<ExprNot>(first);
|
return MakeExpr<ExprNot>(std::move(first));
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr MakeExprAnd(Expr first, Expr second) {
|
Expr MakeExprAnd(Expr first, Expr second) {
|
||||||
@ -45,7 +56,7 @@ Expr MakeExprAnd(Expr first, Expr second) {
|
|||||||
if (ExprIsBoolean(second)) {
|
if (ExprIsBoolean(second)) {
|
||||||
return ExprBooleanGet(second) ? first : second;
|
return ExprBooleanGet(second) ? first : second;
|
||||||
}
|
}
|
||||||
return MakeExpr<ExprAnd>(first, second);
|
return MakeExpr<ExprAnd>(std::move(first), std::move(second));
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr MakeExprOr(Expr first, Expr second) {
|
Expr MakeExprOr(Expr first, Expr second) {
|
||||||
@ -55,14 +66,14 @@ Expr MakeExprOr(Expr first, Expr second) {
|
|||||||
if (ExprIsBoolean(second)) {
|
if (ExprIsBoolean(second)) {
|
||||||
return ExprBooleanGet(second) ? second : first;
|
return ExprBooleanGet(second) ? second : first;
|
||||||
}
|
}
|
||||||
return MakeExpr<ExprOr>(first, second);
|
return MakeExpr<ExprOr>(std::move(first), std::move(second));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExprAreEqual(Expr first, Expr second) {
|
bool ExprAreEqual(const Expr& first, const Expr& second) {
|
||||||
return (*first) == (*second);
|
return (*first) == (*second);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExprAreOpposite(Expr first, Expr second) {
|
bool ExprAreOpposite(const Expr& first, const Expr& second) {
|
||||||
if (std::holds_alternative<ExprNot>(*first)) {
|
if (std::holds_alternative<ExprNot>(*first)) {
|
||||||
return ExprAreEqual(std::get_if<ExprNot>(first.get())->operand1, second);
|
return ExprAreEqual(std::get_if<ExprNot>(first.get())->operand1, second);
|
||||||
}
|
}
|
||||||
@ -72,7 +83,7 @@ bool ExprAreOpposite(Expr first, Expr second) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExprIsTrue(Expr first) {
|
bool ExprIsTrue(const Expr& first) {
|
||||||
if (ExprIsBoolean(first)) {
|
if (ExprIsBoolean(first)) {
|
||||||
return ExprBooleanGet(first);
|
return ExprBooleanGet(first);
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,12 @@ using Tegra::Shader::ConditionCode;
|
|||||||
using Tegra::Shader::Pred;
|
using Tegra::Shader::Pred;
|
||||||
|
|
||||||
class ExprAnd;
|
class ExprAnd;
|
||||||
class ExprOr;
|
|
||||||
class ExprNot;
|
|
||||||
class ExprPredicate;
|
|
||||||
class ExprCondCode;
|
|
||||||
class ExprVar;
|
|
||||||
class ExprBoolean;
|
class ExprBoolean;
|
||||||
|
class ExprCondCode;
|
||||||
|
class ExprNot;
|
||||||
|
class ExprOr;
|
||||||
|
class ExprPredicate;
|
||||||
|
class ExprVar;
|
||||||
|
|
||||||
using ExprData =
|
using ExprData =
|
||||||
std::variant<ExprVar, ExprCondCode, ExprPredicate, ExprNot, ExprOr, ExprAnd, ExprBoolean>;
|
std::variant<ExprVar, ExprCondCode, ExprPredicate, ExprNot, ExprOr, ExprAnd, ExprBoolean>;
|
||||||
@ -28,9 +28,10 @@ using Expr = std::shared_ptr<ExprData>;
|
|||||||
|
|
||||||
class ExprAnd final {
|
class ExprAnd final {
|
||||||
public:
|
public:
|
||||||
explicit ExprAnd(Expr a, Expr b) : operand1{a}, operand2{b} {}
|
explicit ExprAnd(Expr a, Expr b) : operand1{std::move(a)}, operand2{std::move(b)} {}
|
||||||
|
|
||||||
bool operator==(const ExprAnd& b) const;
|
bool operator==(const ExprAnd& b) const;
|
||||||
|
bool operator!=(const ExprAnd& b) const;
|
||||||
|
|
||||||
Expr operand1;
|
Expr operand1;
|
||||||
Expr operand2;
|
Expr operand2;
|
||||||
@ -38,9 +39,10 @@ public:
|
|||||||
|
|
||||||
class ExprOr final {
|
class ExprOr final {
|
||||||
public:
|
public:
|
||||||
explicit ExprOr(Expr a, Expr b) : operand1{a}, operand2{b} {}
|
explicit ExprOr(Expr a, Expr b) : operand1{std::move(a)}, operand2{std::move(b)} {}
|
||||||
|
|
||||||
bool operator==(const ExprOr& b) const;
|
bool operator==(const ExprOr& b) const;
|
||||||
|
bool operator!=(const ExprOr& b) const;
|
||||||
|
|
||||||
Expr operand1;
|
Expr operand1;
|
||||||
Expr operand2;
|
Expr operand2;
|
||||||
@ -48,9 +50,10 @@ public:
|
|||||||
|
|
||||||
class ExprNot final {
|
class ExprNot final {
|
||||||
public:
|
public:
|
||||||
explicit ExprNot(Expr a) : operand1{a} {}
|
explicit ExprNot(Expr a) : operand1{std::move(a)} {}
|
||||||
|
|
||||||
bool operator==(const ExprNot& b) const;
|
bool operator==(const ExprNot& b) const;
|
||||||
|
bool operator!=(const ExprNot& b) const;
|
||||||
|
|
||||||
Expr operand1;
|
Expr operand1;
|
||||||
};
|
};
|
||||||
@ -63,6 +66,10 @@ public:
|
|||||||
return var_index == b.var_index;
|
return var_index == b.var_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator!=(const ExprVar& b) const {
|
||||||
|
return !operator==(b);
|
||||||
|
}
|
||||||
|
|
||||||
u32 var_index;
|
u32 var_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -74,6 +81,10 @@ public:
|
|||||||
return predicate == b.predicate;
|
return predicate == b.predicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator!=(const ExprPredicate& b) const {
|
||||||
|
return !operator==(b);
|
||||||
|
}
|
||||||
|
|
||||||
u32 predicate;
|
u32 predicate;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,6 +96,10 @@ public:
|
|||||||
return cc == b.cc;
|
return cc == b.cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator!=(const ExprCondCode& b) const {
|
||||||
|
return !operator==(b);
|
||||||
|
}
|
||||||
|
|
||||||
ConditionCode cc;
|
ConditionCode cc;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -96,6 +111,10 @@ public:
|
|||||||
return value == b.value;
|
return value == b.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator!=(const ExprBoolean& b) const {
|
||||||
|
return !operator==(b);
|
||||||
|
}
|
||||||
|
|
||||||
bool value;
|
bool value;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,9 +124,9 @@ Expr MakeExpr(Args&&... args) {
|
|||||||
return std::make_shared<ExprData>(T(std::forward<Args>(args)...));
|
return std::make_shared<ExprData>(T(std::forward<Args>(args)...));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExprAreEqual(Expr first, Expr second);
|
bool ExprAreEqual(const Expr& first, const Expr& second);
|
||||||
|
|
||||||
bool ExprAreOpposite(Expr first, Expr second);
|
bool ExprAreOpposite(const Expr& first, const Expr& second);
|
||||||
|
|
||||||
Expr MakeExprNot(Expr first);
|
Expr MakeExprNot(Expr first);
|
||||||
|
|
||||||
@ -115,6 +134,6 @@ Expr MakeExprAnd(Expr first, Expr second);
|
|||||||
|
|
||||||
Expr MakeExprOr(Expr first, Expr second);
|
Expr MakeExprOr(Expr first, Expr second);
|
||||||
|
|
||||||
bool ExprIsTrue(Expr first);
|
bool ExprIsTrue(const Expr& first);
|
||||||
|
|
||||||
} // namespace VideoCommon::Shader
|
} // namespace VideoCommon::Shader
|
||||||
|
Loading…
Reference in New Issue
Block a user