// Walk the statement stack from back to front, ignoring the last element (which is the // enclosing statement). auto iter = stmtStack.rbegin();
++iter;
// Anything counts as a parent statement other than a scopeless Block. for (; iter != stmtStack.rend(); ++iter) {
std::unique_ptr<Statement>* stmt = *iter; if (!is_scopeless_block(stmt->get())) { return stmt;
}
}
// There wasn't any parent statement to be found. return nullptr;
}
void Inliner::ensureScopedBlocks(Statement* inlinedBody, Statement* parentStmt) { // No changes necessary if this statement isn't actually a block. if (!inlinedBody || !inlinedBody->is<Block>()) { return;
}
// No changes necessary if the parent statement doesn't require a scope. if (!parentStmt || !(parentStmt->is<IfStatement>() || parentStmt->is<ForStatement>() ||
parentStmt->is<DoStatement>() || is_scopeless_block(parentStmt))) { return;
}
Block& block = inlinedBody->as<Block>();
// The inliner will create inlined function bodies as a Block containing multiple statements, // but no scope. Normally, this is fine, but if this block is used as the statement for a // do/for/if/while, the block needs to be scoped for the generated code to match the intent. // In the case of Blocks nested inside other Blocks, we add the scope to the outermost block if // needed. for (Block* nestedBlock = █; ) { if (nestedBlock->isScope()) { // We found an explicit scope; all is well. return;
} if (nestedBlock->children().size() == 1 && nestedBlock->children()[0]->is<Block>()) { // This block wraps another unscoped block; we need to go deeper.
nestedBlock = &nestedBlock->children()[0]->as<Block>(); continue;
} // We found a block containing real statements (not just more blocks), but no scope. // Let's add a scope to the outermost block.
block.setBlockKind(Block::Kind::kBracedScope); return;
}
}
std::unique_ptr<Expression> Inliner::inlineExpression(Position pos,
VariableRewriteMap* varMap,
SymbolTable* symbolTableForExpression, const Expression& expression) { auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> { if (e) { return this->inlineExpression(pos, varMap, symbolTableForExpression, *e);
} return nullptr;
}; auto argList = [&](const ExpressionArray& originalArgs) -> ExpressionArray {
ExpressionArray args;
args.reserve_exact(originalArgs.size()); for (const std::unique_ptr<Expression>& arg : originalArgs) {
args.push_back(expr(arg));
} return args;
}; auto childRemap = [&](const Variable& var) -> const Variable& { // If our variable remapping table contains the passed-in variable... if (std::unique_ptr<Expression>* remap = varMap->find(&var)) { // ... the remapped expression _must_ be another variable reference. // SkSL doesn't allow opaque types to participate in complex expressions. if ((*remap)->is<VariableReference>()) { const VariableReference& remappedRef = (*remap)->as<VariableReference>(); return *remappedRef.variable();
} else {
SkDEBUGFAILF("Child effect '%.*s' remaps to unexpected expression '%s'",
(int)var.name().size(), var.name().data(),
(*remap)->description().c_str());
}
}
// There's no remapping for this; return it as-is. return var;
};
case Statement::Kind::kBreak: return BreakStatement::Make(pos);
case Statement::Kind::kContinue: return ContinueStatement::Make(pos);
case Statement::Kind::kDiscard: return DiscardStatement::Make(*fContext, pos);
case Statement::Kind::kDo: { const DoStatement& d = statement.as<DoStatement>(); return DoStatement::Make(*fContext, pos, stmt(d.statement()), expr(d.test()));
} case Statement::Kind::kExpression: { const ExpressionStatement& e = statement.as<ExpressionStatement>(); return ExpressionStatement::Make(*fContext, expr(e.expression()));
} case Statement::Kind::kFor: return makeWithChildSymbolTable([&](std::unique_ptr<SymbolTable> symbolTable) { const ForStatement& f = statement.as<ForStatement>(); // We need to ensure `initializer` is evaluated first, so that we've already // remapped its declaration by the time we evaluate `test` and `next`.
std::unique_ptr<Statement> initializerStmt = stmt(f.initializer());
std::unique_ptr<Expression> testExpr = expr(f.test());
std::unique_ptr<Expression> nextExpr = expr(f.next());
std::unique_ptr<Statement> bodyStmt = stmt(f.statement());
std::unique_ptr<LoopUnrollInfo> unrollInfo; if (f.unrollInfo()) { // The for loop's unroll-info points to the Variable in the initializer as the // index. This variable has been rewritten into a clone by the inliner, so we // need to update the loop-unroll info to point to the clone.
unrollInfo = std::make_unique<LoopUnrollInfo>(*f.unrollInfo());
unrollInfo->fIndex = RemapVariable(unrollInfo->fIndex, varMap);
}
case Statement::Kind::kIf: { const IfStatement& i = statement.as<IfStatement>(); return IfStatement::Make(*fContext, pos, expr(i.test()),
stmt(i.ifTrue()), stmt(i.ifFalse()));
} case Statement::Kind::kNop: return Nop::Make();
case Statement::Kind::kReturn: { const ReturnStatement& r = statement.as<ReturnStatement>(); if (!r.expression()) { // This function doesn't return a value. We won't inline functions with early // returns, so a return statement is a no-op and can be treated as such. return Nop::Make();
}
// If a function only contains a single return, and it doesn't reference variables from // inside an Block's scope, we don't need to store the result in a variable at all. Just // replace the function-call expression with the function's return expression.
SkASSERT(resultExpr); if (returnComplexity <= Analysis::ReturnComplexity::kSingleSafeReturn) {
*resultExpr = expr(r.expression()); return Nop::Make();
}
// For more complex functions, we assign their result into a variable. We refuse to // inline anything with early returns, so this should be safe to do; that is, on this // control path, this is the last statement that will occur.
SkASSERT(*resultExpr); return ExpressionStatement::Make(
*fContext,
BinaryExpression::Make(
*fContext,
pos,
clone_with_ref_kind(**resultExpr, VariableRefKind::kWrite, pos), Operator::Kind::EQ,
expr(r.expression())));
} case Statement::Kind::kSwitch: { const SwitchStatement& ss = statement.as<SwitchStatement>(); return SwitchStatement::Make(*fContext, pos, expr(ss.value()), stmt(ss.caseBlock()));
} case Statement::Kind::kSwitchCase: { const SwitchCase& sc = statement.as<SwitchCase>(); return sc.isDefault() ? SwitchCase::MakeDefault(pos, stmt(sc.statement()))
: SwitchCase::Make(pos, sc.value(), stmt(sc.statement()));
} case Statement::Kind::kVarDeclaration: { const VarDeclaration& decl = statement.as<VarDeclaration>();
std::unique_ptr<Expression> initialValue = expr(decl.value()); const Variable* variable = decl.var();
// We assign unique names to inlined variables--scopes hide most of the problems in this // regard, but see `InlinerAvoidsVariableNameOverlap` for a counterexample where unique // names are important. const std::string* name = symbolTableForStatement->takeOwnershipOfString(
fMangler.uniqueName(variable->name(), symbolTableForStatement)); auto clonedVar =
Variable::Make(pos,
variable->modifiersPosition(),
variable->layout(),
variableModifiers(*variable, initialValue.get()),
variable->type().clone(*fContext, symbolTableForStatement),
name->c_str(), /*mangledName=*/"",
isBuiltinCode,
variable->storage());
varMap->set(variable, VariableReference::Make(pos, clonedVar.get()));
std::unique_ptr<Statement> result =
VarDeclaration::Make(*fContext,
clonedVar.get(),
decl.baseType().clone(*fContext, symbolTableForStatement),
decl.arraySize(),
std::move(initialValue));
symbolTableForStatement->add(*fContext, std::move(clonedVar)); return result;
} default:
SkASSERT(false); return nullptr;
}
}
staticbool argument_needs_scratch_variable(const Expression* arg, const Variable* param, const ProgramUsage& usage) { // If the parameter isn't written to within the inline function ... const ProgramUsage::VariableCounts& paramUsage = usage.get(*param); if (!paramUsage.fWrite) { // ... and it can be inlined trivially (e.g. a swizzle, or a constant array index), // or it is any expression without side effects that is only accessed at most once... if ((paramUsage.fRead > 1) ? Analysis::IsTrivialExpression(*arg)
: !Analysis::HasSideEffects(*arg)) { // ... we don't need to copy it at all! We can just use the existing expression. returnfalse;
}
} // We need a scratch variable. returntrue;
}
// 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.
SkASSERT(fContext);
SkASSERT(this->isSafeToInline(call.function().definition(), usage));
const ExpressionArray& arguments = call.arguments(); const Position pos = call.fPosition; const FunctionDefinition& function = *call.function().definition(); const Block& body = function.body()->as<Block>(); const Analysis::ReturnComplexity returnComplexity = Analysis::GetReturnComplexity(function);
StatementArray inlineStatements; int expectedStmtCount = 1 + // Result variable
arguments.size() + // Function argument temp-vars
body.children().size(); // Inlined code
std::unique_ptr<Expression> resultExpr; if (returnComplexity > Analysis::ReturnComplexity::kSingleSafeReturn &&
!function.declaration().returnType().isVoid()) { // Create a variable to hold the result in the extra statements. We don't need to do this // for void-return functions, or in cases that are simple enough that we can just replace // the function-call node with the result expression.
ScratchVariable var = Variable::MakeScratchVariable(*fContext,
fMangler,
function.declaration().name(),
&function.declaration().returnType(),
symbolTable, /*initialValue=*/nullptr);
inlineStatements.push_back(std::move(var.fVarDecl));
resultExpr = VariableReference::Make(Position(), var.fVarSymbol);
}
// Create variables in the extra statements to hold the arguments, and assign the arguments to // them.
VariableRewriteMap varMap; for (int i = 0; i < arguments.size(); ++i) { const Expression* arg = arguments[i].get(); const Variable* param = function.declaration().parameters()[i]; if (!argument_needs_scratch_variable(arg, param, usage)) {
varMap.set(param, arg->clone()); continue;
}
ScratchVariable var = Variable::MakeScratchVariable(*fContext,
fMangler,
param->name(),
&arg->type(),
symbolTable,
arg->clone());
inlineStatements.push_back(std::move(var.fVarDecl));
varMap.set(param, VariableReference::Make(Position(), var.fVarSymbol));
}
// Wrap all of the generated statements in a block. We need a real Block here, because we need // to add another child statement to the Block later.
InlinedCall inlinedCall;
inlinedCall.fInlinedBody = Block::MakeBlock(pos, std::move(inlineStatements),
Block::Kind::kUnbracedBlock); if (resultExpr) { // Return our result expression as-is.
inlinedCall.fReplacementExpr = std::move(resultExpr);
} elseif (function.declaration().returnType().isVoid()) { // It's a void function, so its result is the empty expression.
inlinedCall.fReplacementExpr = EmptyExpression::Make(pos, *fContext);
} else { // It's a non-void function, but it never created a result expression--that is, it never // returned anything on any path! This should have been detected in the function finalizer. // Still, discard our output and generate an error.
SkDEBUGFAIL("inliner found non-void function that fails to return a value on any path");
fContext->fErrors->error(function.fPosition, "inliner found non-void function '" +
std::string(function.declaration().name()) + "' that fails to return a value on any path");
inlinedCall = {};
}
return inlinedCall;
}
bool Inliner::isSafeToInline(const FunctionDefinition* functionDef, const ProgramUsage& usage) { // A threshold of zero indicates that the inliner is completely disabled, so we can just return. if (this->settings().fInlineThreshold <= 0) { returnfalse;
}
// Enforce a limit on inlining to avoid pathological cases. (inliner/ExponentialGrowth.sksl) if (fInlinedStatementCounter >= kInlinedStatementLimit) { returnfalse;
}
if (functionDef == nullptr) { // Can't inline something if we don't actually have its definition. returnfalse;
}
if (functionDef->declaration().modifierFlags().isNoInline()) { // Refuse to inline functions decorated with `noinline`. returnfalse;
}
for (const Variable* param : functionDef->declaration().parameters()) { // We don't allow inlining functions with parameters that are written-to, if they... // - are `out` parameters (see skia:11326 for rationale.) // - are arrays or structures (introducing temporary copies is non-trivial) if ((param->modifierFlags() & ModifierFlag::kOut) ||
param->type().isArray() ||
param->type().isStruct()) {
ProgramUsage::VariableCounts counts = usage.get(*param); if (counts.fWrite > 0) { returnfalse;
}
}
}
// We don't have a mechanism to simulate early returns, so we can't inline if there is one. return Analysis::GetReturnComplexity(*functionDef) < Analysis::ReturnComplexity::kEarlyReturns;
}
// A candidate function for inlining, containing everything that `inlineCall` needs. struct InlineCandidate {
SymbolTable* fSymbols; // the SymbolTable of the candidate
std::unique_ptr<Statement>* fParentStmt; // the parent Statement of the enclosing stmt
std::unique_ptr<Statement>* fEnclosingStmt; // the Statement containing the candidate
std::unique_ptr<Expression>* fCandidateExpr; // the candidate FunctionCall to be inlined
FunctionDefinition* fEnclosingFunction; // the Function containing the candidate
};
class InlineCandidateAnalyzer { public: // A list of all the inlining candidates we found during analysis.
InlineCandidateList* fCandidateList;
// A stack of the symbol tables; since most nodes don't have one, expected to be shallower than // the enclosing-statement stack.
std::vector<SymbolTable*> fSymbolTableStack; // A stack of "enclosing" statements--these would be suitable for the inliner to use for adding // new instructions. Not all statements are suitable (e.g. a for-loop's initializer). The // inliner might replace a statement with a block containing the statement.
std::vector<std::unique_ptr<Statement>*> fEnclosingStmtStack; // The function that we're currently processing (i.e. inlining into).
FunctionDefinition* fEnclosingFunction = nullptr;
// If this function has parameter names that would shadow globally-scoped names, we // don't scan it for inline candidates, because it's too late to mangle the names. bool foundShadowingParameterName = false; for (const Variable* param : funcDef.declaration().parameters()) { if (fSymbolTableStack.front()->find(param->name())) {
foundShadowingParameterName = true; break;
}
}
if (!foundShadowingParameterName) {
fEnclosingFunction = &funcDef;
this->visitStatement(&funcDef.body());
} break;
} default: // The inliner can't operate outside of a function's scope. break;
}
}
Analysis::SymbolTableStackBuilder scopedStackBuilder(stmt->get(), &fSymbolTableStack); // If this statement contains symbols that would shadow globally-scoped names, we don't look // for any inline candidates, because it's too late to mangle the names. if (scopedStackBuilder.foundSymbolTable() &&
fSymbolTableStack.back()->wouldShadowSymbolsFrom(fSymbolTableStack.front())) { return;
}
if (isViableAsEnclosingStatement) {
fEnclosingStmtStack.push_back(stmt);
}
switch ((*stmt)->kind()) { case Statement::Kind::kBreak: case Statement::Kind::kContinue: case Statement::Kind::kDiscard: case Statement::Kind::kNop: break;
case Statement::Kind::kBlock: {
Block& block = (*stmt)->as<Block>(); for (std::unique_ptr<Statement>& blockStmt : block.children()) {
this->visitStatement(&blockStmt);
} break;
} case Statement::Kind::kDo: {
DoStatement& doStmt = (*stmt)->as<DoStatement>(); // The loop body is a candidate for inlining.
this->visitStatement(&doStmt.statement()); // The inliner isn't smart enough to inline the test-expression for a do-while // loop at this time. There are two limitations: // - We would need to insert the inlined-body block at the very end of the do- // statement's inner fStatement. We don't support that today, but it's doable. // - We cannot inline the test expression if the loop uses `continue` anywhere; that // would skip over the inlined block that evaluates the test expression. There // isn't a good fix for this--any workaround would be more complex than the cost // of a function call. However, loops that don't use `continue` would still be // viable candidates for inlining. break;
} case Statement::Kind::kExpression: {
ExpressionStatement& expr = (*stmt)->as<ExpressionStatement>();
this->visitExpression(&expr.expression()); break;
} case Statement::Kind::kFor: {
ForStatement& forStmt = (*stmt)->as<ForStatement>(); // The initializer and loop body are candidates for inlining.
this->visitStatement(&forStmt.initializer(), /*isViableAsEnclosingStatement=*/false);
this->visitStatement(&forStmt.statement());
// The inliner isn't smart enough to inline the test- or increment-expressions // of a for loop loop at this time. There are a handful of limitations: // - We would need to insert the test-expression block at the very beginning of the // for-loop's inner fStatement, and the increment-expression block at the very // end. We don't support that today, but it's doable. // - The for-loop's built-in test-expression would need to be dropped entirely, // and the loop would be halted via a break statement at the end of the inlined // test-expression. This is again something we don't support today, but it could // be implemented. // - We cannot inline the increment-expression if the loop uses `continue` anywhere; // that would skip over the inlined block that evaluates the increment expression. // There isn't a good fix for this--any workaround would be more complex than the // cost of a function call. However, loops that don't use `continue` would still // be viable candidates for increment-expression inlining. break;
} case Statement::Kind::kIf: {
IfStatement& ifStmt = (*stmt)->as<IfStatement>();
this->visitExpression(&ifStmt.test());
this->visitStatement(&ifStmt.ifTrue());
this->visitStatement(&ifStmt.ifFalse()); break;
} case Statement::Kind::kReturn: {
ReturnStatement& returnStmt = (*stmt)->as<ReturnStatement>();
this->visitExpression(&returnStmt.expression()); break;
} case Statement::Kind::kSwitch: {
SwitchStatement& switchStmt = (*stmt)->as<SwitchStatement>();
this->visitExpression(&switchStmt.value()); for (const std::unique_ptr<Statement>& switchCase : switchStmt.cases()) { // The switch-case's fValue cannot be a FunctionCall; skip it.
this->visitStatement(&switchCase->as<SwitchCase>().statement());
} break;
} case Statement::Kind::kVarDeclaration: {
VarDeclaration& varDeclStmt = (*stmt)->as<VarDeclaration>(); // Don't need to scan the declaration's sizes; those are always literals.
this->visitExpression(&varDeclStmt.value()); break;
} default:
SkUNREACHABLE;
}
// Pop our symbol and enclosing-statement stacks.
fEnclosingStmtStack.resize(oldEnclosingStmtStackSize);
}
void visitExpression(std::unique_ptr<Expression>* expr) { if (!*expr) { return;
}
switch ((*expr)->kind()) { case Expression::Kind::kFieldAccess: case Expression::Kind::kFunctionReference: case Expression::Kind::kLiteral: case Expression::Kind::kMethodReference: case Expression::Kind::kSetting: case Expression::Kind::kTypeReference: case Expression::Kind::kVariableReference: // Nothing to scan here. break;
case Expression::Kind::kBinary: {
BinaryExpression& binaryExpr = (*expr)->as<BinaryExpression>();
this->visitExpression(&binaryExpr.left());
// Logical-and and logical-or binary expressions do not inline the right side, // because that would invalidate short-circuiting. That is, when evaluating // expressions like these: // (false && x()) // always false // (true || y()) // always true // It is illegal for side-effects from x() or y() to occur. The simplest way to // enforce that rule is to avoid inlining the right side entirely. However, it is // safe for other types of binary expression to inline both sides. Operator op = binaryExpr.getOperator(); bool shortCircuitable = (op.kind() == Operator::Kind::LOGICALAND ||
op.kind() == Operator::Kind::LOGICALOR); if (!shortCircuitable) {
this->visitExpression(&binaryExpr.right());
} break;
} case Expression::Kind::kChildCall: {
ChildCall& childCallExpr = (*expr)->as<ChildCall>(); for (std::unique_ptr<Expression>& arg : childCallExpr.arguments()) {
this->visitExpression(&arg);
} break;
} case Expression::Kind::kConstructorArray: case Expression::Kind::kConstructorArrayCast: case Expression::Kind::kConstructorCompound: case Expression::Kind::kConstructorCompoundCast: case Expression::Kind::kConstructorDiagonalMatrix: case Expression::Kind::kConstructorMatrixResize: case Expression::Kind::kConstructorScalarCast: case Expression::Kind::kConstructorSplat: case Expression::Kind::kConstructorStruct: {
AnyConstructor& constructorExpr = (*expr)->asAnyConstructor(); for (std::unique_ptr<Expression>& arg : constructorExpr.argumentSpan()) {
this->visitExpression(&arg);
} break;
} case Expression::Kind::kFunctionCall: {
FunctionCall& funcCallExpr = (*expr)->as<FunctionCall>(); for (std::unique_ptr<Expression>& arg : funcCallExpr.arguments()) {
this->visitExpression(&arg);
}
this->addInlineCandidate(expr); break;
} case Expression::Kind::kIndex: {
IndexExpression& indexExpr = (*expr)->as<IndexExpression>();
this->visitExpression(&indexExpr.base());
this->visitExpression(&indexExpr.index()); break;
} case Expression::Kind::kPostfix: {
PostfixExpression& postfixExpr = (*expr)->as<PostfixExpression>();
this->visitExpression(&postfixExpr.operand()); break;
} case Expression::Kind::kPrefix: {
PrefixExpression& prefixExpr = (*expr)->as<PrefixExpression>();
this->visitExpression(&prefixExpr.operand()); break;
} case Expression::Kind::kSwizzle: {
Swizzle& swizzleExpr = (*expr)->as<Swizzle>();
this->visitExpression(&swizzleExpr.base()); break;
} case Expression::Kind::kTernary: {
TernaryExpression& ternaryExpr = (*expr)->as<TernaryExpression>(); // The test expression is a candidate for inlining.
this->visitExpression(&ternaryExpr.test()); // The true- and false-expressions cannot be inlined, because we are only allowed to // evaluate one side. break;
} default:
SkUNREACHABLE;
}
}
bool Inliner::candidateCanBeInlined(const InlineCandidate& candidate, const ProgramUsage& usage,
InlinabilityCache* cache) { // Check the cache to see if this function is safe to inline. const FunctionDeclaration& funcDecl = candidate_func(candidate); if (!this->functionCanBeInlined(funcDecl, usage, cache)) { returnfalse;
}
// Even if the function is safe, the arguments we are passing may not be. In particular, we // can't make copies of opaque values, so we need to reject inline candidates that would need to // do this. Every call has different arguments, so this part is not cacheable. (skia:13824) const FunctionCall& call = candidate.fCandidateExpr->get()->as<FunctionCall>(); const ExpressionArray& arguments = call.arguments(); for (int i = 0; i < arguments.size(); ++i) { const Expression* arg = arguments[i].get(); if (arg->type().isOpaque()) { const Variable* param = funcDecl.parameters()[i]; if (argument_needs_scratch_variable(arg, param, usage)) { returnfalse;
}
}
}
returntrue;
}
int Inliner::getFunctionSize(const FunctionDeclaration& funcDecl, FunctionSizeCache* cache) { if (constint* cachedSize = cache->find(&funcDecl)) { return *cachedSize;
} int size = Analysis::NodeCountUpToLimit(*funcDecl.definition(),
this->settings().fInlineThreshold);
cache->set(&funcDecl, size); return size;
}
void Inliner::buildCandidateList(const std::vector<std::unique_ptr<ProgramElement>>& elements,
SymbolTable* symbols,
ProgramUsage* usage,
InlineCandidateList* candidateList) { // This is structured much like a ProgramVisitor, but does not actually use ProgramVisitor. // The analyzer needs to keep track of the `unique_ptr<T>*` of statements and expressions so // that they can later be replaced, and ProgramVisitor does not provide this; it only provides a // `const T&`.
InlineCandidateAnalyzer analyzer;
analyzer.visit(elements, symbols, candidateList);
// Early out if there are no inlining candidates.
std::vector<InlineCandidate>& candidates = candidateList->fCandidates; if (candidates.empty()) { return;
}
// Remove candidates that are not safe to inline.
InlinabilityCache cache;
candidates.erase(std::remove_if(candidates.begin(),
candidates.end(),
[&](const InlineCandidate& candidate) { return !this->candidateCanBeInlined(
candidate, *usage, &cache);
}),
candidates.end());
// If the inline threshold is unlimited, or if we have no candidates left, our candidate list is // complete. if (this->settings().fInlineThreshold == INT_MAX || candidates.empty()) { return;
}
// Remove candidates on a per-function basis if the effect of inlining would be to make more // than `inlineThreshold` nodes. (i.e. if Func() would be inlined six times and its size is // 10 nodes, it should be inlined if the inlineThreshold is 60 or higher.)
FunctionSizeCache functionSizeCache;
FunctionSizeCache candidateTotalCost; for (InlineCandidate& candidate : candidates) { const FunctionDeclaration& fnDecl = candidate_func(candidate);
candidateTotalCost[&fnDecl] += this->getFunctionSize(fnDecl, &functionSizeCache);
}
candidates.erase(std::remove_if(candidates.begin(), candidates.end(),
[&](const InlineCandidate& candidate) { const FunctionDeclaration& fnDecl = candidate_func(candidate); if (fnDecl.modifierFlags().isInline()) { // Functions marked `inline` ignore size limitations. returnfalse;
} if (usage->get(fnDecl) == 1) { // If a function is only used once, it's cost-free to inline. returnfalse;
} if (candidateTotalCost[&fnDecl] <= this->settings().fInlineThreshold) { // We won't exceed the inline threshold by inlining this. returnfalse;
} // Inlining this function will add too many IRNodes. returntrue;
}),
candidates.end());
}
bool Inliner::analyze(const std::vector<std::unique_ptr<ProgramElement>>& elements,
SymbolTable* symbols,
ProgramUsage* usage) { // A threshold of zero indicates that the inliner is completely disabled, so we can just return. if (this->settings().fInlineThreshold <= 0) { returnfalse;
}
// Enforce a limit on inlining to avoid pathological cases. (inliner/ExponentialGrowth.sksl) if (fInlinedStatementCounter >= kInlinedStatementLimit) { returnfalse;
}
// Inline the candidates where we've determined that it's safe to do so. using StatementRemappingTable = THashMap<std::unique_ptr<Statement>*,
std::unique_ptr<Statement>*>;
StatementRemappingTable statementRemappingTable;
// Convert the function call to its inlined equivalent.
InlinedCall inlinedCall = this->inlineCall(funcCall, candidate.fSymbols, *usage,
&candidate.fEnclosingFunction->declaration());
// Stop if an error was detected during the inlining process. if (!inlinedCall.fInlinedBody && !inlinedCall.fReplacementExpr) { break;
}
// 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());
// Look up the enclosing statement; remap it if necessary.
std::unique_ptr<Statement>* enclosingStmt = candidate.fEnclosingStmt; for (;;) {
std::unique_ptr<Statement>** remappedStmt = statementRemappingTable.find(enclosingStmt); if (!remappedStmt) { break;
}
enclosingStmt = *remappedStmt;
}
// Move the enclosing statement to the end of the unscoped Block containing the inlined // function, then replace the enclosing statement with that Block. // Before: // fInlinedBody = Block{ stmt1, stmt2, stmt3 } // fEnclosingStmt = stmt4 // After: // fInlinedBody = null // fEnclosingStmt = Block{ stmt1, stmt2, stmt3, stmt4 }
inlinedCall.fInlinedBody->children().push_back(std::move(*enclosingStmt));
*enclosingStmt = std::move(inlinedCall.fInlinedBody);
// Replace the candidate function call with our replacement expression.
usage->remove(candidate.fCandidateExpr->get());
usage->add(inlinedCall.fReplacementExpr.get());
*candidate.fCandidateExpr = std::move(inlinedCall.fReplacementExpr);
madeChanges = true;
// If anything else pointed at our enclosing statement, it's now pointing at a Block // containing many other statements as well. Maintain a fix-up table to account for this.
statementRemappingTable.set(enclosingStmt,&(*enclosingStmt)->as<Block>().children().back());
// Stop inlining if we've reached our hard cap on new statements. if (fInlinedStatementCounter >= kInlinedStatementLimit) { break;
}
// Note that nothing was destroyed except for the FunctionCall. All other nodes should // remain valid.
}
return madeChanges;
}
} // namespace SkSL
#endif// SK_ENABLE_OPTIMIZE_SIZE
Messung V0.5
¤ Dauer der Verarbeitung: 0.24 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.