blob: 3c5ee2f46ba95659390937f751c6ca61d67a6bb2 [file] [log] [blame]
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkSpan.h"
#include "include/core/SkTypes.h"
#include "src/core/SkTHash.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/analysis/SkSLProgramUsage.h"
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLExpressionStatement.h"
#include "src/sksl/ir/SkSLFunctionDefinition.h"
#include "src/sksl/ir/SkSLNop.h"
#include "src/sksl/ir/SkSLProgram.h"
#include "src/sksl/ir/SkSLProgramElement.h"
#include "src/sksl/ir/SkSLStatement.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h"
#include "src/sksl/ir/SkSLVariableReference.h"
#include "src/sksl/transform/SkSLProgramWriter.h"
#include "src/sksl/transform/SkSLTransform.h"
#include <memory>
#include <utility>
#include <vector>
using namespace skia_private;
namespace SkSL {
class Context;
static bool eliminate_dead_local_variables(const Context& context,
SkSpan<std::unique_ptr<ProgramElement>> elements,
ProgramUsage* usage) {
class DeadLocalVariableEliminator : public ProgramWriter {
public:
DeadLocalVariableEliminator(const Context& context, ProgramUsage* usage)
: fContext(context)
, fUsage(usage) {}
using ProgramWriter::visitProgramElement;
bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
if (expr->is<BinaryExpression>()) {
// Search for expressions of the form `deadVar = anyExpression`.
BinaryExpression& binary = expr->as<BinaryExpression>();
if (VariableReference* assignedVar = binary.isAssignmentIntoVariable()) {
if (fDeadVariables.contains(assignedVar->variable())) {
// Replace `deadVar = anyExpression` with `anyExpression`.
fUsage->remove(expr.get());
expr = std::move(binary.right());
fUsage->add(expr.get());
// If `anyExpression` is now a lone ExpressionStatement, it's highly likely
// that we can eliminate it entirely. This flag will let us know to check.
fAssignmentWasEliminated = true;
// Re-process the newly cleaned-up expression. This lets us fully clean up
// gnarly assignments like `a = b = 123;` where both `a` and `b` are dead,
// or silly double-assignments like `a = a = 123;`.
return this->visitExpressionPtr(expr);
}
}
}
if (expr->is<VariableReference>()) {
SkASSERT(!fDeadVariables.contains(expr->as<VariableReference>().variable()));
}
return INHERITED::visitExpressionPtr(expr);
}
bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
if (stmt->is<VarDeclaration>()) {
VarDeclaration& varDecl = stmt->as<VarDeclaration>();
const Variable* var = varDecl.var();
ProgramUsage::VariableCounts* counts = fUsage->fVariableCounts.find(var);
SkASSERT(counts);
SkASSERT(counts->fVarExists);
if (CanEliminate(var, *counts)) {
fDeadVariables.add(var);
if (var->initialValue()) {
// The variable has an initial-value expression, which might have side
// effects. ExpressionStatement::Make will preserve side effects, but
// replaces pure expressions with Nop.
fUsage->remove(stmt.get());
stmt = ExpressionStatement::Make(fContext, std::move(varDecl.value()));
fUsage->add(stmt.get());
} else {
// The variable has no initial-value and can be cleanly eliminated.
fUsage->remove(stmt.get());
stmt = Nop::Make();
}
fMadeChanges = true;
// Re-process the newly cleaned-up statement. This lets us fully clean up
// gnarly assignments like `a = b = 123;` where both `a` and `b` are dead,
// or silly double-assignments like `a = a = 123;`.
return this->visitStatementPtr(stmt);
}
}
bool result = INHERITED::visitStatementPtr(stmt);
// If we eliminated an assignment above, we may have left behind an inert
// ExpressionStatement.
if (fAssignmentWasEliminated) {
fAssignmentWasEliminated = false;
if (stmt->is<ExpressionStatement>()) {
ExpressionStatement& exprStmt = stmt->as<ExpressionStatement>();
if (!Analysis::HasSideEffects(*exprStmt.expression())) {
// The expression-statement was inert; eliminate it entirely.
fUsage->remove(&exprStmt);
stmt = Nop::Make();
}
}
}
return result;
}
static bool CanEliminate(const Variable* var, const ProgramUsage::VariableCounts& counts) {
return counts.fVarExists && !counts.fRead && var->storage() == VariableStorage::kLocal;
}
bool fMadeChanges = false;
const Context& fContext;
ProgramUsage* fUsage;
THashSet<const Variable*> fDeadVariables;
bool fAssignmentWasEliminated = false;
using INHERITED = ProgramWriter;
};
DeadLocalVariableEliminator visitor{context, usage};
for (auto& [var, counts] : usage->fVariableCounts) {
if (DeadLocalVariableEliminator::CanEliminate(var, counts)) {
// This program contains at least one dead local variable.
// Scan the program for any dead local variables and eliminate them all.
for (std::unique_ptr<ProgramElement>& pe : elements) {
if (pe->is<FunctionDefinition>()) {
visitor.visitProgramElement(*pe);
}
}
break;
}
}
return visitor.fMadeChanges;
}
bool Transform::EliminateDeadLocalVariables(const Context& context,
Module& module,
ProgramUsage* usage) {
return eliminate_dead_local_variables(context, SkSpan(module.fElements), usage);
}
bool Transform::EliminateDeadLocalVariables(Program& program) {
return program.fConfig->fSettings.fRemoveDeadVariables
? eliminate_dead_local_variables(*program.fContext,
SkSpan(program.fOwnedElements),
program.fUsage.get())
: false;
}
} // namespace SkSL