Add ProgramUsage side-car to track variable and function references

Automatically computed once IR generation is complete, and then used
throughout optimization and inlining, and the backend generators.
Optimization and inlining are reworked to keep the data up-to-date as
they edit the IR.

Bug: skia:10589
Change-Id: Iaded42d8157732dd6fe05f74c5b7bb8366916635
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/328382
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2020-10-19 16:34:10 -04:00 committed by Skia Commit-Bot
parent 0a7e2fa5d5
commit 010ce6a15a
13 changed files with 228 additions and 161 deletions

View File

@ -181,17 +181,41 @@ private:
using INHERITED = ProgramVisitor;
};
class CallCountVisitor : public ProgramVisitor {
class ProgramUsageVisitor : public ProgramVisitor {
public:
ProgramUsageVisitor(ProgramUsage* usage, int delta) : fUsage(usage), fDelta(delta) {}
bool visitExpression(const Expression& e) override {
if (e.is<FunctionCall>()) {
const FunctionDeclaration* f = &e.as<FunctionCall>().function();
fCounts[f]++;
fUsage->fCallCounts[f] += fDelta;
SkASSERT(fUsage->fCallCounts[f] >= 0);
} else if (e.is<VariableReference>()) {
const VariableReference& ref = e.as<VariableReference>();
ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[ref.variable()];
switch (ref.refKind()) {
case VariableRefKind::kRead:
counts.fRead += fDelta;
break;
case VariableRefKind::kWrite:
counts.fWrite += fDelta;
break;
case VariableRefKind::kReadWrite:
case VariableRefKind::kPointer:
counts.fRead += fDelta;
counts.fWrite += fDelta;
break;
}
SkASSERT(counts.fRead >= 0 && counts.fWrite >= 0);
}
return INHERITED::visitExpression(e);
}
Analysis::CallCountMap fCounts;
using ProgramVisitor::visitProgramElement;
using ProgramVisitor::visitStatement;
ProgramUsage* fUsage;
int fDelta;
using INHERITED = ProgramVisitor;
};
@ -341,10 +365,68 @@ bool Analysis::NodeCountExceeds(const FunctionDefinition& function, int limit) {
return NodeCountVisitor{limit}.visit(*function.body()) > limit;
}
Analysis::CallCountMap Analysis::GetCallCounts(const Program& program) {
CallCountVisitor visitor;
visitor.visit(program);
return std::move(visitor.fCounts);
std::unique_ptr<ProgramUsage> Analysis::GetUsage(const Program& program) {
auto usage = std::make_unique<ProgramUsage>();
ProgramUsageVisitor addRefs(usage.get(), /*delta=*/+1);
addRefs.visit(program);
return usage;
}
ProgramUsage::VariableCounts ProgramUsage::get(const Variable& v) const {
VariableCounts result = { 0, v.initialValue() ? 1 : 0 };
if (const VariableCounts* counts = fVariableCounts.find(&v)) {
result.fRead += counts->fRead;
result.fWrite += counts->fWrite;
}
return result;
}
bool ProgramUsage::isDead(const Variable& v) const {
const Modifiers& modifiers = v.modifiers();
VariableCounts counts = this->get(v);
if ((v.storage() != Variable::Storage::kLocal && counts.fRead) ||
(modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag | Modifiers::kUniform_Flag |
Modifiers::kVarying_Flag))) {
return false;
}
return !counts.fWrite || (!counts.fRead && !(modifiers.fFlags &
(Modifiers::kPLS_Flag | Modifiers::kPLSOut_Flag)));
}
int ProgramUsage::get(const FunctionDeclaration& f) const {
const int* count = fCallCounts.find(&f);
return count ? *count : 0;
}
void ProgramUsage::replace(const Expression* oldExpr, const Expression* newExpr) {
if (oldExpr) {
ProgramUsageVisitor subRefs(this, /*delta=*/-1);
subRefs.visitExpression(*oldExpr);
}
if (newExpr) {
ProgramUsageVisitor addRefs(this, /*delta=*/+1);
addRefs.visitExpression(*newExpr);
}
}
void ProgramUsage::add(const Statement* stmt) {
ProgramUsageVisitor addRefs(this, /*delta=*/+1);
addRefs.visitStatement(*stmt);
}
void ProgramUsage::remove(const Expression* expr) {
ProgramUsageVisitor subRefs(this, /*delta=*/-1);
subRefs.visitExpression(*expr);
}
void ProgramUsage::remove(const Statement* stmt) {
ProgramUsageVisitor subRefs(this, /*delta=*/-1);
subRefs.visitStatement(*stmt);
}
void ProgramUsage::remove(const ProgramElement& element) {
ProgramUsageVisitor subRefs(this, /*delta=*/-1);
subRefs.visitProgramElement(element);
}
bool Analysis::StatementWritesToVariable(const Statement& stmt, const Variable& var) {
@ -388,7 +470,8 @@ bool TProgramVisitor<PROG, EXPR, STMT, ELEM>::visitExpression(EXPR e) {
case Expression::Kind::kBinary: {
auto& b = e.template as<BinaryExpression>();
return this->visitExpression(b.left()) || this->visitExpression(b.right());
return (b.leftPointer() && this->visitExpression(b.left())) ||
(b.rightPointer() && this->visitExpression(b.right()));
}
case Expression::Kind::kConstructor: {
auto& c = e.template as<Constructor>();
@ -410,7 +493,7 @@ bool TProgramVisitor<PROG, EXPR, STMT, ELEM>::visitExpression(EXPR e) {
case Expression::Kind::kFunctionCall: {
auto& c = e.template as<FunctionCall>();
for (auto& arg : c.arguments()) {
if (this->visitExpression(*arg)) { return true; }
if (arg && this->visitExpression(*arg)) { return true; }
}
return false;
}
@ -424,13 +507,16 @@ bool TProgramVisitor<PROG, EXPR, STMT, ELEM>::visitExpression(EXPR e) {
case Expression::Kind::kPrefix:
return this->visitExpression(*e.template as<PrefixExpression>().operand());
case Expression::Kind::kSwizzle:
return this->visitExpression(*e.template as<Swizzle>().base());
case Expression::Kind::kSwizzle: {
auto& s = e.template as<Swizzle>();
return s.base() && this->visitExpression(*s.base());
}
case Expression::Kind::kTernary: {
auto& t = e.template as<TernaryExpression>();
return this->visitExpression(*t.test()) || this->visitExpression(*t.ifTrue()) ||
this->visitExpression(*t.ifFalse());
return this->visitExpression(*t.test()) ||
(t.ifTrue() && this->visitExpression(*t.ifTrue())) ||
(t.ifFalse() && this->visitExpression(*t.ifFalse()));
}
default:
SkUNREACHABLE;
@ -450,7 +536,7 @@ bool TProgramVisitor<PROG, EXPR, STMT, ELEM>::visitStatement(STMT s) {
case Statement::Kind::kBlock:
for (auto& stmt : s.template as<Block>().children()) {
if (this->visitStatement(*stmt)) {
if (stmt && this->visitStatement(*stmt)) {
return true;
}
}
@ -473,7 +559,7 @@ bool TProgramVisitor<PROG, EXPR, STMT, ELEM>::visitStatement(STMT s) {
case Statement::Kind::kIf: {
auto& i = s.template as<IfStatement>();
return this->visitExpression(*i.test()) ||
this->visitStatement(*i.ifTrue()) ||
(i.ifTrue() && this->visitStatement(*i.ifTrue())) ||
(i.ifFalse() && this->visitStatement(*i.ifFalse()));
}
case Statement::Kind::kReturn: {
@ -490,7 +576,7 @@ bool TProgramVisitor<PROG, EXPR, STMT, ELEM>::visitStatement(STMT s) {
return true;
}
for (auto& st : c.statements()) {
if (this->visitStatement(*st)) {
if (st && this->visitStatement(*st)) {
return true;
}
}

View File

@ -9,9 +9,10 @@
#define SkSLAnalysis_DEFINED
#include "include/private/SkSLSampleUsage.h"
#include "include/private/SkTHash.h"
#include "src/sksl/SkSLDefines.h"
#include <memory>
namespace SkSL {
class ErrorReporter;
@ -20,6 +21,7 @@ class FunctionDeclaration;
struct FunctionDefinition;
struct Program;
class ProgramElement;
class ProgramUsage;
class Statement;
class Variable;
class VariableReference;
@ -37,8 +39,7 @@ struct Analysis {
static bool NodeCountExceeds(const FunctionDefinition& function, int limit);
using CallCountMap = SkTHashMap<const FunctionDeclaration*, int>;
static CallCountMap GetCallCounts(const Program& program);
static std::unique_ptr<ProgramUsage> GetUsage(const Program& program);
static bool StatementWritesToVariable(const Statement& stmt, const Variable& var);
static bool IsAssignable(Expression& expr, VariableReference** assignableVar,

View File

@ -27,6 +27,20 @@
namespace SkSL {
void BasicBlock::Node::setExpression(std::unique_ptr<Expression> expr, ProgramUsage* usage) {
SkASSERT(!this->isStatement());
usage->remove(fExpression->get());
*fExpression = std::move(expr);
}
void BasicBlock::Node::setStatement(std::unique_ptr<Statement> stmt, ProgramUsage* usage) {
SkASSERT(!this->isExpression());
// See comment in header - we assume that stmt was already counted in usage (it was a subset
// of fStatement). There is no way to verify that, unfortunately.
usage->remove(fStatement->get());
*fStatement = std::move(stmt);
}
BlockId CFG::newBlock() {
BlockId result = fBlocks.size();
fBlocks.emplace_back();

View File

@ -16,6 +16,8 @@
namespace SkSL {
class ProgramUsage;
// index of a block within CFG.fBlocks
typedef size_t BlockId;
@ -40,10 +42,9 @@ struct BasicBlock {
return fExpression;
}
void setExpression(std::unique_ptr<Expression> expr) {
SkASSERT(!this->isStatement());
*fExpression = std::move(expr);
}
// See comment below on setStatement. Assumption is that 'expr' is a strict subset of the
// existing expression.
void setExpression(std::unique_ptr<Expression> expr, ProgramUsage* usage);
bool isStatement() const {
return fStatement != nullptr;
@ -54,10 +55,12 @@ struct BasicBlock {
return fStatement;
}
void setStatement(std::unique_ptr<Statement> stmt) {
SkASSERT(!this->isExpression());
*fStatement = std::move(stmt);
}
// Replaces the pointed-to statement with 'stmt'. The assumption is that 'stmt' is a strict
// subset of the existing statement, or a Nop. For example: just the True or False of an if,
// or a single Case from a Switch. To maintain usage's bookkeeping, we remove references in
// this node's pointed-to statement. By the time this is called, there is no path from our
// statement to 'stmt', because it's been moved to the argument.
void setStatement(std::unique_ptr<Statement> stmt, ProgramUsage* usage);
#ifdef SK_DEBUG
String description() const {

View File

@ -584,22 +584,24 @@ static DefinitionMap compute_start_state(const CFG& cfg) {
/**
* Returns true if assigning to this lvalue has no effect.
*/
static bool is_dead(const Expression& lvalue) {
static bool is_dead(const Expression& lvalue, ProgramUsage* usage) {
switch (lvalue.kind()) {
case Expression::Kind::kVariableReference:
return lvalue.as<VariableReference>().variable()->dead();
return usage->isDead(*lvalue.as<VariableReference>().variable());
case Expression::Kind::kSwizzle:
return is_dead(*lvalue.as<Swizzle>().base());
return is_dead(*lvalue.as<Swizzle>().base(), usage);
case Expression::Kind::kFieldAccess:
return is_dead(*lvalue.as<FieldAccess>().base());
return is_dead(*lvalue.as<FieldAccess>().base(), usage);
case Expression::Kind::kIndex: {
const IndexExpression& idx = lvalue.as<IndexExpression>();
return is_dead(*idx.base()) &&
return is_dead(*idx.base(), usage) &&
!idx.index()->hasProperty(Expression::Property::kSideEffects);
}
case Expression::Kind::kTernary: {
const TernaryExpression& t = lvalue.as<TernaryExpression>();
return !t.test()->hasSideEffects() && is_dead(*t.ifTrue()) && is_dead(*t.ifFalse());
return !t.test()->hasSideEffects() &&
is_dead(*t.ifTrue(), usage) &&
is_dead(*t.ifFalse(), usage);
}
case Expression::Kind::kExternalValue:
return false;
@ -615,11 +617,11 @@ static bool is_dead(const Expression& lvalue) {
* Returns true if this is an assignment which can be collapsed down to just the right hand side due
* to a dead target and lack of side effects on the left hand side.
*/
static bool dead_assignment(const BinaryExpression& b) {
static bool dead_assignment(const BinaryExpression& b, ProgramUsage* usage) {
if (!Compiler::IsAssignment(b.getOperator())) {
return false;
}
return is_dead(b.left());
return is_dead(b.left(), usage);
}
void Compiler::computeDataFlow(CFG* cfg) {
@ -720,6 +722,8 @@ static void delete_left(BasicBlock* b,
} else {
result = b->tryRemoveExpressionBefore(iter, &left);
}
// Remove references within LHS.
optimizationContext->fUsage->remove(&left);
*target = std::move(rightPointer);
if (!result) {
optimizationContext->fNeedsRescan = true;
@ -751,6 +755,8 @@ static void delete_right(BasicBlock* b,
std::unique_ptr<Expression>& leftPointer = bin.leftPointer();
Expression& right = bin.right();
SkASSERT(!right.hasSideEffects());
// Remove references within RHS.
optimizationContext->fUsage->remove(&right);
if (!b->tryRemoveExpressionBefore(iter, &right)) {
*target = std::move(leftPointer);
optimizationContext->fNeedsRescan = true;
@ -813,6 +819,8 @@ static void vectorize_left(BasicBlock* b,
std::vector<BasicBlock::Node>::iterator* iter,
Compiler::OptimizationContext* optimizationContext) {
BinaryExpression& bin = (*(*iter)->expression())->as<BinaryExpression>();
// Remove references within RHS. Vectorization of LHS doesn't change reference counts.
optimizationContext->fUsage->remove(bin.rightPointer().get());
vectorize(b, iter, bin.right().type(), &bin.leftPointer(), optimizationContext);
}
@ -824,6 +832,8 @@ static void vectorize_right(BasicBlock* b,
std::vector<BasicBlock::Node>::iterator* iter,
Compiler::OptimizationContext* optimizationContext) {
BinaryExpression& bin = (*(*iter)->expression())->as<BinaryExpression>();
// Remove references within LHS. Vectorization of RHS doesn't change reference counts.
optimizationContext->fUsage->remove(bin.leftPointer().get());
vectorize(b, iter, bin.left().type(), &bin.rightPointer(), optimizationContext);
}
@ -862,6 +872,8 @@ void Compiler::simplifyExpression(DefinitionMap& definitions,
optimizationContext->fUpdated = true;
optimized = fIRGenerator->coerce(std::move(optimized), expr->type());
SkASSERT(optimized);
// Remove references within 'expr', add references within 'optimized'
optimizationContext->fUsage->replace(expr, optimized.get());
if (!try_replace_expression(&b, iter, &optimized)) {
optimizationContext->fNeedsRescan = true;
return;
@ -890,9 +902,9 @@ void Compiler::simplifyExpression(DefinitionMap& definitions,
// ternary has a constant test, replace it with either the true or
// false branch
if (t->test()->as<BoolLiteral>().value()) {
(*iter)->setExpression(std::move(t->ifTrue()));
(*iter)->setExpression(std::move(t->ifTrue()), optimizationContext->fUsage);
} else {
(*iter)->setExpression(std::move(t->ifFalse()));
(*iter)->setExpression(std::move(t->ifFalse()), optimizationContext->fUsage);
}
optimizationContext->fUpdated = true;
optimizationContext->fNeedsRescan = true;
@ -901,7 +913,7 @@ void Compiler::simplifyExpression(DefinitionMap& definitions,
}
case Expression::Kind::kBinary: {
BinaryExpression* bin = &expr->as<BinaryExpression>();
if (dead_assignment(*bin)) {
if (dead_assignment(*bin, optimizationContext->fUsage)) {
delete_left(&b, iter, optimizationContext);
break;
}
@ -1082,6 +1094,7 @@ void Compiler::simplifyExpression(DefinitionMap& definitions,
}
if (identity) {
optimizationContext->fUpdated = true;
// No fUsage change: foo.rgba and foo have equivalent reference counts
if (!try_replace_expression(&b, iter, &s.base())) {
optimizationContext->fNeedsRescan = true;
return;
@ -1100,6 +1113,7 @@ void Compiler::simplifyExpression(DefinitionMap& definitions,
optimizationContext->fUpdated = true;
std::unique_ptr<Expression> replacement(new Swizzle(*fContext, base.base()->clone(),
std::move(final)));
// No fUsage change: foo.gbr.gbr and foo.brg have equivalent reference counts
if (!try_replace_expression(&b, iter, &replacement)) {
optimizationContext->fNeedsRescan = true;
return;
@ -1275,11 +1289,12 @@ void Compiler::simplifyStatement(DefinitionMap& definitions,
BasicBlock& b,
std::vector<BasicBlock::Node>::iterator* iter,
OptimizationContext* optimizationContext) {
ProgramUsage* usage = optimizationContext->fUsage;
Statement* stmt = (*iter)->statement()->get();
switch (stmt->kind()) {
case Statement::Kind::kVarDeclaration: {
const auto& varDecl = stmt->as<VarDeclaration>();
if (varDecl.var().dead() &&
if (usage->isDead(varDecl.var()) &&
(!varDecl.value() ||
!varDecl.value()->hasSideEffects())) {
if (varDecl.value()) {
@ -1288,7 +1303,7 @@ void Compiler::simplifyStatement(DefinitionMap& definitions,
optimizationContext->fNeedsRescan = true;
}
}
(*iter)->setStatement(std::unique_ptr<Statement>(new Nop()));
(*iter)->setStatement(std::make_unique<Nop>(), usage);
optimizationContext->fUpdated = true;
}
break;
@ -1299,12 +1314,12 @@ void Compiler::simplifyStatement(DefinitionMap& definitions,
// constant if, collapse down to a single branch
if (i.test()->as<BoolLiteral>().value()) {
SkASSERT(i.ifTrue());
(*iter)->setStatement(std::move(i.ifTrue()));
(*iter)->setStatement(std::move(i.ifTrue()), usage);
} else {
if (i.ifFalse()) {
(*iter)->setStatement(std::move(i.ifFalse()));
(*iter)->setStatement(std::move(i.ifFalse()), usage);
} else {
(*iter)->setStatement(std::unique_ptr<Statement>(new Nop()));
(*iter)->setStatement(std::make_unique<Nop>(), usage);
}
}
optimizationContext->fUpdated = true;
@ -1321,12 +1336,12 @@ void Compiler::simplifyStatement(DefinitionMap& definitions,
// if block doesn't do anything, no else block
if (i.test()->hasSideEffects()) {
// test has side effects, keep it
(*iter)->setStatement(std::unique_ptr<Statement>(
new ExpressionStatement(std::move(i.test()))));
(*iter)->setStatement(
std::make_unique<ExpressionStatement>(std::move(i.test())), usage);
} else {
// no if, no else, no test side effects, kill the whole if
// statement
(*iter)->setStatement(std::unique_ptr<Statement>(new Nop()));
(*iter)->setStatement(std::make_unique<Nop>(), usage);
}
optimizationContext->fUpdated = true;
optimizationContext->fNeedsRescan = true;
@ -1350,7 +1365,7 @@ void Compiler::simplifyStatement(DefinitionMap& definitions,
if (caseValue == switchValue) {
std::unique_ptr<Statement> newBlock = block_for_case(&s, &c);
if (newBlock) {
(*iter)->setStatement(std::move(newBlock));
(*iter)->setStatement(std::move(newBlock), usage);
found = true;
break;
} else {
@ -1370,7 +1385,7 @@ void Compiler::simplifyStatement(DefinitionMap& definitions,
if (defaultCase) {
std::unique_ptr<Statement> newBlock = block_for_case(&s, defaultCase);
if (newBlock) {
(*iter)->setStatement(std::move(newBlock));
(*iter)->setStatement(std::move(newBlock), usage);
} else {
if (s.isStatic() && !(fFlags & kPermitInvalidStaticTests_Flag) &&
optimizationContext->fSilences.find(&s) ==
@ -1382,7 +1397,7 @@ void Compiler::simplifyStatement(DefinitionMap& definitions,
return; // can't simplify
}
} else {
(*iter)->setStatement(std::unique_ptr<Statement>(new Nop()));
(*iter)->setStatement(std::make_unique<Nop>(), usage);
}
}
optimizationContext->fUpdated = true;
@ -1399,7 +1414,7 @@ void Compiler::simplifyStatement(DefinitionMap& definitions,
optimizationContext->fNeedsRescan = true;
}
SkASSERT((*iter)->statement()->get() == stmt);
(*iter)->setStatement(std::unique_ptr<Statement>(new Nop()));
(*iter)->setStatement(std::make_unique<Nop>(), usage);
optimizationContext->fUpdated = true;
}
break;
@ -1409,7 +1424,7 @@ void Compiler::simplifyStatement(DefinitionMap& definitions,
}
}
bool Compiler::scanCFG(FunctionDefinition& f) {
bool Compiler::scanCFG(FunctionDefinition& f, ProgramUsage* usage) {
bool madeChanges = false;
CFG cfg = CFGGenerator().getCFG(f);
@ -1442,6 +1457,7 @@ bool Compiler::scanCFG(FunctionDefinition& f) {
// check for dead code & undefined variables, perform constant propagation
OptimizationContext optimizationContext;
optimizationContext.fUsage = usage;
do {
if (optimizationContext.fNeedsRescan) {
cfg = CFGGenerator().getCFG(f);
@ -1459,7 +1475,7 @@ bool Compiler::scanCFG(FunctionDefinition& f) {
// have not been properly assigned. Kill it by replacing all statements with Nops.
for (BasicBlock::Node& node : b.fNodes) {
if (node.isStatement() && !(*node.statement())->is<Nop>()) {
node.setStatement(std::make_unique<Nop>());
node.setStatement(std::make_unique<Nop>(), usage);
madeChanges = true;
}
}
@ -1584,6 +1600,7 @@ bool Compiler::optimize(Program& program) {
SkASSERT(!fErrorCount);
fIRGenerator->fKind = program.fKind;
fIRGenerator->fSettings = &program.fSettings;
ProgramUsage* usage = program.fUsage.get();
while (fErrorCount == 0) {
bool madeChanges = false;
@ -1591,7 +1608,7 @@ bool Compiler::optimize(Program& program) {
// Scan and optimize based on the control-flow graph for each function.
for (const auto& element : program.elements()) {
if (element->is<FunctionDefinition>()) {
madeChanges |= this->scanCFG(element->as<FunctionDefinition>());
madeChanges |= this->scanCFG(element->as<FunctionDefinition>(), usage);
}
}
@ -1601,8 +1618,6 @@ bool Compiler::optimize(Program& program) {
// Remove dead functions. We wait until after analysis so that we still report errors,
// even in unused code.
if (program.fSettings.fRemoveDeadFunctions) {
Analysis::CallCountMap callCounts = Analysis::GetCallCounts(program);
program.fElements.erase(
std::remove_if(program.fElements.begin(),
program.fElements.end(),
@ -1612,8 +1627,11 @@ bool Compiler::optimize(Program& program) {
}
const auto& fn = element->as<FunctionDefinition>();
bool dead = fn.declaration().name() != "main" &&
callCounts[&fn.declaration()] == 0;
madeChanges |= dead;
usage->get(fn.declaration()) == 0;
if (dead) {
madeChanges = true;
usage->remove(*element);
}
return dead;
}),
program.fElements.end());
@ -1630,7 +1648,7 @@ bool Compiler::optimize(Program& program) {
const auto& global = element->as<GlobalVarDeclaration>();
const auto& varDecl =
global.declaration()->as<VarDeclaration>();
bool dead = varDecl.var().dead();
bool dead = usage->isDead(varDecl.var());
madeChanges |= dead;
return dead;
}),

View File

@ -12,6 +12,7 @@
#include <unordered_set>
#include <vector>
#include "src/sksl/SkSLASTFile.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLCFGGenerator.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLErrorReporter.h"
@ -49,6 +50,7 @@ class ExternalValue;
class IRGenerator;
class IRIntrinsicMap;
struct PipelineStageArgs;
class ProgramUsage;
struct LoadedModule {
std::shared_ptr<SymbolTable> fSymbols;
@ -114,6 +116,8 @@ public:
bool fUpdated = false;
// true if we need to completely regenerate the CFG
bool fNeedsRescan = false;
// Metadata about function and variable usage within the program
ProgramUsage* fUsage = nullptr;
};
#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU
@ -250,7 +254,7 @@ private:
/**
* Optimizes a function based on control flow analysis. Returns true if changes were made.
*/
bool scanCFG(FunctionDefinition& f);
bool scanCFG(FunctionDefinition& f, ProgramUsage* usage);
/**
* Optimize every function in the program.

View File

@ -1141,36 +1141,13 @@ void Inliner::buildCandidateList(Program& program, InlineCandidateList* candidat
}
}
static bool multiple_calls_to(const Program& program, const FunctionDeclaration* fn) {
class MultipleCallVisitor : public ProgramVisitor {
public:
MultipleCallVisitor(const FunctionDeclaration* function) : fFunction(function) {}
bool visitExpression(const Expression& e) override {
if (e.is<FunctionCall>() && &e.as<FunctionCall>().function() == fFunction) {
if (fCalled) {
return true;
}
fCalled = true;
}
return INHERITED::visitExpression(e);
}
const FunctionDeclaration* fFunction;
bool fCalled = false;
using INHERITED = ProgramVisitor;
};
MultipleCallVisitor visitor(fn);
return visitor.visit(program);
}
bool Inliner::analyze(Program& program) {
// A threshold of zero indicates that the inliner is completely disabled, so we can just return.
if (fSettings->fInlineThreshold <= 0) {
return false;
}
ProgramUsage* usage = program.fUsage.get();
InlineCandidateList candidateList;
this->buildCandidateList(program, &candidateList);
@ -1179,13 +1156,12 @@ bool Inliner::analyze(Program& program) {
bool madeChanges = false;
for (const InlineCandidate& candidate : candidateList.fCandidates) {
FunctionCall& funcCall = (*candidate.fCandidateExpr)->as<FunctionCall>();
const FunctionDeclaration* funcDecl = &funcCall.function();
const FunctionDeclaration& funcDecl = funcCall.function();
// If the function is large, not marked `inline`, and is called more than once, it's a bad
// idea to inline it.
if (candidate.fIsLargeFunction &&
!(funcDecl->modifiers().fFlags & Modifiers::kInline_Flag) &&
multiple_calls_to(program, funcDecl)) {
!(funcDecl.modifiers().fFlags & Modifiers::kInline_Flag) && usage->get(funcDecl) > 1) {
continue;
}
@ -1203,6 +1179,9 @@ bool Inliner::analyze(Program& program) {
// Ensure that the inlined body has a scope if it needs one.
this->ensureScopedBlocks(inlinedCall.fInlinedBody.get(), candidate.fParentStmt->get());
// Add references within the inlined body
usage->add(inlinedCall.fInlinedBody.get());
// Move the enclosing statement to the end of the unscoped Block containing the inlined
// function, then replace the enclosing statement with that Block.
// Before:
@ -1216,6 +1195,7 @@ bool Inliner::analyze(Program& program) {
}
// Replace the candidate function call with our replacement expression.
usage->replace(candidate.fCandidateExpr->get(), inlinedCall.fReplacementExpr.get());
*candidate.fCandidateExpr = std::move(inlinedCall.fReplacementExpr);
madeChanges = true;

View File

@ -2767,8 +2767,9 @@ void SPIRVCodeGenerator::writePrecisionModifier(Precision precision, SpvId id) {
}
}
bool is_dead(const Variable& var) {
if (var.readCount() || var.writeCount()) {
bool is_dead(const Variable& var, const ProgramUsage* usage) {
ProgramUsage::VariableCounts counts = usage->get(var);
if (counts.fRead || counts.fWrite) {
return false;
}
// not entirely sure what the rules are for when it's safe to elide interface variables, but it
@ -2802,7 +2803,7 @@ void SPIRVCodeGenerator::writeGlobalVar(Program::Kind kind, const VarDeclaration
SkASSERT(!fProgram.fSettings.fFragColorIsInOut);
return;
}
if (is_dead(var)) {
if (is_dead(var, fProgram.fUsage.get())) {
return;
}
const Type& type = var.type();
@ -3214,7 +3215,7 @@ void SPIRVCodeGenerator::writeInstructions(const Program& program, OutputStream&
if (((modifiers.fFlags & Modifiers::kIn_Flag) ||
(modifiers.fFlags & Modifiers::kOut_Flag)) &&
modifiers.fLayout.fBuiltin == -1 &&
!is_dead(intf.variable())) {
!is_dead(intf.variable(), fProgram.fUsage.get())) {
interfaceVars.insert(id);
}
}
@ -3245,7 +3246,8 @@ void SPIRVCodeGenerator::writeInstructions(const Program& program, OutputStream&
const Variable* var = entry.first;
if (var->storage() == Variable::Storage::kGlobal &&
((var->modifiers().fFlags & Modifiers::kIn_Flag) ||
(var->modifiers().fFlags & Modifiers::kOut_Flag)) && !is_dead(*var)) {
(var->modifiers().fFlags & Modifiers::kOut_Flag)) &&
!is_dead(*var, fProgram.fUsage.get())) {
interfaceVars.insert(entry.second);
}
}

View File

@ -252,12 +252,6 @@ protected:
const Type* fType;
const Expression* fInitialValue = nullptr;
ModifiersPool::Handle fModifiersHandle;
// Tracks how many sites read from the variable. If this is zero for a non-out variable (or
// becomes zero during optimization), the variable is dead and may be eliminated.
mutable int16_t fReadCount;
// Tracks how many sites write to the variable. If this is zero, the variable is dead and
// may be eliminated.
mutable int16_t fWriteCount;
VariableStorage fStorage;
bool fBuiltin;
};

View File

@ -11,6 +11,8 @@
#include <vector>
#include <memory>
#include "include/private/SkTHash.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/ir/SkSLBoolLiteral.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLFloatLiteral.h"
@ -34,6 +36,27 @@ namespace SkSL {
class Context;
class Pool;
/**
* Side-car class holding mutable information about a Program's IR
*/
class ProgramUsage {
public:
struct VariableCounts { int fRead = 0; int fWrite = 0; };
VariableCounts get(const Variable&) const;
bool isDead(const Variable&) const;
int get(const FunctionDeclaration&) const;
void replace(const Expression* oldExpr, const Expression* newExpr);
void add(const Statement* stmt);
void remove(const Expression* expr);
void remove(const Statement* stmt);
void remove(const ProgramElement& element);
SkTHashMap<const Variable*, VariableCounts> fVariableCounts;
SkTHashMap<const FunctionDeclaration*, int> fCallCounts;
};
/**
* Represents a fully-digested program, ready for code generation.
*/
@ -176,7 +199,9 @@ struct Program {
, fPool(std::move(pool))
, fInputs(inputs)
, fElements(std::move(elements))
, fModifiers(std::move(modifiers)) {}
, fModifiers(std::move(modifiers)) {
fUsage = Analysis::GetUsage(*this);
}
~Program() {
// Some or all of the program elements are in the pool. To free them safely, we must attach
@ -209,8 +234,10 @@ struct Program {
private:
std::vector<std::unique_ptr<ProgramElement>> fElements;
std::unique_ptr<ModifiersPool> fModifiers;
std::unique_ptr<ProgramUsage> fUsage;
friend class Compiler;
friend class Inliner; // fUsage
friend class SPIRVCodeGenerator; // fModifiers
};

View File

@ -38,17 +38,7 @@ public:
Variable(int offset, ModifiersPool::Handle modifiers, StringFragment name, const Type* type,
bool builtin, Storage storage, const Expression* initialValue = nullptr)
: INHERITED(offset, VariableData{name, type, initialValue, modifiers, /*readCount=*/0,
/*writeCount=*/(int16_t) (initialValue ? 1 : 0),
storage, builtin}) {}
~Variable() override {
// can't destroy a variable while there are remaining references to it
if (this->initialValue()) {
--this->variableData().fWriteCount;
}
SkASSERT(!this->variableData().fReadCount && !this->variableData().fWriteCount);
}
: INHERITED(offset, VariableData{name, type, initialValue, modifiers, storage, builtin}) {}
const Type& type() const override {
return *this->variableData().fType;
@ -76,17 +66,7 @@ public:
void setInitialValue(const Expression* initialValue) {
SkASSERT(!this->initialValue());
SkASSERT(this->variableData().fWriteCount == 0);
this->variableData().fInitialValue = initialValue;
++this->variableData().fWriteCount;
}
int readCount() const {
return this->variableData().fReadCount;
}
int writeCount() const {
return this->variableData().fWriteCount;
}
StringFragment name() const override {
@ -97,38 +77,7 @@ public:
return this->modifiers().description() + this->type().name() + " " + this->name();
}
bool dead() const {
const VariableData& data = this->variableData();
const Modifiers& modifiers = this->modifiers();
if ((data.fStorage != Storage::kLocal && this->variableData().fReadCount) ||
(modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag |
Modifiers::kUniform_Flag | Modifiers::kVarying_Flag))) {
return false;
}
return !data.fWriteCount ||
(!data.fReadCount && !(modifiers.fFlags & (Modifiers::kPLS_Flag |
Modifiers::kPLSOut_Flag)));
}
private:
void referenceCreated(VariableReference::RefKind refKind) const {
if (refKind != VariableReference::RefKind::kRead) {
++this->variableData().fWriteCount;
}
if (refKind != VariableReference::RefKind::kWrite) {
++this->variableData().fReadCount;
}
}
void referenceDestroyed(VariableReference::RefKind refKind) const {
if (refKind != VariableReference::RefKind::kRead) {
--this->variableData().fWriteCount;
}
if (refKind != VariableReference::RefKind::kWrite) {
--this->variableData().fReadCount;
}
}
using INHERITED = Symbol;
friend class VariableReference;

View File

@ -17,11 +17,6 @@ namespace SkSL {
VariableReference::VariableReference(int offset, const Variable* variable, RefKind refKind)
: INHERITED(offset, VariableReferenceData{variable, refKind}) {
SkASSERT(this->variable());
this->variable()->referenceCreated(refKind);
}
VariableReference::~VariableReference() {
this->variable()->referenceDestroyed(this->refKind());
}
const Type& VariableReference::type() const {
@ -47,15 +42,11 @@ String VariableReference::description() const {
}
void VariableReference::setRefKind(RefKind refKind) {
this->variable()->referenceDestroyed(this->refKind());
this->variableReferenceData().fRefKind = refKind;
this->variable()->referenceCreated(this->refKind());
}
void VariableReference::setVariable(const Variable* variable) {
this->variable()->referenceDestroyed(this->refKind());
this->variableReferenceData().fVariable = variable;
this->variable()->referenceCreated(this->refKind());
}
std::unique_ptr<Expression> VariableReference::constantPropagate(const IRGenerator& irGenerator,

View File

@ -39,8 +39,6 @@ public:
VariableReference(int offset, const Variable* variable, RefKind refKind = RefKind::kRead);
~VariableReference() override;
VariableReference(const VariableReference&) = delete;
VariableReference& operator=(const VariableReference&) = delete;