Migrate SkSL Inliner out of IRGenerator and into its own module.
The functional changes needed to make this migration possible have already been submitted in prior CLs, so although this is large, it is almost entirely a mechanical migration of code from one file to another. Change-Id: I54380b52e38ebcec40ffbca7cb254f9655cb1865 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/313909 Commit-Queue: John Stiles <johnstiles@google.com> Reviewed-by: Ethan Nicholas <ethannicholas@google.com> Reviewed-by: Brian Osman <brianosman@google.com> Auto-Submit: John Stiles <johnstiles@google.com>
This commit is contained in:
parent
16d5b0a81a
commit
44e96bee4b
@ -30,6 +30,8 @@ skia_sksl_sources = [
|
||||
"$_src/sksl/SkSLFileOutputStream.h",
|
||||
"$_src/sksl/SkSLIRGenerator.cpp",
|
||||
"$_src/sksl/SkSLIRGenerator.h",
|
||||
"$_src/sksl/SkSLInliner.cpp",
|
||||
"$_src/sksl/SkSLInliner.h",
|
||||
"$_src/sksl/SkSLLexer.cpp",
|
||||
"$_src/sksl/SkSLLexer.h",
|
||||
"$_src/sksl/SkSLMemoryLayout.h",
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "src/sksl/SkSLIRGenerator.h"
|
||||
|
||||
#include "limits.h"
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
@ -175,10 +176,11 @@ void IRGenerator::start(const Program::Settings* settings,
|
||||
this->pushSymbolTable();
|
||||
fInvocations = -1;
|
||||
fInputs.reset();
|
||||
fInliner.reset(fContext, *fSettings);
|
||||
fSkPerVertex = nullptr;
|
||||
fRTAdjust = nullptr;
|
||||
fRTAdjustInterfaceBlock = nullptr;
|
||||
fInlineVarCounter = 0;
|
||||
fTmpSwizzleCounter = 0;
|
||||
if (inherited) {
|
||||
for (const auto& e : *inherited) {
|
||||
if (e->fKind == ProgramElement::kInterfaceBlock_Kind) {
|
||||
@ -1920,507 +1922,6 @@ std::unique_ptr<Expression> IRGenerator::convertTernaryExpression(const ASTNode&
|
||||
std::move(ifFalse)));
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> IRGenerator::inlineExpression(
|
||||
int offset,
|
||||
std::unordered_map<const Variable*, const Variable*>* varMap,
|
||||
const Expression& expression) {
|
||||
auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> {
|
||||
if (e) {
|
||||
return this->inlineExpression(offset, varMap, *e);
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
auto argList = [&](const std::vector<std::unique_ptr<Expression>>& originalArgs)
|
||||
-> std::vector<std::unique_ptr<Expression>> {
|
||||
std::vector<std::unique_ptr<Expression>> args;
|
||||
args.reserve(originalArgs.size());
|
||||
for (const std::unique_ptr<Expression>& arg : originalArgs) {
|
||||
args.push_back(expr(arg));
|
||||
}
|
||||
return args;
|
||||
};
|
||||
|
||||
switch (expression.fKind) {
|
||||
case Expression::kBinary_Kind: {
|
||||
const BinaryExpression& b = expression.as<BinaryExpression>();
|
||||
return std::make_unique<BinaryExpression>(offset,
|
||||
expr(b.fLeft),
|
||||
b.fOperator,
|
||||
expr(b.fRight),
|
||||
b.fType);
|
||||
}
|
||||
case Expression::kBoolLiteral_Kind:
|
||||
case Expression::kIntLiteral_Kind:
|
||||
case Expression::kFloatLiteral_Kind:
|
||||
case Expression::kNullLiteral_Kind:
|
||||
return expression.clone();
|
||||
case Expression::kConstructor_Kind: {
|
||||
const Constructor& constructor = expression.as<Constructor>();
|
||||
return std::make_unique<Constructor>(offset, constructor.fType,
|
||||
argList(constructor.fArguments));
|
||||
}
|
||||
case Expression::kExternalFunctionCall_Kind: {
|
||||
const ExternalFunctionCall& externalCall = expression.as<ExternalFunctionCall>();
|
||||
return std::make_unique<ExternalFunctionCall>(offset, externalCall.fType,
|
||||
externalCall.fFunction,
|
||||
argList(externalCall.fArguments));
|
||||
}
|
||||
case Expression::kExternalValue_Kind:
|
||||
return expression.clone();
|
||||
case Expression::kFieldAccess_Kind: {
|
||||
const FieldAccess& f = expression.as<FieldAccess>();
|
||||
return std::make_unique<FieldAccess>(expr(f.fBase), f.fFieldIndex, f.fOwnerKind);
|
||||
}
|
||||
case Expression::kFunctionCall_Kind: {
|
||||
const FunctionCall& funcCall = expression.as<FunctionCall>();
|
||||
return std::make_unique<FunctionCall>(offset, funcCall.fType, funcCall.fFunction,
|
||||
argList(funcCall.fArguments));
|
||||
}
|
||||
case Expression::kIndex_Kind: {
|
||||
const IndexExpression& idx = expression.as<IndexExpression>();
|
||||
return std::make_unique<IndexExpression>(fContext, expr(idx.fBase), expr(idx.fIndex));
|
||||
}
|
||||
case Expression::kPrefix_Kind: {
|
||||
const PrefixExpression& p = expression.as<PrefixExpression>();
|
||||
return std::make_unique<PrefixExpression>(p.fOperator, expr(p.fOperand));
|
||||
}
|
||||
case Expression::kPostfix_Kind: {
|
||||
const PostfixExpression& p = expression.as<PostfixExpression>();
|
||||
return std::make_unique<PostfixExpression>(expr(p.fOperand), p.fOperator);
|
||||
}
|
||||
case Expression::kSetting_Kind:
|
||||
return expression.clone();
|
||||
case Expression::kSwizzle_Kind: {
|
||||
const Swizzle& s = expression.as<Swizzle>();
|
||||
return std::make_unique<Swizzle>(fContext, expr(s.fBase), s.fComponents);
|
||||
}
|
||||
case Expression::kTernary_Kind: {
|
||||
const TernaryExpression& t = expression.as<TernaryExpression>();
|
||||
return std::make_unique<TernaryExpression>(offset, expr(t.fTest),
|
||||
expr(t.fIfTrue), expr(t.fIfFalse));
|
||||
}
|
||||
case Expression::kVariableReference_Kind: {
|
||||
const VariableReference& v = expression.as<VariableReference>();
|
||||
auto found = varMap->find(&v.fVariable);
|
||||
if (found != varMap->end()) {
|
||||
return std::make_unique<VariableReference>(offset, *found->second, v.fRefKind);
|
||||
}
|
||||
return v.clone();
|
||||
}
|
||||
default:
|
||||
SkASSERT(false);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static const Type* copy_if_needed(const Type* src, SymbolTable& symbolTable) {
|
||||
if (src->kind() == Type::kArray_Kind) {
|
||||
return symbolTable.takeOwnershipOfSymbol(std::make_unique<Type>(*src));
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
std::unique_ptr<Statement> IRGenerator::inlineStatement(
|
||||
int offset,
|
||||
std::unordered_map<const Variable*, const Variable*>* varMap,
|
||||
SymbolTable* symbolTableForStatement,
|
||||
const Variable* returnVar,
|
||||
bool haveEarlyReturns,
|
||||
const Statement& statement) {
|
||||
auto stmt = [&](const std::unique_ptr<Statement>& s) -> std::unique_ptr<Statement> {
|
||||
if (s) {
|
||||
return this->inlineStatement(offset, varMap, symbolTableForStatement, returnVar,
|
||||
haveEarlyReturns, *s);
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
auto stmts = [&](const std::vector<std::unique_ptr<Statement>>& ss) {
|
||||
std::vector<std::unique_ptr<Statement>> result;
|
||||
for (const auto& s : ss) {
|
||||
result.push_back(stmt(s));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> {
|
||||
if (e) {
|
||||
return this->inlineExpression(offset, varMap, *e);
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
switch (statement.fKind) {
|
||||
case Statement::kBlock_Kind: {
|
||||
const Block& b = statement.as<Block>();
|
||||
return std::make_unique<Block>(offset, stmts(b.fStatements), b.fSymbols, b.fIsScope);
|
||||
}
|
||||
|
||||
case Statement::kBreak_Kind:
|
||||
case Statement::kContinue_Kind:
|
||||
case Statement::kDiscard_Kind:
|
||||
return statement.clone();
|
||||
|
||||
case Statement::kDo_Kind: {
|
||||
const DoStatement& d = statement.as<DoStatement>();
|
||||
return std::make_unique<DoStatement>(offset, stmt(d.fStatement), expr(d.fTest));
|
||||
}
|
||||
case Statement::kExpression_Kind: {
|
||||
const ExpressionStatement& e = statement.as<ExpressionStatement>();
|
||||
return std::make_unique<ExpressionStatement>(expr(e.fExpression));
|
||||
}
|
||||
case Statement::kFor_Kind: {
|
||||
const ForStatement& f = statement.as<ForStatement>();
|
||||
// need to ensure initializer is evaluated first so that we've already remapped its
|
||||
// declarations by the time we evaluate test & next
|
||||
std::unique_ptr<Statement> initializer = stmt(f.fInitializer);
|
||||
return std::make_unique<ForStatement>(offset, std::move(initializer), expr(f.fTest),
|
||||
expr(f.fNext), stmt(f.fStatement), f.fSymbols);
|
||||
}
|
||||
case Statement::kIf_Kind: {
|
||||
const IfStatement& i = statement.as<IfStatement>();
|
||||
return std::make_unique<IfStatement>(offset, i.fIsStatic, expr(i.fTest),
|
||||
stmt(i.fIfTrue), stmt(i.fIfFalse));
|
||||
}
|
||||
case Statement::kNop_Kind:
|
||||
return statement.clone();
|
||||
case Statement::kReturn_Kind: {
|
||||
const ReturnStatement& r = statement.as<ReturnStatement>();
|
||||
if (r.fExpression) {
|
||||
auto assignment = std::make_unique<ExpressionStatement>(
|
||||
std::make_unique<BinaryExpression>(
|
||||
offset,
|
||||
std::make_unique<VariableReference>(offset, *returnVar,
|
||||
VariableReference::kWrite_RefKind),
|
||||
Token::Kind::TK_EQ,
|
||||
expr(r.fExpression),
|
||||
returnVar->fType));
|
||||
if (haveEarlyReturns) {
|
||||
std::vector<std::unique_ptr<Statement>> block;
|
||||
block.push_back(std::move(assignment));
|
||||
block.emplace_back(new BreakStatement(offset));
|
||||
return std::make_unique<Block>(offset, std::move(block), /*symbols=*/nullptr,
|
||||
/*isScope=*/true);
|
||||
} else {
|
||||
return std::move(assignment);
|
||||
}
|
||||
} else {
|
||||
if (haveEarlyReturns) {
|
||||
return std::make_unique<BreakStatement>(offset);
|
||||
} else {
|
||||
return std::make_unique<Nop>();
|
||||
}
|
||||
}
|
||||
}
|
||||
case Statement::kSwitch_Kind: {
|
||||
const SwitchStatement& ss = statement.as<SwitchStatement>();
|
||||
std::vector<std::unique_ptr<SwitchCase>> cases;
|
||||
for (const auto& sc : ss.fCases) {
|
||||
cases.emplace_back(new SwitchCase(offset, expr(sc->fValue),
|
||||
stmts(sc->fStatements)));
|
||||
}
|
||||
return std::make_unique<SwitchStatement>(offset, ss.fIsStatic, expr(ss.fValue),
|
||||
std::move(cases), ss.fSymbols);
|
||||
}
|
||||
case Statement::kVarDeclaration_Kind: {
|
||||
const VarDeclaration& decl = statement.as<VarDeclaration>();
|
||||
std::vector<std::unique_ptr<Expression>> sizes;
|
||||
for (const auto& size : decl.fSizes) {
|
||||
sizes.push_back(expr(size));
|
||||
}
|
||||
std::unique_ptr<Expression> initialValue = expr(decl.fValue);
|
||||
const Variable* old = decl.fVar;
|
||||
// need to copy the var name in case the originating function is discarded and we lose
|
||||
// its symbols
|
||||
std::unique_ptr<String> name(new String(old->fName));
|
||||
const String* namePtr = symbolTableForStatement->takeOwnershipOfString(std::move(name));
|
||||
const Type* typePtr = copy_if_needed(&old->fType, *symbolTableForStatement);
|
||||
const Variable* clone = symbolTableForStatement->takeOwnershipOfSymbol(
|
||||
std::make_unique<Variable>(offset,
|
||||
old->fModifiers,
|
||||
namePtr->c_str(),
|
||||
*typePtr,
|
||||
old->fStorage,
|
||||
initialValue.get()));
|
||||
(*varMap)[old] = clone;
|
||||
return std::make_unique<VarDeclaration>(clone, std::move(sizes),
|
||||
std::move(initialValue));
|
||||
}
|
||||
case Statement::kVarDeclarations_Kind: {
|
||||
const VarDeclarations& decls = *statement.as<VarDeclarationsStatement>().fDeclaration;
|
||||
std::vector<std::unique_ptr<VarDeclaration>> vars;
|
||||
for (const auto& var : decls.fVars) {
|
||||
vars.emplace_back(&stmt(var).release()->as<VarDeclaration>());
|
||||
}
|
||||
const Type* typePtr = copy_if_needed(&decls.fBaseType, *symbolTableForStatement);
|
||||
return std::unique_ptr<Statement>(new VarDeclarationsStatement(
|
||||
std::make_unique<VarDeclarations>(offset, typePtr, std::move(vars))));
|
||||
}
|
||||
case Statement::kWhile_Kind: {
|
||||
const WhileStatement& w = statement.as<WhileStatement>();
|
||||
return std::make_unique<WhileStatement>(offset, expr(w.fTest), stmt(w.fStatement));
|
||||
}
|
||||
default:
|
||||
SkASSERT(false);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static int count_all_returns(const FunctionDefinition& funcDef) {
|
||||
class CountAllReturns : public ProgramVisitor {
|
||||
public:
|
||||
CountAllReturns(const FunctionDefinition& funcDef) {
|
||||
this->visitProgramElement(funcDef);
|
||||
}
|
||||
|
||||
bool visitStatement(const Statement& stmt) override {
|
||||
switch (stmt.fKind) {
|
||||
case Statement::kReturn_Kind:
|
||||
++fNumReturns;
|
||||
[[fallthrough]];
|
||||
|
||||
default:
|
||||
return this->INHERITED::visitStatement(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
int fNumReturns = 0;
|
||||
using INHERITED = ProgramVisitor;
|
||||
};
|
||||
|
||||
return CountAllReturns{funcDef}.fNumReturns;
|
||||
}
|
||||
|
||||
static int count_returns_at_end_of_control_flow(const FunctionDefinition& funcDef) {
|
||||
class CountReturnsAtEndOfControlFlow : public ProgramVisitor {
|
||||
public:
|
||||
CountReturnsAtEndOfControlFlow(const FunctionDefinition& funcDef) {
|
||||
this->visitProgramElement(funcDef);
|
||||
}
|
||||
|
||||
bool visitStatement(const Statement& stmt) override {
|
||||
switch (stmt.fKind) {
|
||||
case Statement::kBlock_Kind: {
|
||||
// Check only the last statement of a block.
|
||||
const auto& blockStmts = stmt.as<Block>().fStatements;
|
||||
return (blockStmts.size() > 0) ? this->visitStatement(*blockStmts.back())
|
||||
: false;
|
||||
}
|
||||
case Statement::kSwitch_Kind:
|
||||
case Statement::kWhile_Kind:
|
||||
case Statement::kDo_Kind:
|
||||
case Statement::kFor_Kind:
|
||||
// Don't introspect switches or loop structures at all.
|
||||
return false;
|
||||
|
||||
case Statement::kReturn_Kind:
|
||||
++fNumReturns;
|
||||
[[fallthrough]];
|
||||
|
||||
default:
|
||||
return this->INHERITED::visitStatement(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
int fNumReturns = 0;
|
||||
using INHERITED = ProgramVisitor;
|
||||
};
|
||||
|
||||
return CountReturnsAtEndOfControlFlow{funcDef}.fNumReturns;
|
||||
}
|
||||
|
||||
static int count_returns_in_breakable_constructs(const FunctionDefinition& funcDef) {
|
||||
class CountReturnsInBreakableConstructs : public ProgramVisitor {
|
||||
public:
|
||||
CountReturnsInBreakableConstructs(const FunctionDefinition& funcDef) {
|
||||
this->visitProgramElement(funcDef);
|
||||
}
|
||||
|
||||
bool visitStatement(const Statement& stmt) override {
|
||||
switch (stmt.fKind) {
|
||||
case Statement::kSwitch_Kind:
|
||||
case Statement::kWhile_Kind:
|
||||
case Statement::kDo_Kind:
|
||||
case Statement::kFor_Kind: {
|
||||
++fInsideBreakableConstruct;
|
||||
bool result = this->INHERITED::visitStatement(stmt);
|
||||
--fInsideBreakableConstruct;
|
||||
return result;
|
||||
}
|
||||
|
||||
case Statement::kReturn_Kind:
|
||||
fNumReturns += (fInsideBreakableConstruct > 0) ? 1 : 0;
|
||||
[[fallthrough]];
|
||||
|
||||
default:
|
||||
return this->INHERITED::visitStatement(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
int fNumReturns = 0;
|
||||
int fInsideBreakableConstruct = 0;
|
||||
using INHERITED = ProgramVisitor;
|
||||
};
|
||||
|
||||
return CountReturnsInBreakableConstructs{funcDef}.fNumReturns;
|
||||
}
|
||||
|
||||
static bool has_early_return(const FunctionDefinition& funcDef) {
|
||||
int returnCount = count_all_returns(funcDef);
|
||||
if (returnCount == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int returnsAtEndOfControlFlow = count_returns_at_end_of_control_flow(funcDef);
|
||||
return returnCount > returnsAtEndOfControlFlow;
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> IRGenerator::inlineCall(std::unique_ptr<FunctionCall> call,
|
||||
SymbolTable* symbolTableForCall) {
|
||||
// Inlining is more complicated here than in a typical compiler, because we have to have a
|
||||
// high-level IR and can't just drop statements into the middle of an expression or even use
|
||||
// gotos.
|
||||
//
|
||||
// Since we can't insert statements into an expression, we run the inline function as extra
|
||||
// statements before the statement we're currently processing, relying on a lack of execution
|
||||
// order guarantees. Since we can't use gotos (which are normally used to replace return
|
||||
// statements), we wrap the whole function in a loop and use break statements to jump to the
|
||||
// end.
|
||||
SkASSERT(call);
|
||||
SkASSERT(this->isSafeToInline(*call, /*inlineThreshold=*/INT_MAX));
|
||||
|
||||
int offset = call->fOffset;
|
||||
std::vector<std::unique_ptr<Expression>>& arguments = call->fArguments;
|
||||
const FunctionDefinition& function = *call->fFunction.fDefinition;
|
||||
|
||||
// Use unique variable names based on the function signature. Otherwise there are situations in
|
||||
// which an inlined function is later inlined into another function, and we end up with
|
||||
// duplicate names like 'inlineResult0' because the counter was reset. (skbug.com/10526)
|
||||
String raw = function.fDeclaration.description();
|
||||
String inlineSalt;
|
||||
for (size_t i = 0; i < raw.length(); ++i) {
|
||||
char c = raw[i];
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||
c == '_') {
|
||||
inlineSalt += c;
|
||||
}
|
||||
}
|
||||
|
||||
auto makeInlineVar = [&](const String& name, const Type& type, Modifiers modifiers,
|
||||
std::unique_ptr<Expression>* initialValue) -> const Variable* {
|
||||
// Add our new variable's name to the symbol table.
|
||||
const String* namePtr =
|
||||
symbolTableForCall->takeOwnershipOfString(std::make_unique<String>(name));
|
||||
StringFragment nameFrag{namePtr->c_str(), namePtr->length()};
|
||||
|
||||
// Add our new variable to the symbol table.
|
||||
auto newVar = std::make_unique<Variable>(/*offset=*/-1, Modifiers(), nameFrag, type,
|
||||
Variable::kLocal_Storage, initialValue->get());
|
||||
const Variable* variableSymbol = symbolTableForCall->add(nameFrag, std::move(newVar));
|
||||
|
||||
// Prepare the variable declaration (taking extra care with `out` params to not clobber any
|
||||
// initial value).
|
||||
std::vector<std::unique_ptr<VarDeclaration>> variables;
|
||||
if (initialValue && (modifiers.fFlags & Modifiers::kOut_Flag)) {
|
||||
variables.push_back(std::make_unique<VarDeclaration>(
|
||||
variableSymbol, /*sizes=*/std::vector<std::unique_ptr<Expression>>{},
|
||||
(*initialValue)->clone()));
|
||||
} else {
|
||||
variables.push_back(std::make_unique<VarDeclaration>(
|
||||
variableSymbol, /*sizes=*/std::vector<std::unique_ptr<Expression>>{},
|
||||
std::move(*initialValue)));
|
||||
}
|
||||
|
||||
// Add the new variable-declaration statement to our block of extra statements.
|
||||
fExtraStatements.push_back(std::make_unique<VarDeclarationsStatement>(
|
||||
std::make_unique<VarDeclarations>(offset, &type, std::move(variables))));
|
||||
|
||||
return variableSymbol;
|
||||
};
|
||||
|
||||
// Create a variable to hold the result in the extra statements (excepting void).
|
||||
const Variable* resultVar = nullptr;
|
||||
if (function.fDeclaration.fReturnType != *fContext.fVoid_Type) {
|
||||
int varIndex = fInlineVarCounter++;
|
||||
|
||||
std::unique_ptr<Expression> noInitialValue;
|
||||
resultVar = makeInlineVar(String::printf("_inlineResult%s%d", inlineSalt.c_str(), varIndex),
|
||||
function.fDeclaration.fReturnType, Modifiers{}, &noInitialValue);
|
||||
}
|
||||
|
||||
// Create variables in the extra statements to hold the arguments, and assign the arguments to
|
||||
// them.
|
||||
std::unordered_map<const Variable*, const Variable*> varMap;
|
||||
int argIndex = fInlineVarCounter++;
|
||||
for (int i = 0; i < (int) arguments.size(); ++i) {
|
||||
const Variable* param = function.fDeclaration.fParameters[i];
|
||||
|
||||
if (arguments[i]->fKind == Expression::kVariableReference_Kind) {
|
||||
// The argument is just a variable, so we only need to copy it if it's an out parameter
|
||||
// or it's written to within the function.
|
||||
if ((param->fModifiers.fFlags & Modifiers::kOut_Flag) ||
|
||||
!Analysis::StatementWritesToVariable(*function.fBody, *param)) {
|
||||
varMap[param] = &arguments[i]->as<VariableReference>().fVariable;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
varMap[param] = makeInlineVar(
|
||||
String::printf("_inlineArg%s%d_%d", inlineSalt.c_str(), argIndex, i),
|
||||
arguments[i]->fType, param->fModifiers, &arguments[i]);
|
||||
}
|
||||
|
||||
const Block& body = function.fBody->as<Block>();
|
||||
bool hasEarlyReturn = has_early_return(function);
|
||||
auto inlineBlock = std::make_unique<Block>(offset, std::vector<std::unique_ptr<Statement>>{});
|
||||
inlineBlock->fStatements.reserve(body.fStatements.size());
|
||||
for (const std::unique_ptr<Statement>& stmt : body.fStatements) {
|
||||
inlineBlock->fStatements.push_back(this->inlineStatement(
|
||||
offset, &varMap, symbolTableForCall, resultVar, hasEarlyReturn, *stmt));
|
||||
}
|
||||
if (hasEarlyReturn) {
|
||||
// Since we output to backends that don't have a goto statement (which would normally be
|
||||
// used to perform an early return), we fake it by wrapping the function in a
|
||||
// do { } while (false); and then use break statements to jump to the end in order to
|
||||
// emulate a goto.
|
||||
fExtraStatements.push_back(std::make_unique<DoStatement>(
|
||||
/*offset=*/-1,
|
||||
std::move(inlineBlock),
|
||||
std::make_unique<BoolLiteral>(fContext, offset, /*value=*/false)));
|
||||
} else {
|
||||
// No early returns, so we can just dump the code in. We need to use a block so we don't get
|
||||
// name conflicts with locals.
|
||||
fExtraStatements.push_back(std::move(inlineBlock));
|
||||
}
|
||||
|
||||
// Copy the values of `out` parameters into their destinations.
|
||||
for (size_t i = 0; i < arguments.size(); ++i) {
|
||||
const Variable* p = function.fDeclaration.fParameters[i];
|
||||
if (p->fModifiers.fFlags & Modifiers::kOut_Flag) {
|
||||
SkASSERT(varMap.find(p) != varMap.end());
|
||||
if (arguments[i]->fKind == Expression::kVariableReference_Kind &&
|
||||
&arguments[i]->as<VariableReference>().fVariable == varMap[p]) {
|
||||
// we didn't create a temporary for this parameter, so there's nothing to copy back
|
||||
// out
|
||||
continue;
|
||||
}
|
||||
auto varRef = std::make_unique<VariableReference>(offset, *varMap[p]);
|
||||
fExtraStatements.push_back(std::make_unique<ExpressionStatement>(
|
||||
std::make_unique<BinaryExpression>(offset,
|
||||
arguments[i]->clone(),
|
||||
Token::Kind::TK_EQ,
|
||||
std::move(varRef),
|
||||
arguments[i]->fType)));
|
||||
}
|
||||
}
|
||||
|
||||
if (function.fDeclaration.fReturnType != *fContext.fVoid_Type) {
|
||||
// Return a reference to the result variable as our replacement expression.
|
||||
return std::make_unique<VariableReference>(offset, *resultVar);
|
||||
} else {
|
||||
// It's a void function, so it doesn't actually result in anything, but we have to return
|
||||
// something non-null as a standin.
|
||||
return std::make_unique<BoolLiteral>(fContext, /*offset=*/-1, /*value=*/false);
|
||||
}
|
||||
}
|
||||
|
||||
void IRGenerator::copyIntrinsicIfNeeded(const FunctionDeclaration& function) {
|
||||
auto found = fIntrinsics->find(function.description());
|
||||
if (found != fIntrinsics->end() && !found->second.fAlreadyIncluded) {
|
||||
@ -2433,42 +1934,6 @@ void IRGenerator::copyIntrinsicIfNeeded(const FunctionDeclaration& function) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IRGenerator::isSafeToInline(const FunctionCall& functionCall, int inlineThreshold) {
|
||||
if (!fCanInline) {
|
||||
// Inlining has been explicitly disabled by the IR generator.
|
||||
return false;
|
||||
}
|
||||
if (functionCall.fFunction.fDefinition == nullptr) {
|
||||
// Can't inline something if we don't actually have its definition.
|
||||
return false;
|
||||
}
|
||||
const FunctionDefinition& functionDef = *functionCall.fFunction.fDefinition;
|
||||
if (inlineThreshold < INT_MAX) {
|
||||
if (!(functionDef.fDeclaration.fModifiers.fFlags & Modifiers::kInline_Flag) &&
|
||||
Analysis::NodeCount(functionDef) >= inlineThreshold) {
|
||||
// The function exceeds our maximum inline size and is not flagged 'inline'.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!fSettings->fCaps || !fSettings->fCaps->canUseDoLoops()) {
|
||||
// We don't have do-while loops. We use do-while loops to simulate early returns, so we
|
||||
// can't inline functions that have an early return.
|
||||
bool hasEarlyReturn = has_early_return(functionDef);
|
||||
|
||||
// If we didn't detect an early return, there shouldn't be any returns in breakable
|
||||
// constructs either.
|
||||
SkASSERT(hasEarlyReturn || count_returns_in_breakable_constructs(functionDef) == 0);
|
||||
return !hasEarlyReturn;
|
||||
}
|
||||
// We have do-while loops, but we don't have any mechanism to simulate early returns within a
|
||||
// breakable construct (switch/for/do/while), so we can't inline if there's a return inside one.
|
||||
bool hasReturnInBreakableConstruct = (count_returns_in_breakable_constructs(functionDef) > 0);
|
||||
|
||||
// If we detected returns in breakable constructs, we should also detect an early return.
|
||||
SkASSERT(!hasReturnInBreakableConstruct || has_early_return(functionDef));
|
||||
return !hasReturnInBreakableConstruct;
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> IRGenerator::call(int offset,
|
||||
const FunctionDeclaration& function,
|
||||
std::vector<std::unique_ptr<Expression>> arguments) {
|
||||
@ -2525,8 +1990,13 @@ std::unique_ptr<Expression> IRGenerator::call(int offset,
|
||||
|
||||
auto funcCall = std::make_unique<FunctionCall>(offset, *returnType, function,
|
||||
std::move(arguments));
|
||||
if (this->isSafeToInline(*funcCall, fSettings->fInlineThreshold)) {
|
||||
return this->inlineCall(std::move(funcCall), fSymbolTable.get());
|
||||
if (fCanInline && fInliner.isSafeToInline(*funcCall, fSettings->fInlineThreshold)) {
|
||||
Inliner::InlinedCall inlinedCall = fInliner.inlineCall(std::move(funcCall),
|
||||
fSymbolTable.get());
|
||||
fExtraStatements.insert(fExtraStatements.end(),
|
||||
std::make_move_iterator(inlinedCall.fInlinedBody.begin()),
|
||||
std::make_move_iterator(inlinedCall.fInlinedBody.end()));
|
||||
return std::move(inlinedCall.fReplacementExpr);
|
||||
}
|
||||
|
||||
return std::move(funcCall);
|
||||
@ -3002,7 +2472,7 @@ std::unique_ptr<Expression> IRGenerator::convertSwizzle(std::unique_ptr<Expressi
|
||||
expr = std::move(base);
|
||||
} else {
|
||||
// store the value in a temporary variable so we can re-use it
|
||||
int varIndex = fInlineVarCounter++;
|
||||
int varIndex = fTmpSwizzleCounter++;
|
||||
auto name = std::make_unique<String>();
|
||||
name->appendf("_tmpSwizzle%d", varIndex);
|
||||
const String* namePtr = fSymbolTable->takeOwnershipOfString(std::move(name));
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "src/sksl/SkSLASTFile.h"
|
||||
#include "src/sksl/SkSLASTNode.h"
|
||||
#include "src/sksl/SkSLErrorReporter.h"
|
||||
#include "src/sksl/SkSLInliner.h"
|
||||
#include "src/sksl/ir/SkSLBlock.h"
|
||||
#include "src/sksl/ir/SkSLExpression.h"
|
||||
#include "src/sksl/ir/SkSLExtension.h"
|
||||
@ -99,23 +100,9 @@ private:
|
||||
std::unique_ptr<ModifiersDeclaration> convertModifiersDeclaration(const ASTNode& m);
|
||||
|
||||
const Type* convertType(const ASTNode& type);
|
||||
std::unique_ptr<Expression> inlineExpression(
|
||||
int offset,
|
||||
std::unordered_map<const Variable*, const Variable*>* varMap,
|
||||
const Expression& expression);
|
||||
std::unique_ptr<Statement> inlineStatement(
|
||||
int offset,
|
||||
std::unordered_map<const Variable*, const Variable*>* varMap,
|
||||
SymbolTable* symbolTableForStatement,
|
||||
const Variable* returnVar,
|
||||
bool haveEarlyReturns,
|
||||
const Statement& statement);
|
||||
std::unique_ptr<Expression> inlineCall(std::unique_ptr<FunctionCall> call,
|
||||
SymbolTable* symbolTableForCall);
|
||||
std::unique_ptr<Expression> call(int offset,
|
||||
const FunctionDeclaration& function,
|
||||
std::vector<std::unique_ptr<Expression>> arguments);
|
||||
bool isSafeToInline(const FunctionCall& function, int inlineThreshold);
|
||||
int callCost(const FunctionDeclaration& function,
|
||||
const std::vector<std::unique_ptr<Expression>>& arguments);
|
||||
std::unique_ptr<Expression> call(int offset, std::unique_ptr<Expression> function,
|
||||
@ -181,6 +168,7 @@ private:
|
||||
bool checkSwizzleWrite(const Swizzle& swizzle);
|
||||
void copyIntrinsicIfNeeded(const FunctionDeclaration& function);
|
||||
|
||||
Inliner fInliner;
|
||||
std::unique_ptr<ASTFile> fFile;
|
||||
const FunctionDeclaration* fCurrentFunction;
|
||||
std::unordered_map<String, Program::Settings::Value> fCapsMap;
|
||||
@ -202,7 +190,7 @@ private:
|
||||
const Variable* fRTAdjust;
|
||||
const Variable* fRTAdjustInterfaceBlock;
|
||||
int fRTAdjustFieldIndex;
|
||||
int fInlineVarCounter;
|
||||
int fTmpSwizzleCounter;
|
||||
bool fCanInline = true;
|
||||
// true if we are currently processing one of the built-in SkSL include files
|
||||
bool fIsBuiltinCode;
|
||||
|
607
src/sksl/SkSLInliner.cpp
Normal file
607
src/sksl/SkSLInliner.cpp
Normal file
@ -0,0 +1,607 @@
|
||||
/*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/sksl/SkSLInliner.h"
|
||||
|
||||
#include "limits.h"
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "src/sksl/SkSLAnalysis.h"
|
||||
#include "src/sksl/ir/SkSLBinaryExpression.h"
|
||||
#include "src/sksl/ir/SkSLBoolLiteral.h"
|
||||
#include "src/sksl/ir/SkSLBreakStatement.h"
|
||||
#include "src/sksl/ir/SkSLConstructor.h"
|
||||
#include "src/sksl/ir/SkSLContinueStatement.h"
|
||||
#include "src/sksl/ir/SkSLDiscardStatement.h"
|
||||
#include "src/sksl/ir/SkSLDoStatement.h"
|
||||
#include "src/sksl/ir/SkSLEnum.h"
|
||||
#include "src/sksl/ir/SkSLExpressionStatement.h"
|
||||
#include "src/sksl/ir/SkSLExternalFunctionCall.h"
|
||||
#include "src/sksl/ir/SkSLExternalValueReference.h"
|
||||
#include "src/sksl/ir/SkSLField.h"
|
||||
#include "src/sksl/ir/SkSLFieldAccess.h"
|
||||
#include "src/sksl/ir/SkSLFloatLiteral.h"
|
||||
#include "src/sksl/ir/SkSLForStatement.h"
|
||||
#include "src/sksl/ir/SkSLFunctionCall.h"
|
||||
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
|
||||
#include "src/sksl/ir/SkSLFunctionDefinition.h"
|
||||
#include "src/sksl/ir/SkSLFunctionReference.h"
|
||||
#include "src/sksl/ir/SkSLIfStatement.h"
|
||||
#include "src/sksl/ir/SkSLIndexExpression.h"
|
||||
#include "src/sksl/ir/SkSLIntLiteral.h"
|
||||
#include "src/sksl/ir/SkSLInterfaceBlock.h"
|
||||
#include "src/sksl/ir/SkSLLayout.h"
|
||||
#include "src/sksl/ir/SkSLNop.h"
|
||||
#include "src/sksl/ir/SkSLNullLiteral.h"
|
||||
#include "src/sksl/ir/SkSLPostfixExpression.h"
|
||||
#include "src/sksl/ir/SkSLPrefixExpression.h"
|
||||
#include "src/sksl/ir/SkSLReturnStatement.h"
|
||||
#include "src/sksl/ir/SkSLSetting.h"
|
||||
#include "src/sksl/ir/SkSLSwitchCase.h"
|
||||
#include "src/sksl/ir/SkSLSwitchStatement.h"
|
||||
#include "src/sksl/ir/SkSLSwizzle.h"
|
||||
#include "src/sksl/ir/SkSLTernaryExpression.h"
|
||||
#include "src/sksl/ir/SkSLUnresolvedFunction.h"
|
||||
#include "src/sksl/ir/SkSLVarDeclarations.h"
|
||||
#include "src/sksl/ir/SkSLVarDeclarationsStatement.h"
|
||||
#include "src/sksl/ir/SkSLVariable.h"
|
||||
#include "src/sksl/ir/SkSLVariableReference.h"
|
||||
#include "src/sksl/ir/SkSLWhileStatement.h"
|
||||
|
||||
namespace SkSL {
|
||||
namespace {
|
||||
|
||||
static int count_all_returns(const FunctionDefinition& funcDef) {
|
||||
class CountAllReturns : public ProgramVisitor {
|
||||
public:
|
||||
CountAllReturns(const FunctionDefinition& funcDef) {
|
||||
this->visitProgramElement(funcDef);
|
||||
}
|
||||
|
||||
bool visitStatement(const Statement& stmt) override {
|
||||
switch (stmt.fKind) {
|
||||
case Statement::kReturn_Kind:
|
||||
++fNumReturns;
|
||||
[[fallthrough]];
|
||||
|
||||
default:
|
||||
return this->INHERITED::visitStatement(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
int fNumReturns = 0;
|
||||
using INHERITED = ProgramVisitor;
|
||||
};
|
||||
|
||||
return CountAllReturns{funcDef}.fNumReturns;
|
||||
}
|
||||
|
||||
static int count_returns_at_end_of_control_flow(const FunctionDefinition& funcDef) {
|
||||
class CountReturnsAtEndOfControlFlow : public ProgramVisitor {
|
||||
public:
|
||||
CountReturnsAtEndOfControlFlow(const FunctionDefinition& funcDef) {
|
||||
this->visitProgramElement(funcDef);
|
||||
}
|
||||
|
||||
bool visitStatement(const Statement& stmt) override {
|
||||
switch (stmt.fKind) {
|
||||
case Statement::kBlock_Kind: {
|
||||
// Check only the last statement of a block.
|
||||
const auto& blockStmts = stmt.as<Block>().fStatements;
|
||||
return (blockStmts.size() > 0) ? this->visitStatement(*blockStmts.back())
|
||||
: false;
|
||||
}
|
||||
case Statement::kSwitch_Kind:
|
||||
case Statement::kWhile_Kind:
|
||||
case Statement::kDo_Kind:
|
||||
case Statement::kFor_Kind:
|
||||
// Don't introspect switches or loop structures at all.
|
||||
return false;
|
||||
|
||||
case Statement::kReturn_Kind:
|
||||
++fNumReturns;
|
||||
[[fallthrough]];
|
||||
|
||||
default:
|
||||
return this->INHERITED::visitStatement(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
int fNumReturns = 0;
|
||||
using INHERITED = ProgramVisitor;
|
||||
};
|
||||
|
||||
return CountReturnsAtEndOfControlFlow{funcDef}.fNumReturns;
|
||||
}
|
||||
|
||||
static int count_returns_in_breakable_constructs(const FunctionDefinition& funcDef) {
|
||||
class CountReturnsInBreakableConstructs : public ProgramVisitor {
|
||||
public:
|
||||
CountReturnsInBreakableConstructs(const FunctionDefinition& funcDef) {
|
||||
this->visitProgramElement(funcDef);
|
||||
}
|
||||
|
||||
bool visitStatement(const Statement& stmt) override {
|
||||
switch (stmt.fKind) {
|
||||
case Statement::kSwitch_Kind:
|
||||
case Statement::kWhile_Kind:
|
||||
case Statement::kDo_Kind:
|
||||
case Statement::kFor_Kind: {
|
||||
++fInsideBreakableConstruct;
|
||||
bool result = this->INHERITED::visitStatement(stmt);
|
||||
--fInsideBreakableConstruct;
|
||||
return result;
|
||||
}
|
||||
|
||||
case Statement::kReturn_Kind:
|
||||
fNumReturns += (fInsideBreakableConstruct > 0) ? 1 : 0;
|
||||
[[fallthrough]];
|
||||
|
||||
default:
|
||||
return this->INHERITED::visitStatement(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
int fNumReturns = 0;
|
||||
int fInsideBreakableConstruct = 0;
|
||||
using INHERITED = ProgramVisitor;
|
||||
};
|
||||
|
||||
return CountReturnsInBreakableConstructs{funcDef}.fNumReturns;
|
||||
}
|
||||
|
||||
static bool has_early_return(const FunctionDefinition& funcDef) {
|
||||
int returnCount = count_all_returns(funcDef);
|
||||
if (returnCount == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int returnsAtEndOfControlFlow = count_returns_at_end_of_control_flow(funcDef);
|
||||
return returnCount > returnsAtEndOfControlFlow;
|
||||
}
|
||||
|
||||
static const Type* copy_if_needed(const Type* src, SymbolTable& symbolTable) {
|
||||
if (src->kind() == Type::kArray_Kind) {
|
||||
return symbolTable.takeOwnershipOfSymbol(std::make_unique<Type>(*src));
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Inliner::reset(const Context& context, const Program::Settings& settings) {
|
||||
fContext = &context;
|
||||
fSettings = &settings;
|
||||
fInlineVarCounter = 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> Inliner::inlineExpression(int offset,
|
||||
VariableRewriteMap* varMap,
|
||||
const Expression& expression) {
|
||||
auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> {
|
||||
if (e) {
|
||||
return this->inlineExpression(offset, varMap, *e);
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
auto argList = [&](const std::vector<std::unique_ptr<Expression>>& originalArgs)
|
||||
-> std::vector<std::unique_ptr<Expression>> {
|
||||
std::vector<std::unique_ptr<Expression>> args;
|
||||
args.reserve(originalArgs.size());
|
||||
for (const std::unique_ptr<Expression>& arg : originalArgs) {
|
||||
args.push_back(expr(arg));
|
||||
}
|
||||
return args;
|
||||
};
|
||||
|
||||
switch (expression.fKind) {
|
||||
case Expression::kBinary_Kind: {
|
||||
const BinaryExpression& b = expression.as<BinaryExpression>();
|
||||
return std::make_unique<BinaryExpression>(offset,
|
||||
expr(b.fLeft),
|
||||
b.fOperator,
|
||||
expr(b.fRight),
|
||||
b.fType);
|
||||
}
|
||||
case Expression::kBoolLiteral_Kind:
|
||||
case Expression::kIntLiteral_Kind:
|
||||
case Expression::kFloatLiteral_Kind:
|
||||
case Expression::kNullLiteral_Kind:
|
||||
return expression.clone();
|
||||
case Expression::kConstructor_Kind: {
|
||||
const Constructor& constructor = expression.as<Constructor>();
|
||||
return std::make_unique<Constructor>(offset, constructor.fType,
|
||||
argList(constructor.fArguments));
|
||||
}
|
||||
case Expression::kExternalFunctionCall_Kind: {
|
||||
const ExternalFunctionCall& externalCall = expression.as<ExternalFunctionCall>();
|
||||
return std::make_unique<ExternalFunctionCall>(offset, externalCall.fType,
|
||||
externalCall.fFunction,
|
||||
argList(externalCall.fArguments));
|
||||
}
|
||||
case Expression::kExternalValue_Kind:
|
||||
return expression.clone();
|
||||
case Expression::kFieldAccess_Kind: {
|
||||
const FieldAccess& f = expression.as<FieldAccess>();
|
||||
return std::make_unique<FieldAccess>(expr(f.fBase), f.fFieldIndex, f.fOwnerKind);
|
||||
}
|
||||
case Expression::kFunctionCall_Kind: {
|
||||
const FunctionCall& funcCall = expression.as<FunctionCall>();
|
||||
return std::make_unique<FunctionCall>(offset, funcCall.fType, funcCall.fFunction,
|
||||
argList(funcCall.fArguments));
|
||||
}
|
||||
case Expression::kIndex_Kind: {
|
||||
const IndexExpression& idx = expression.as<IndexExpression>();
|
||||
return std::make_unique<IndexExpression>(*fContext, expr(idx.fBase), expr(idx.fIndex));
|
||||
}
|
||||
case Expression::kPrefix_Kind: {
|
||||
const PrefixExpression& p = expression.as<PrefixExpression>();
|
||||
return std::make_unique<PrefixExpression>(p.fOperator, expr(p.fOperand));
|
||||
}
|
||||
case Expression::kPostfix_Kind: {
|
||||
const PostfixExpression& p = expression.as<PostfixExpression>();
|
||||
return std::make_unique<PostfixExpression>(expr(p.fOperand), p.fOperator);
|
||||
}
|
||||
case Expression::kSetting_Kind:
|
||||
return expression.clone();
|
||||
case Expression::kSwizzle_Kind: {
|
||||
const Swizzle& s = expression.as<Swizzle>();
|
||||
return std::make_unique<Swizzle>(*fContext, expr(s.fBase), s.fComponents);
|
||||
}
|
||||
case Expression::kTernary_Kind: {
|
||||
const TernaryExpression& t = expression.as<TernaryExpression>();
|
||||
return std::make_unique<TernaryExpression>(offset, expr(t.fTest),
|
||||
expr(t.fIfTrue), expr(t.fIfFalse));
|
||||
}
|
||||
case Expression::kVariableReference_Kind: {
|
||||
const VariableReference& v = expression.as<VariableReference>();
|
||||
auto found = varMap->find(&v.fVariable);
|
||||
if (found != varMap->end()) {
|
||||
return std::make_unique<VariableReference>(offset, *found->second, v.fRefKind);
|
||||
}
|
||||
return v.clone();
|
||||
}
|
||||
default:
|
||||
SkASSERT(false);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Statement> Inliner::inlineStatement(int offset,
|
||||
VariableRewriteMap* varMap,
|
||||
SymbolTable* symbolTableForStatement,
|
||||
const Variable* returnVar,
|
||||
bool haveEarlyReturns,
|
||||
const Statement& statement) {
|
||||
auto stmt = [&](const std::unique_ptr<Statement>& s) -> std::unique_ptr<Statement> {
|
||||
if (s) {
|
||||
return this->inlineStatement(offset, varMap, symbolTableForStatement, returnVar,
|
||||
haveEarlyReturns, *s);
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
auto stmts = [&](const std::vector<std::unique_ptr<Statement>>& ss) {
|
||||
std::vector<std::unique_ptr<Statement>> result;
|
||||
for (const auto& s : ss) {
|
||||
result.push_back(stmt(s));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> {
|
||||
if (e) {
|
||||
return this->inlineExpression(offset, varMap, *e);
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
switch (statement.fKind) {
|
||||
case Statement::kBlock_Kind: {
|
||||
const Block& b = statement.as<Block>();
|
||||
return std::make_unique<Block>(offset, stmts(b.fStatements), b.fSymbols, b.fIsScope);
|
||||
}
|
||||
|
||||
case Statement::kBreak_Kind:
|
||||
case Statement::kContinue_Kind:
|
||||
case Statement::kDiscard_Kind:
|
||||
return statement.clone();
|
||||
|
||||
case Statement::kDo_Kind: {
|
||||
const DoStatement& d = statement.as<DoStatement>();
|
||||
return std::make_unique<DoStatement>(offset, stmt(d.fStatement), expr(d.fTest));
|
||||
}
|
||||
case Statement::kExpression_Kind: {
|
||||
const ExpressionStatement& e = statement.as<ExpressionStatement>();
|
||||
return std::make_unique<ExpressionStatement>(expr(e.fExpression));
|
||||
}
|
||||
case Statement::kFor_Kind: {
|
||||
const ForStatement& f = statement.as<ForStatement>();
|
||||
// need to ensure initializer is evaluated first so that we've already remapped its
|
||||
// declarations by the time we evaluate test & next
|
||||
std::unique_ptr<Statement> initializer = stmt(f.fInitializer);
|
||||
return std::make_unique<ForStatement>(offset, std::move(initializer), expr(f.fTest),
|
||||
expr(f.fNext), stmt(f.fStatement), f.fSymbols);
|
||||
}
|
||||
case Statement::kIf_Kind: {
|
||||
const IfStatement& i = statement.as<IfStatement>();
|
||||
return std::make_unique<IfStatement>(offset, i.fIsStatic, expr(i.fTest),
|
||||
stmt(i.fIfTrue), stmt(i.fIfFalse));
|
||||
}
|
||||
case Statement::kNop_Kind:
|
||||
return statement.clone();
|
||||
case Statement::kReturn_Kind: {
|
||||
const ReturnStatement& r = statement.as<ReturnStatement>();
|
||||
if (r.fExpression) {
|
||||
auto assignment = std::make_unique<ExpressionStatement>(
|
||||
std::make_unique<BinaryExpression>(
|
||||
offset,
|
||||
std::make_unique<VariableReference>(offset, *returnVar,
|
||||
VariableReference::kWrite_RefKind),
|
||||
Token::Kind::TK_EQ,
|
||||
expr(r.fExpression),
|
||||
returnVar->fType));
|
||||
if (haveEarlyReturns) {
|
||||
std::vector<std::unique_ptr<Statement>> block;
|
||||
block.push_back(std::move(assignment));
|
||||
block.emplace_back(new BreakStatement(offset));
|
||||
return std::make_unique<Block>(offset, std::move(block), /*symbols=*/nullptr,
|
||||
/*isScope=*/true);
|
||||
} else {
|
||||
return std::move(assignment);
|
||||
}
|
||||
} else {
|
||||
if (haveEarlyReturns) {
|
||||
return std::make_unique<BreakStatement>(offset);
|
||||
} else {
|
||||
return std::make_unique<Nop>();
|
||||
}
|
||||
}
|
||||
}
|
||||
case Statement::kSwitch_Kind: {
|
||||
const SwitchStatement& ss = statement.as<SwitchStatement>();
|
||||
std::vector<std::unique_ptr<SwitchCase>> cases;
|
||||
for (const auto& sc : ss.fCases) {
|
||||
cases.emplace_back(new SwitchCase(offset, expr(sc->fValue),
|
||||
stmts(sc->fStatements)));
|
||||
}
|
||||
return std::make_unique<SwitchStatement>(offset, ss.fIsStatic, expr(ss.fValue),
|
||||
std::move(cases), ss.fSymbols);
|
||||
}
|
||||
case Statement::kVarDeclaration_Kind: {
|
||||
const VarDeclaration& decl = statement.as<VarDeclaration>();
|
||||
std::vector<std::unique_ptr<Expression>> sizes;
|
||||
for (const auto& size : decl.fSizes) {
|
||||
sizes.push_back(expr(size));
|
||||
}
|
||||
std::unique_ptr<Expression> initialValue = expr(decl.fValue);
|
||||
const Variable* old = decl.fVar;
|
||||
// need to copy the var name in case the originating function is discarded and we lose
|
||||
// its symbols
|
||||
std::unique_ptr<String> name(new String(old->fName));
|
||||
const String* namePtr = symbolTableForStatement->takeOwnershipOfString(std::move(name));
|
||||
const Type* typePtr = copy_if_needed(&old->fType, *symbolTableForStatement);
|
||||
const Variable* clone = symbolTableForStatement->takeOwnershipOfSymbol(
|
||||
std::make_unique<Variable>(offset,
|
||||
old->fModifiers,
|
||||
namePtr->c_str(),
|
||||
*typePtr,
|
||||
old->fStorage,
|
||||
initialValue.get()));
|
||||
(*varMap)[old] = clone;
|
||||
return std::make_unique<VarDeclaration>(clone, std::move(sizes),
|
||||
std::move(initialValue));
|
||||
}
|
||||
case Statement::kVarDeclarations_Kind: {
|
||||
const VarDeclarations& decls = *statement.as<VarDeclarationsStatement>().fDeclaration;
|
||||
std::vector<std::unique_ptr<VarDeclaration>> vars;
|
||||
for (const auto& var : decls.fVars) {
|
||||
vars.emplace_back(&stmt(var).release()->as<VarDeclaration>());
|
||||
}
|
||||
const Type* typePtr = copy_if_needed(&decls.fBaseType, *symbolTableForStatement);
|
||||
return std::unique_ptr<Statement>(new VarDeclarationsStatement(
|
||||
std::make_unique<VarDeclarations>(offset, typePtr, std::move(vars))));
|
||||
}
|
||||
case Statement::kWhile_Kind: {
|
||||
const WhileStatement& w = statement.as<WhileStatement>();
|
||||
return std::make_unique<WhileStatement>(offset, expr(w.fTest), stmt(w.fStatement));
|
||||
}
|
||||
default:
|
||||
SkASSERT(false);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Inliner::InlinedCall Inliner::inlineCall(std::unique_ptr<FunctionCall> call,
|
||||
SymbolTable* symbolTableForCall) {
|
||||
// Inlining is more complicated here than in a typical compiler, because we have to have a
|
||||
// high-level IR and can't just drop statements into the middle of an expression or even use
|
||||
// gotos.
|
||||
//
|
||||
// Since we can't insert statements into an expression, we run the inline function as extra
|
||||
// statements before the statement we're currently processing, relying on a lack of execution
|
||||
// order guarantees. Since we can't use gotos (which are normally used to replace return
|
||||
// statements), we wrap the whole function in a loop and use break statements to jump to the
|
||||
// end.
|
||||
SkASSERT(fSettings);
|
||||
SkASSERT(fContext);
|
||||
SkASSERT(call);
|
||||
SkASSERT(this->isSafeToInline(*call, /*inlineThreshold=*/INT_MAX));
|
||||
|
||||
int offset = call->fOffset;
|
||||
std::vector<std::unique_ptr<Expression>>& arguments = call->fArguments;
|
||||
const FunctionDefinition& function = *call->fFunction.fDefinition;
|
||||
InlinedCall inlinedCall;
|
||||
|
||||
// Use unique variable names based on the function signature. Otherwise there are situations in
|
||||
// which an inlined function is later inlined into another function, and we end up with
|
||||
// duplicate names like 'inlineResult0' because the counter was reset. (skbug.com/10526)
|
||||
String raw = function.fDeclaration.description();
|
||||
String inlineSalt;
|
||||
for (size_t i = 0; i < raw.length(); ++i) {
|
||||
char c = raw[i];
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||
c == '_') {
|
||||
inlineSalt += c;
|
||||
}
|
||||
}
|
||||
|
||||
auto makeInlineVar = [&](const String& name, const Type& type, Modifiers modifiers,
|
||||
std::unique_ptr<Expression>* initialValue) -> const Variable* {
|
||||
// Add our new variable's name to the symbol table.
|
||||
const String* namePtr =
|
||||
symbolTableForCall->takeOwnershipOfString(std::make_unique<String>(name));
|
||||
StringFragment nameFrag{namePtr->c_str(), namePtr->length()};
|
||||
|
||||
// Add our new variable to the symbol table.
|
||||
auto newVar = std::make_unique<Variable>(/*offset=*/-1, Modifiers(), nameFrag, type,
|
||||
Variable::kLocal_Storage, initialValue->get());
|
||||
const Variable* variableSymbol = symbolTableForCall->add(nameFrag, std::move(newVar));
|
||||
|
||||
// Prepare the variable declaration (taking extra care with `out` params to not clobber any
|
||||
// initial value).
|
||||
std::vector<std::unique_ptr<VarDeclaration>> variables;
|
||||
if (initialValue && (modifiers.fFlags & Modifiers::kOut_Flag)) {
|
||||
variables.push_back(std::make_unique<VarDeclaration>(
|
||||
variableSymbol, /*sizes=*/std::vector<std::unique_ptr<Expression>>{},
|
||||
(*initialValue)->clone()));
|
||||
} else {
|
||||
variables.push_back(std::make_unique<VarDeclaration>(
|
||||
variableSymbol, /*sizes=*/std::vector<std::unique_ptr<Expression>>{},
|
||||
std::move(*initialValue)));
|
||||
}
|
||||
|
||||
// Add the new variable-declaration statement to our block of extra statements.
|
||||
inlinedCall.fInlinedBody.push_back(std::make_unique<VarDeclarationsStatement>(
|
||||
std::make_unique<VarDeclarations>(offset, &type, std::move(variables))));
|
||||
|
||||
return variableSymbol;
|
||||
};
|
||||
|
||||
// Create a variable to hold the result in the extra statements (excepting void).
|
||||
const Variable* resultVar = nullptr;
|
||||
if (function.fDeclaration.fReturnType != *fContext->fVoid_Type) {
|
||||
int varIndex = fInlineVarCounter++;
|
||||
|
||||
std::unique_ptr<Expression> noInitialValue;
|
||||
resultVar = makeInlineVar(String::printf("_inlineResult%s%d", inlineSalt.c_str(), varIndex),
|
||||
function.fDeclaration.fReturnType, Modifiers{}, &noInitialValue);
|
||||
}
|
||||
|
||||
// Create variables in the extra statements to hold the arguments, and assign the arguments to
|
||||
// them.
|
||||
VariableRewriteMap varMap;
|
||||
int argIndex = fInlineVarCounter++;
|
||||
for (int i = 0; i < (int) arguments.size(); ++i) {
|
||||
const Variable* param = function.fDeclaration.fParameters[i];
|
||||
|
||||
if (arguments[i]->fKind == Expression::kVariableReference_Kind) {
|
||||
// The argument is just a variable, so we only need to copy it if it's an out parameter
|
||||
// or it's written to within the function.
|
||||
if ((param->fModifiers.fFlags & Modifiers::kOut_Flag) ||
|
||||
!Analysis::StatementWritesToVariable(*function.fBody, *param)) {
|
||||
varMap[param] = &arguments[i]->as<VariableReference>().fVariable;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
varMap[param] = makeInlineVar(
|
||||
String::printf("_inlineArg%s%d_%d", inlineSalt.c_str(), argIndex, i),
|
||||
arguments[i]->fType, param->fModifiers, &arguments[i]);
|
||||
}
|
||||
|
||||
const Block& body = function.fBody->as<Block>();
|
||||
bool hasEarlyReturn = has_early_return(function);
|
||||
auto inlineBlock = std::make_unique<Block>(offset, std::vector<std::unique_ptr<Statement>>{});
|
||||
inlineBlock->fStatements.reserve(body.fStatements.size());
|
||||
for (const std::unique_ptr<Statement>& stmt : body.fStatements) {
|
||||
inlineBlock->fStatements.push_back(this->inlineStatement(
|
||||
offset, &varMap, symbolTableForCall, resultVar, hasEarlyReturn, *stmt));
|
||||
}
|
||||
if (hasEarlyReturn) {
|
||||
// Since we output to backends that don't have a goto statement (which would normally be
|
||||
// used to perform an early return), we fake it by wrapping the function in a
|
||||
// do { } while (false); and then use break statements to jump to the end in order to
|
||||
// emulate a goto.
|
||||
inlinedCall.fInlinedBody.push_back(std::make_unique<DoStatement>(
|
||||
/*offset=*/-1,
|
||||
std::move(inlineBlock),
|
||||
std::make_unique<BoolLiteral>(*fContext, offset, /*value=*/false)));
|
||||
} else {
|
||||
// No early returns, so we can just dump the code in. We need to use a block so we don't get
|
||||
// name conflicts with locals.
|
||||
inlinedCall.fInlinedBody.push_back(std::move(inlineBlock));
|
||||
}
|
||||
|
||||
// Copy the values of `out` parameters into their destinations.
|
||||
for (size_t i = 0; i < arguments.size(); ++i) {
|
||||
const Variable* p = function.fDeclaration.fParameters[i];
|
||||
if (p->fModifiers.fFlags & Modifiers::kOut_Flag) {
|
||||
SkASSERT(varMap.find(p) != varMap.end());
|
||||
if (arguments[i]->fKind == Expression::kVariableReference_Kind &&
|
||||
&arguments[i]->as<VariableReference>().fVariable == varMap[p]) {
|
||||
// we didn't create a temporary for this parameter, so there's nothing to copy back
|
||||
// out
|
||||
continue;
|
||||
}
|
||||
auto varRef = std::make_unique<VariableReference>(offset, *varMap[p]);
|
||||
inlinedCall.fInlinedBody.push_back(std::make_unique<ExpressionStatement>(
|
||||
std::make_unique<BinaryExpression>(offset,
|
||||
arguments[i]->clone(),
|
||||
Token::Kind::TK_EQ,
|
||||
std::move(varRef),
|
||||
arguments[i]->fType)));
|
||||
}
|
||||
}
|
||||
|
||||
if (function.fDeclaration.fReturnType != *fContext->fVoid_Type) {
|
||||
// Return a reference to the result variable as our replacement expression.
|
||||
inlinedCall.fReplacementExpr = std::make_unique<VariableReference>(offset, *resultVar);
|
||||
} else {
|
||||
// It's a void function, so it doesn't actually result in anything, but we have to return
|
||||
// something non-null as a standin.
|
||||
inlinedCall.fReplacementExpr = std::make_unique<BoolLiteral>(*fContext, offset,
|
||||
/*value=*/false);
|
||||
}
|
||||
|
||||
return inlinedCall;
|
||||
}
|
||||
|
||||
bool Inliner::isSafeToInline(const FunctionCall& functionCall,
|
||||
int inlineThreshold) {
|
||||
SkASSERT(fSettings);
|
||||
|
||||
if (functionCall.fFunction.fDefinition == nullptr) {
|
||||
// Can't inline something if we don't actually have its definition.
|
||||
return false;
|
||||
}
|
||||
const FunctionDefinition& functionDef = *functionCall.fFunction.fDefinition;
|
||||
if (inlineThreshold < INT_MAX) {
|
||||
if (!(functionDef.fDeclaration.fModifiers.fFlags & Modifiers::kInline_Flag) &&
|
||||
Analysis::NodeCount(functionDef) >= inlineThreshold) {
|
||||
// The function exceeds our maximum inline size and is not flagged 'inline'.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!fSettings->fCaps || !fSettings->fCaps->canUseDoLoops()) {
|
||||
// We don't have do-while loops. We use do-while loops to simulate early returns, so we
|
||||
// can't inline functions that have an early return.
|
||||
bool hasEarlyReturn = has_early_return(functionDef);
|
||||
|
||||
// If we didn't detect an early return, there shouldn't be any returns in breakable
|
||||
// constructs either.
|
||||
SkASSERT(hasEarlyReturn || count_returns_in_breakable_constructs(functionDef) == 0);
|
||||
return !hasEarlyReturn;
|
||||
}
|
||||
// We have do-while loops, but we don't have any mechanism to simulate early returns within a
|
||||
// breakable construct (switch/for/do/while), so we can't inline if there's a return inside one.
|
||||
bool hasReturnInBreakableConstruct = (count_returns_in_breakable_constructs(functionDef) > 0);
|
||||
|
||||
// If we detected returns in breakable constructs, we should also detect an early return.
|
||||
SkASSERT(!hasReturnInBreakableConstruct || has_early_return(functionDef));
|
||||
return !hasReturnInBreakableConstruct;
|
||||
}
|
||||
|
||||
} // namespace SkSL
|
71
src/sksl/SkSLInliner.h
Normal file
71
src/sksl/SkSLInliner.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SKSL_INLINER
|
||||
#define SKSL_INLINER
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "src/sksl/ir/SkSLProgram.h"
|
||||
|
||||
namespace SkSL {
|
||||
|
||||
class Context;
|
||||
struct Expression;
|
||||
struct FunctionCall;
|
||||
struct Statement;
|
||||
class SymbolTable;
|
||||
struct Variable;
|
||||
|
||||
/**
|
||||
* Converts a FunctionCall in the IR to a set of statements to be injected ahead of the function
|
||||
* call, and a replacement expression. Can also detect cases where inlining isn't cleanly possible
|
||||
* (e.g. return statements nested inside of a loop construct). The inliner isn't able to guarantee
|
||||
* identical-to-GLSL execution order if the inlined function has visible side effects.
|
||||
*/
|
||||
class Inliner {
|
||||
public:
|
||||
Inliner() {}
|
||||
|
||||
void reset(const Context&, const Program::Settings&);
|
||||
|
||||
/**
|
||||
* Processes the passed-in FunctionCall expression. The FunctionCall expression should be
|
||||
* replaced with `fReplacementExpr`. Any statements in `fInlinedBody` should be inserted
|
||||
* immediately above the statement containing the inlined expression.
|
||||
*/
|
||||
struct InlinedCall {
|
||||
std::vector<std::unique_ptr<Statement>> fInlinedBody;
|
||||
std::unique_ptr<Expression> fReplacementExpr;
|
||||
};
|
||||
InlinedCall inlineCall(std::unique_ptr<FunctionCall>, SymbolTable*);
|
||||
|
||||
/** Checks whether inlining is viable for a FunctionCall. */
|
||||
bool isSafeToInline(const FunctionCall&, int inlineThreshold);
|
||||
|
||||
private:
|
||||
using VariableRewriteMap = std::unordered_map<const Variable*, const Variable*>;
|
||||
|
||||
std::unique_ptr<Expression> inlineExpression(int offset,
|
||||
VariableRewriteMap* varMap,
|
||||
const Expression& expression);
|
||||
std::unique_ptr<Statement> inlineStatement(int offset,
|
||||
VariableRewriteMap* varMap,
|
||||
SymbolTable* symbolTableForStatement,
|
||||
const Variable* returnVar,
|
||||
bool haveEarlyReturns,
|
||||
const Statement& statement);
|
||||
|
||||
const Context* fContext = nullptr;
|
||||
const Program::Settings* fSettings = nullptr;
|
||||
int fInlineVarCounter = 0;
|
||||
};
|
||||
|
||||
} // namespace SkSL
|
||||
|
||||
#endif // SKSL_INLINER
|
Loading…
Reference in New Issue
Block a user