1 /*
2 * Copyright 2020 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/sksl/SkSLInliner.h"
9
10 #ifndef SK_ENABLE_OPTIMIZE_SIZE
11
12 #include "include/core/SkSpan.h"
13 #include "include/core/SkTypes.h"
14 #include "include/private/SkSLDefines.h"
15 #include "include/private/SkSLIRNode.h"
16 #include "include/private/SkSLModifiers.h"
17 #include "include/private/SkSLProgramElement.h"
18 #include "include/private/SkSLStatement.h"
19 #include "include/private/base/SkTArray.h"
20 #include "include/sksl/SkSLErrorReporter.h"
21 #include "include/sksl/SkSLOperator.h"
22 #include "include/sksl/SkSLPosition.h"
23 #include "src/sksl/SkSLAnalysis.h"
24 #include "src/sksl/analysis/SkSLProgramUsage.h"
25 #include "src/sksl/ir/SkSLBinaryExpression.h"
26 #include "src/sksl/ir/SkSLChildCall.h"
27 #include "src/sksl/ir/SkSLConstructor.h"
28 #include "src/sksl/ir/SkSLConstructorArray.h"
29 #include "src/sksl/ir/SkSLConstructorArrayCast.h"
30 #include "src/sksl/ir/SkSLConstructorCompound.h"
31 #include "src/sksl/ir/SkSLConstructorCompoundCast.h"
32 #include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
33 #include "src/sksl/ir/SkSLConstructorMatrixResize.h"
34 #include "src/sksl/ir/SkSLConstructorScalarCast.h"
35 #include "src/sksl/ir/SkSLConstructorSplat.h"
36 #include "src/sksl/ir/SkSLConstructorStruct.h"
37 #include "src/sksl/ir/SkSLDoStatement.h"
38 #include "src/sksl/ir/SkSLExpressionStatement.h"
39 #include "src/sksl/ir/SkSLFieldAccess.h"
40 #include "src/sksl/ir/SkSLForStatement.h"
41 #include "src/sksl/ir/SkSLFunctionCall.h"
42 #include "src/sksl/ir/SkSLFunctionDeclaration.h"
43 #include "src/sksl/ir/SkSLFunctionDefinition.h"
44 #include "src/sksl/ir/SkSLIfStatement.h"
45 #include "src/sksl/ir/SkSLIndexExpression.h"
46 #include "src/sksl/ir/SkSLLiteral.h"
47 #include "src/sksl/ir/SkSLNop.h"
48 #include "src/sksl/ir/SkSLPostfixExpression.h"
49 #include "src/sksl/ir/SkSLPrefixExpression.h"
50 #include "src/sksl/ir/SkSLReturnStatement.h"
51 #include "src/sksl/ir/SkSLSetting.h"
52 #include "src/sksl/ir/SkSLSwitchCase.h"
53 #include "src/sksl/ir/SkSLSwitchStatement.h"
54 #include "src/sksl/ir/SkSLSwizzle.h"
55 #include "src/sksl/ir/SkSLSymbolTable.h"
56 #include "src/sksl/ir/SkSLTernaryExpression.h"
57 #include "src/sksl/ir/SkSLType.h"
58 #include "src/sksl/ir/SkSLVarDeclarations.h"
59 #include "src/sksl/ir/SkSLVariable.h"
60 #include "src/sksl/ir/SkSLVariableReference.h"
61 #include "src/sksl/transform/SkSLTransform.h"
62
63 #include <algorithm>
64 #include <climits>
65 #include <cstddef>
66 #include <memory>
67 #include <string>
68 #include <string_view>
69 #include <utility>
70
71 namespace SkSL {
72 namespace {
73
74 static constexpr int kInlinedStatementLimit = 2500;
75
find_parent_statement(const std::vector<std::unique_ptr<Statement> * > & stmtStack)76 static std::unique_ptr<Statement>* find_parent_statement(
77 const std::vector<std::unique_ptr<Statement>*>& stmtStack) {
78 SkASSERT(!stmtStack.empty());
79
80 // Walk the statement stack from back to front, ignoring the last element (which is the
81 // enclosing statement).
82 auto iter = stmtStack.rbegin();
83 ++iter;
84
85 // Anything counts as a parent statement other than a scopeless Block.
86 for (; iter != stmtStack.rend(); ++iter) {
87 std::unique_ptr<Statement>* stmt = *iter;
88 if (!(*stmt)->is<Block>() || (*stmt)->as<Block>().isScope()) {
89 return stmt;
90 }
91 }
92
93 // There wasn't any parent statement to be found.
94 return nullptr;
95 }
96
clone_with_ref_kind(const Expression & expr,VariableReference::RefKind refKind)97 std::unique_ptr<Expression> clone_with_ref_kind(const Expression& expr,
98 VariableReference::RefKind refKind) {
99 std::unique_ptr<Expression> clone = expr.clone();
100 Analysis::UpdateVariableRefKind(clone.get(), refKind);
101 return clone;
102 }
103
104 } // namespace
105
RemapVariable(const Variable * variable,const VariableRewriteMap * varMap)106 const Variable* Inliner::RemapVariable(const Variable* variable,
107 const VariableRewriteMap* varMap) {
108 std::unique_ptr<Expression>* remap = varMap->find(variable);
109 if (!remap) {
110 SkDEBUGFAILF("rewrite map does not contain variable '%.*s'",
111 (int)variable->name().size(), variable->name().data());
112 return variable;
113 }
114 Expression* expr = remap->get();
115 SkASSERT(expr);
116 if (!expr->is<VariableReference>()) {
117 SkDEBUGFAILF("rewrite map contains non-variable replacement for '%.*s'",
118 (int)variable->name().size(), variable->name().data());
119 return variable;
120 }
121 return expr->as<VariableReference>().variable();
122 }
123
ensureScopedBlocks(Statement * inlinedBody,Statement * parentStmt)124 void Inliner::ensureScopedBlocks(Statement* inlinedBody, Statement* parentStmt) {
125 // No changes necessary if this statement isn't actually a block.
126 if (!inlinedBody || !inlinedBody->is<Block>()) {
127 return;
128 }
129
130 // No changes necessary if the parent statement doesn't require a scope.
131 if (!parentStmt || !(parentStmt->is<IfStatement>() || parentStmt->is<ForStatement>() ||
132 parentStmt->is<DoStatement>())) {
133 return;
134 }
135
136 Block& block = inlinedBody->as<Block>();
137
138 // The inliner will create inlined function bodies as a Block containing multiple statements,
139 // but no scope. Normally, this is fine, but if this block is used as the statement for a
140 // do/for/if/while, the block needs to be scoped for the generated code to match the intent.
141 // In the case of Blocks nested inside other Blocks, we add the scope to the outermost block if
142 // needed.
143 for (Block* nestedBlock = █; ) {
144 if (nestedBlock->isScope()) {
145 // We found an explicit scope; all is well.
146 return;
147 }
148 if (nestedBlock->children().size() == 1 && nestedBlock->children()[0]->is<Block>()) {
149 // This block wraps another unscoped block; we need to go deeper.
150 nestedBlock = &nestedBlock->children()[0]->as<Block>();
151 continue;
152 }
153 // We found a block containing real statements (not just more blocks), but no scope.
154 // Let's add a scope to the outermost block.
155 block.setBlockKind(Block::Kind::kBracedScope);
156 return;
157 }
158 }
159
inlineExpression(Position pos,VariableRewriteMap * varMap,SymbolTable * symbolTableForExpression,const Expression & expression)160 std::unique_ptr<Expression> Inliner::inlineExpression(Position pos,
161 VariableRewriteMap* varMap,
162 SymbolTable* symbolTableForExpression,
163 const Expression& expression) {
164 auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> {
165 if (e) {
166 return this->inlineExpression(pos, varMap, symbolTableForExpression, *e);
167 }
168 return nullptr;
169 };
170 auto argList = [&](const ExpressionArray& originalArgs) -> ExpressionArray {
171 ExpressionArray args;
172 args.reserve_back(originalArgs.size());
173 for (const std::unique_ptr<Expression>& arg : originalArgs) {
174 args.push_back(expr(arg));
175 }
176 return args;
177 };
178
179 switch (expression.kind()) {
180 case Expression::Kind::kBinary: {
181 const BinaryExpression& binaryExpr = expression.as<BinaryExpression>();
182 return BinaryExpression::Make(*fContext,
183 pos,
184 expr(binaryExpr.left()),
185 binaryExpr.getOperator(),
186 expr(binaryExpr.right()));
187 }
188 case Expression::Kind::kLiteral:
189 return expression.clone();
190 case Expression::Kind::kChildCall: {
191 const ChildCall& childCall = expression.as<ChildCall>();
192 return ChildCall::Make(*fContext,
193 pos,
194 childCall.type().clone(symbolTableForExpression),
195 childCall.child(),
196 argList(childCall.arguments()));
197 }
198 case Expression::Kind::kConstructorArray: {
199 const ConstructorArray& ctor = expression.as<ConstructorArray>();
200 return ConstructorArray::Make(*fContext, pos,
201 *ctor.type().clone(symbolTableForExpression),
202 argList(ctor.arguments()));
203 }
204 case Expression::Kind::kConstructorArrayCast: {
205 const ConstructorArrayCast& ctor = expression.as<ConstructorArrayCast>();
206 return ConstructorArrayCast::Make(*fContext, pos,
207 *ctor.type().clone(symbolTableForExpression),
208 expr(ctor.argument()));
209 }
210 case Expression::Kind::kConstructorCompound: {
211 const ConstructorCompound& ctor = expression.as<ConstructorCompound>();
212 return ConstructorCompound::Make(*fContext, pos,
213 *ctor.type().clone(symbolTableForExpression),
214 argList(ctor.arguments()));
215 }
216 case Expression::Kind::kConstructorCompoundCast: {
217 const ConstructorCompoundCast& ctor = expression.as<ConstructorCompoundCast>();
218 return ConstructorCompoundCast::Make(*fContext, pos,
219 *ctor.type().clone(symbolTableForExpression),
220 expr(ctor.argument()));
221 }
222 case Expression::Kind::kConstructorDiagonalMatrix: {
223 const ConstructorDiagonalMatrix& ctor = expression.as<ConstructorDiagonalMatrix>();
224 return ConstructorDiagonalMatrix::Make(*fContext, pos,
225 *ctor.type().clone(symbolTableForExpression),
226 expr(ctor.argument()));
227 }
228 case Expression::Kind::kConstructorMatrixResize: {
229 const ConstructorMatrixResize& ctor = expression.as<ConstructorMatrixResize>();
230 return ConstructorMatrixResize::Make(*fContext, pos,
231 *ctor.type().clone(symbolTableForExpression),
232 expr(ctor.argument()));
233 }
234 case Expression::Kind::kConstructorScalarCast: {
235 const ConstructorScalarCast& ctor = expression.as<ConstructorScalarCast>();
236 return ConstructorScalarCast::Make(*fContext, pos,
237 *ctor.type().clone(symbolTableForExpression),
238 expr(ctor.argument()));
239 }
240 case Expression::Kind::kConstructorSplat: {
241 const ConstructorSplat& ctor = expression.as<ConstructorSplat>();
242 return ConstructorSplat::Make(*fContext, pos,
243 *ctor.type().clone(symbolTableForExpression),
244 expr(ctor.argument()));
245 }
246 case Expression::Kind::kConstructorStruct: {
247 const ConstructorStruct& ctor = expression.as<ConstructorStruct>();
248 return ConstructorStruct::Make(*fContext, pos,
249 *ctor.type().clone(symbolTableForExpression),
250 argList(ctor.arguments()));
251 }
252 case Expression::Kind::kFieldAccess: {
253 const FieldAccess& f = expression.as<FieldAccess>();
254 return FieldAccess::Make(*fContext, pos, expr(f.base()), f.fieldIndex(), f.ownerKind());
255 }
256 case Expression::Kind::kFunctionCall: {
257 const FunctionCall& funcCall = expression.as<FunctionCall>();
258 return FunctionCall::Make(*fContext,
259 pos,
260 funcCall.type().clone(symbolTableForExpression),
261 funcCall.function(),
262 argList(funcCall.arguments()));
263 }
264 case Expression::Kind::kFunctionReference:
265 return expression.clone();
266 case Expression::Kind::kIndex: {
267 const IndexExpression& idx = expression.as<IndexExpression>();
268 return IndexExpression::Make(*fContext, pos, expr(idx.base()), expr(idx.index()));
269 }
270 case Expression::Kind::kMethodReference:
271 return expression.clone();
272 case Expression::Kind::kPrefix: {
273 const PrefixExpression& p = expression.as<PrefixExpression>();
274 return PrefixExpression::Make(*fContext, pos, p.getOperator(), expr(p.operand()));
275 }
276 case Expression::Kind::kPostfix: {
277 const PostfixExpression& p = expression.as<PostfixExpression>();
278 return PostfixExpression::Make(*fContext, pos, expr(p.operand()), p.getOperator());
279 }
280 case Expression::Kind::kSetting: {
281 const Setting& s = expression.as<Setting>();
282 return Setting::Convert(*fContext, pos, s.name());
283 }
284 case Expression::Kind::kSwizzle: {
285 const Swizzle& s = expression.as<Swizzle>();
286 return Swizzle::Make(*fContext, pos, expr(s.base()), s.components());
287 }
288 case Expression::Kind::kTernary: {
289 const TernaryExpression& t = expression.as<TernaryExpression>();
290 return TernaryExpression::Make(*fContext, pos, expr(t.test()),
291 expr(t.ifTrue()), expr(t.ifFalse()));
292 }
293 case Expression::Kind::kTypeReference:
294 return expression.clone();
295 case Expression::Kind::kVariableReference: {
296 const VariableReference& v = expression.as<VariableReference>();
297 std::unique_ptr<Expression>* remap = varMap->find(v.variable());
298 if (remap) {
299 return clone_with_ref_kind(**remap, v.refKind());
300 }
301 return expression.clone();
302 }
303 default:
304 SkASSERT(false);
305 return nullptr;
306 }
307 }
308
inlineStatement(Position pos,VariableRewriteMap * varMap,SymbolTable * symbolTableForStatement,std::unique_ptr<Expression> * resultExpr,Analysis::ReturnComplexity returnComplexity,const Statement & statement,const ProgramUsage & usage,bool isBuiltinCode)309 std::unique_ptr<Statement> Inliner::inlineStatement(Position pos,
310 VariableRewriteMap* varMap,
311 SymbolTable* symbolTableForStatement,
312 std::unique_ptr<Expression>* resultExpr,
313 Analysis::ReturnComplexity returnComplexity,
314 const Statement& statement,
315 const ProgramUsage& usage,
316 bool isBuiltinCode) {
317 auto stmt = [&](const std::unique_ptr<Statement>& s) -> std::unique_ptr<Statement> {
318 if (s) {
319 return this->inlineStatement(pos, varMap, symbolTableForStatement, resultExpr,
320 returnComplexity, *s, usage, isBuiltinCode);
321 }
322 return nullptr;
323 };
324 auto blockStmts = [&](const Block& block) {
325 StatementArray result;
326 result.reserve_back(block.children().size());
327 for (const std::unique_ptr<Statement>& child : block.children()) {
328 result.push_back(stmt(child));
329 }
330 return result;
331 };
332 auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> {
333 if (e) {
334 return this->inlineExpression(pos, varMap, symbolTableForStatement, *e);
335 }
336 return nullptr;
337 };
338 auto variableModifiers = [&](const Variable& variable,
339 const Expression* initialValue) -> const Modifiers* {
340 return Transform::AddConstToVarModifiers(*fContext, variable, initialValue, &usage);
341 };
342
343 ++fInlinedStatementCounter;
344
345 switch (statement.kind()) {
346 case Statement::Kind::kBlock: {
347 const Block& b = statement.as<Block>();
348 return Block::Make(pos, blockStmts(b), b.blockKind(),
349 SymbolTable::WrapIfBuiltin(b.symbolTable()));
350 }
351
352 case Statement::Kind::kBreak:
353 case Statement::Kind::kContinue:
354 case Statement::Kind::kDiscard:
355 return statement.clone();
356
357 case Statement::Kind::kDo: {
358 const DoStatement& d = statement.as<DoStatement>();
359 return DoStatement::Make(*fContext, pos, stmt(d.statement()), expr(d.test()));
360 }
361 case Statement::Kind::kExpression: {
362 const ExpressionStatement& e = statement.as<ExpressionStatement>();
363 return ExpressionStatement::Make(*fContext, expr(e.expression()));
364 }
365 case Statement::Kind::kFor: {
366 const ForStatement& f = statement.as<ForStatement>();
367 // need to ensure initializer is evaluated first so that we've already remapped its
368 // declarations by the time we evaluate test & next
369 std::unique_ptr<Statement> initializer = stmt(f.initializer());
370
371 std::unique_ptr<LoopUnrollInfo> unrollInfo;
372 if (f.unrollInfo()) {
373 // The for loop's unroll-info points to the Variable in the initializer as the
374 // index. This variable has been rewritten into a clone by the inliner, so we need
375 // to update the loop-unroll info to point to the clone.
376 unrollInfo = std::make_unique<LoopUnrollInfo>(*f.unrollInfo());
377 unrollInfo->fIndex = RemapVariable(unrollInfo->fIndex, varMap);
378 }
379 return ForStatement::Make(*fContext, pos, ForLoopPositions{}, std::move(initializer),
380 expr(f.test()), expr(f.next()), stmt(f.statement()),
381 std::move(unrollInfo),
382 SymbolTable::WrapIfBuiltin(f.symbols()));
383 }
384 case Statement::Kind::kIf: {
385 const IfStatement& i = statement.as<IfStatement>();
386 return IfStatement::Make(*fContext, pos, expr(i.test()),
387 stmt(i.ifTrue()), stmt(i.ifFalse()));
388 }
389 case Statement::Kind::kNop:
390 return statement.clone();
391
392 case Statement::Kind::kReturn: {
393 const ReturnStatement& r = statement.as<ReturnStatement>();
394 if (!r.expression()) {
395 // This function doesn't return a value. We won't inline functions with early
396 // returns, so a return statement is a no-op and can be treated as such.
397 return Nop::Make();
398 }
399
400 // If a function only contains a single return, and it doesn't reference variables from
401 // inside an Block's scope, we don't need to store the result in a variable at all. Just
402 // replace the function-call expression with the function's return expression.
403 SkASSERT(resultExpr);
404 if (returnComplexity <= Analysis::ReturnComplexity::kSingleSafeReturn) {
405 *resultExpr = expr(r.expression());
406 return Nop::Make();
407 }
408
409 // For more complex functions, we assign their result into a variable. We refuse to
410 // inline anything with early returns, so this should be safe to do; that is, on this
411 // control path, this is the last statement that will occur.
412 SkASSERT(*resultExpr);
413 return ExpressionStatement::Make(
414 *fContext,
415 BinaryExpression::Make(
416 *fContext,
417 pos,
418 clone_with_ref_kind(**resultExpr, VariableRefKind::kWrite),
419 Operator::Kind::EQ,
420 expr(r.expression())));
421 }
422 case Statement::Kind::kSwitch: {
423 const SwitchStatement& ss = statement.as<SwitchStatement>();
424 StatementArray cases;
425 cases.reserve_back(ss.cases().size());
426 for (const std::unique_ptr<Statement>& switchCaseStmt : ss.cases()) {
427 const SwitchCase& sc = switchCaseStmt->as<SwitchCase>();
428 if (sc.isDefault()) {
429 cases.push_back(SwitchCase::MakeDefault(pos, stmt(sc.statement())));
430 } else {
431 cases.push_back(SwitchCase::Make(pos, sc.value(), stmt(sc.statement())));
432 }
433 }
434 return SwitchStatement::Make(*fContext, pos, expr(ss.value()),
435 std::move(cases), SymbolTable::WrapIfBuiltin(ss.symbols()));
436 }
437 case Statement::Kind::kVarDeclaration: {
438 const VarDeclaration& decl = statement.as<VarDeclaration>();
439 std::unique_ptr<Expression> initialValue = expr(decl.value());
440 const Variable* variable = decl.var();
441
442 // We assign unique names to inlined variables--scopes hide most of the problems in this
443 // regard, but see `InlinerAvoidsVariableNameOverlap` for a counterexample where unique
444 // names are important.
445 const std::string* name = symbolTableForStatement->takeOwnershipOfString(
446 fMangler.uniqueName(variable->name(), symbolTableForStatement));
447 auto clonedVar = std::make_unique<Variable>(
448 pos,
449 variable->modifiersPosition(),
450 variableModifiers(*variable, initialValue.get()),
451 name->c_str(),
452 variable->type().clone(symbolTableForStatement),
453 isBuiltinCode,
454 variable->storage());
455 varMap->set(variable, VariableReference::Make(pos, clonedVar.get()));
456 auto result = VarDeclaration::Make(*fContext,
457 clonedVar.get(),
458 decl.baseType().clone(symbolTableForStatement),
459 decl.arraySize(),
460 std::move(initialValue));
461 symbolTableForStatement->takeOwnershipOfSymbol(std::move(clonedVar));
462 return result;
463 }
464 default:
465 SkASSERT(false);
466 return nullptr;
467 }
468 }
469
inlineCall(const FunctionCall & call,std::shared_ptr<SymbolTable> symbolTable,const ProgramUsage & usage,const FunctionDeclaration * caller)470 Inliner::InlinedCall Inliner::inlineCall(const FunctionCall& call,
471 std::shared_ptr<SymbolTable> symbolTable,
472 const ProgramUsage& usage,
473 const FunctionDeclaration* caller) {
474 using ScratchVariable = Variable::ScratchVariable;
475
476 // Inlining is more complicated here than in a typical compiler, because we have to have a
477 // high-level IR and can't just drop statements into the middle of an expression or even use
478 // gotos.
479 //
480 // Since we can't insert statements into an expression, we run the inline function as extra
481 // statements before the statement we're currently processing, relying on a lack of execution
482 // order guarantees. Since we can't use gotos (which are normally used to replace return
483 // statements), we wrap the whole function in a loop and use break statements to jump to the
484 // end.
485 SkASSERT(fContext);
486 SkASSERT(this->isSafeToInline(call.function().definition(), usage));
487
488 const ExpressionArray& arguments = call.arguments();
489 const Position pos = call.fPosition;
490 const FunctionDefinition& function = *call.function().definition();
491 const Block& body = function.body()->as<Block>();
492 const Analysis::ReturnComplexity returnComplexity = Analysis::GetReturnComplexity(function);
493
494 StatementArray inlineStatements;
495 int expectedStmtCount = 1 + // Result variable
496 arguments.size() + // Function argument temp-vars
497 body.children().size(); // Inlined code
498
499 inlineStatements.reserve_back(expectedStmtCount);
500
501 std::unique_ptr<Expression> resultExpr;
502 if (returnComplexity > Analysis::ReturnComplexity::kSingleSafeReturn &&
503 !function.declaration().returnType().isVoid()) {
504 // Create a variable to hold the result in the extra statements. We don't need to do this
505 // for void-return functions, or in cases that are simple enough that we can just replace
506 // the function-call node with the result expression.
507 ScratchVariable var = Variable::MakeScratchVariable(*fContext,
508 fMangler,
509 function.declaration().name(),
510 &function.declaration().returnType(),
511 Modifiers{},
512 symbolTable.get(),
513 /*initialValue=*/nullptr);
514 inlineStatements.push_back(std::move(var.fVarDecl));
515 resultExpr = VariableReference::Make(Position(), var.fVarSymbol);
516 }
517
518 // Create variables in the extra statements to hold the arguments, and assign the arguments to
519 // them.
520 VariableRewriteMap varMap;
521 for (int i = 0; i < arguments.size(); ++i) {
522 // If the parameter isn't written to within the inline function ...
523 const Expression* arg = arguments[i].get();
524 const Variable* param = function.declaration().parameters()[i];
525 const ProgramUsage::VariableCounts& paramUsage = usage.get(*param);
526 if (!paramUsage.fWrite) {
527 // ... and can be inlined trivially (e.g. a swizzle, or a constant array index),
528 // or any expression without side effects that is only accessed at most once...
529 if ((paramUsage.fRead > 1) ? Analysis::IsTrivialExpression(*arg)
530 : !Analysis::HasSideEffects(*arg)) {
531 // ... we don't need to copy it at all! We can just use the existing expression.
532 varMap.set(param, arg->clone());
533 continue;
534 }
535 }
536 ScratchVariable var = Variable::MakeScratchVariable(*fContext,
537 fMangler,
538 param->name(),
539 &arg->type(),
540 param->modifiers(),
541 symbolTable.get(),
542 arg->clone());
543 inlineStatements.push_back(std::move(var.fVarDecl));
544 varMap.set(param, VariableReference::Make(Position(), var.fVarSymbol));
545 }
546
547 for (const std::unique_ptr<Statement>& stmt : body.children()) {
548 inlineStatements.push_back(this->inlineStatement(pos, &varMap, symbolTable.get(),
549 &resultExpr, returnComplexity, *stmt,
550 usage, caller->isBuiltin()));
551 }
552
553 SkASSERT(inlineStatements.size() <= expectedStmtCount);
554
555 // Wrap all of the generated statements in a block. We need a real Block here, because we need
556 // to add another child statement to the Block later.
557 InlinedCall inlinedCall;
558 inlinedCall.fInlinedBody = Block::MakeBlock(pos, std::move(inlineStatements),
559 Block::Kind::kUnbracedBlock);
560 if (resultExpr) {
561 // Return our result expression as-is.
562 inlinedCall.fReplacementExpr = std::move(resultExpr);
563 } else if (function.declaration().returnType().isVoid()) {
564 // It's a void function, so it doesn't actually result in anything, but we have to return
565 // something non-null as a standin.
566 inlinedCall.fReplacementExpr = Literal::MakeBool(*fContext, pos, /*value=*/false);
567 } else {
568 // It's a non-void function, but it never created a result expression--that is, it never
569 // returned anything on any path! This should have been detected in the function finalizer.
570 // Still, discard our output and generate an error.
571 SkDEBUGFAIL("inliner found non-void function that fails to return a value on any path");
572 fContext->fErrors->error(function.fPosition, "inliner found non-void function '" +
573 std::string(function.declaration().name()) +
574 "' that fails to return a value on any path");
575 inlinedCall = {};
576 }
577
578 return inlinedCall;
579 }
580
isSafeToInline(const FunctionDefinition * functionDef,const ProgramUsage & usage)581 bool Inliner::isSafeToInline(const FunctionDefinition* functionDef, const ProgramUsage& usage) {
582 // A threshold of zero indicates that the inliner is completely disabled, so we can just return.
583 if (this->settings().fInlineThreshold <= 0) {
584 return false;
585 }
586
587 // Enforce a limit on inlining to avoid pathological cases. (inliner/ExponentialGrowth.sksl)
588 if (fInlinedStatementCounter >= kInlinedStatementLimit) {
589 return false;
590 }
591
592 if (functionDef == nullptr) {
593 // Can't inline something if we don't actually have its definition.
594 return false;
595 }
596
597 if (functionDef->declaration().modifiers().fFlags & Modifiers::kNoInline_Flag) {
598 // Refuse to inline functions decorated with `noinline`.
599 return false;
600 }
601
602 // We don't allow inlining a function with out parameters that are written to.
603 // (See skia:11326 for rationale.)
604 for (const Variable* param : functionDef->declaration().parameters()) {
605 if (param->modifiers().fFlags & Modifiers::Flag::kOut_Flag) {
606 ProgramUsage::VariableCounts counts = usage.get(*param);
607 if (counts.fWrite > 0) {
608 return false;
609 }
610 }
611 }
612
613 // We don't have a mechanism to simulate early returns, so we can't inline if there is one.
614 return Analysis::GetReturnComplexity(*functionDef) < Analysis::ReturnComplexity::kEarlyReturns;
615 }
616
617 // A candidate function for inlining, containing everything that `inlineCall` needs.
618 struct InlineCandidate {
619 std::shared_ptr<SymbolTable> fSymbols; // the SymbolTable of the candidate
620 std::unique_ptr<Statement>* fParentStmt; // the parent Statement of the enclosing stmt
621 std::unique_ptr<Statement>* fEnclosingStmt; // the Statement containing the candidate
622 std::unique_ptr<Expression>* fCandidateExpr; // the candidate FunctionCall to be inlined
623 FunctionDefinition* fEnclosingFunction; // the Function containing the candidate
624 };
625
626 struct InlineCandidateList {
627 std::vector<InlineCandidate> fCandidates;
628 };
629
630 class InlineCandidateAnalyzer {
631 public:
632 // A list of all the inlining candidates we found during analysis.
633 InlineCandidateList* fCandidateList;
634
635 // A stack of the symbol tables; since most nodes don't have one, expected to be shallower than
636 // the enclosing-statement stack.
637 std::vector<std::shared_ptr<SymbolTable>> fSymbolTableStack;
638 // A stack of "enclosing" statements--these would be suitable for the inliner to use for adding
639 // new instructions. Not all statements are suitable (e.g. a for-loop's initializer). The
640 // inliner might replace a statement with a block containing the statement.
641 std::vector<std::unique_ptr<Statement>*> fEnclosingStmtStack;
642 // The function that we're currently processing (i.e. inlining into).
643 FunctionDefinition* fEnclosingFunction = nullptr;
644
visit(const std::vector<std::unique_ptr<ProgramElement>> & elements,std::shared_ptr<SymbolTable> symbols,InlineCandidateList * candidateList)645 void visit(const std::vector<std::unique_ptr<ProgramElement>>& elements,
646 std::shared_ptr<SymbolTable> symbols,
647 InlineCandidateList* candidateList) {
648 fCandidateList = candidateList;
649 fSymbolTableStack.push_back(symbols);
650
651 for (const std::unique_ptr<ProgramElement>& pe : elements) {
652 this->visitProgramElement(pe.get());
653 }
654
655 fSymbolTableStack.pop_back();
656 fCandidateList = nullptr;
657 }
658
visitProgramElement(ProgramElement * pe)659 void visitProgramElement(ProgramElement* pe) {
660 switch (pe->kind()) {
661 case ProgramElement::Kind::kFunction: {
662 FunctionDefinition& funcDef = pe->as<FunctionDefinition>();
663 fEnclosingFunction = &funcDef;
664 this->visitStatement(&funcDef.body());
665 break;
666 }
667 default:
668 // The inliner can't operate outside of a function's scope.
669 break;
670 }
671 }
672
visitStatement(std::unique_ptr<Statement> * stmt,bool isViableAsEnclosingStatement=true)673 void visitStatement(std::unique_ptr<Statement>* stmt,
674 bool isViableAsEnclosingStatement = true) {
675 if (!*stmt) {
676 return;
677 }
678
679 Analysis::SymbolTableStackBuilder scopedStackBuilder(stmt->get(), &fSymbolTableStack);
680 size_t oldEnclosingStmtStackSize = fEnclosingStmtStack.size();
681
682 if (isViableAsEnclosingStatement) {
683 fEnclosingStmtStack.push_back(stmt);
684 }
685
686 switch ((*stmt)->kind()) {
687 case Statement::Kind::kBreak:
688 case Statement::Kind::kContinue:
689 case Statement::Kind::kDiscard:
690 case Statement::Kind::kNop:
691 break;
692
693 case Statement::Kind::kBlock: {
694 Block& block = (*stmt)->as<Block>();
695 for (std::unique_ptr<Statement>& blockStmt : block.children()) {
696 this->visitStatement(&blockStmt);
697 }
698 break;
699 }
700 case Statement::Kind::kDo: {
701 DoStatement& doStmt = (*stmt)->as<DoStatement>();
702 // The loop body is a candidate for inlining.
703 this->visitStatement(&doStmt.statement());
704 // The inliner isn't smart enough to inline the test-expression for a do-while
705 // loop at this time. There are two limitations:
706 // - We would need to insert the inlined-body block at the very end of the do-
707 // statement's inner fStatement. We don't support that today, but it's doable.
708 // - We cannot inline the test expression if the loop uses `continue` anywhere; that
709 // would skip over the inlined block that evaluates the test expression. There
710 // isn't a good fix for this--any workaround would be more complex than the cost
711 // of a function call. However, loops that don't use `continue` would still be
712 // viable candidates for inlining.
713 break;
714 }
715 case Statement::Kind::kExpression: {
716 ExpressionStatement& expr = (*stmt)->as<ExpressionStatement>();
717 this->visitExpression(&expr.expression());
718 break;
719 }
720 case Statement::Kind::kFor: {
721 ForStatement& forStmt = (*stmt)->as<ForStatement>();
722 // The initializer and loop body are candidates for inlining.
723 this->visitStatement(&forStmt.initializer(),
724 /*isViableAsEnclosingStatement=*/false);
725 this->visitStatement(&forStmt.statement());
726
727 // The inliner isn't smart enough to inline the test- or increment-expressions
728 // of a for loop loop at this time. There are a handful of limitations:
729 // - We would need to insert the test-expression block at the very beginning of the
730 // for-loop's inner fStatement, and the increment-expression block at the very
731 // end. We don't support that today, but it's doable.
732 // - The for-loop's built-in test-expression would need to be dropped entirely,
733 // and the loop would be halted via a break statement at the end of the inlined
734 // test-expression. This is again something we don't support today, but it could
735 // be implemented.
736 // - We cannot inline the increment-expression if the loop uses `continue` anywhere;
737 // that would skip over the inlined block that evaluates the increment expression.
738 // There isn't a good fix for this--any workaround would be more complex than the
739 // cost of a function call. However, loops that don't use `continue` would still
740 // be viable candidates for increment-expression inlining.
741 break;
742 }
743 case Statement::Kind::kIf: {
744 IfStatement& ifStmt = (*stmt)->as<IfStatement>();
745 this->visitExpression(&ifStmt.test());
746 this->visitStatement(&ifStmt.ifTrue());
747 this->visitStatement(&ifStmt.ifFalse());
748 break;
749 }
750 case Statement::Kind::kReturn: {
751 ReturnStatement& returnStmt = (*stmt)->as<ReturnStatement>();
752 this->visitExpression(&returnStmt.expression());
753 break;
754 }
755 case Statement::Kind::kSwitch: {
756 SwitchStatement& switchStmt = (*stmt)->as<SwitchStatement>();
757 this->visitExpression(&switchStmt.value());
758 for (const std::unique_ptr<Statement>& switchCase : switchStmt.cases()) {
759 // The switch-case's fValue cannot be a FunctionCall; skip it.
760 this->visitStatement(&switchCase->as<SwitchCase>().statement());
761 }
762 break;
763 }
764 case Statement::Kind::kVarDeclaration: {
765 VarDeclaration& varDeclStmt = (*stmt)->as<VarDeclaration>();
766 // Don't need to scan the declaration's sizes; those are always IntLiterals.
767 this->visitExpression(&varDeclStmt.value());
768 break;
769 }
770 default:
771 SkUNREACHABLE;
772 }
773
774 // Pop our symbol and enclosing-statement stacks.
775 fEnclosingStmtStack.resize(oldEnclosingStmtStackSize);
776 }
777
visitExpression(std::unique_ptr<Expression> * expr)778 void visitExpression(std::unique_ptr<Expression>* expr) {
779 if (!*expr) {
780 return;
781 }
782
783 switch ((*expr)->kind()) {
784 case Expression::Kind::kFieldAccess:
785 case Expression::Kind::kFunctionReference:
786 case Expression::Kind::kLiteral:
787 case Expression::Kind::kMethodReference:
788 case Expression::Kind::kSetting:
789 case Expression::Kind::kTypeReference:
790 case Expression::Kind::kVariableReference:
791 // Nothing to scan here.
792 break;
793
794 case Expression::Kind::kBinary: {
795 BinaryExpression& binaryExpr = (*expr)->as<BinaryExpression>();
796 this->visitExpression(&binaryExpr.left());
797
798 // Logical-and and logical-or binary expressions do not inline the right side,
799 // because that would invalidate short-circuiting. That is, when evaluating
800 // expressions like these:
801 // (false && x()) // always false
802 // (true || y()) // always true
803 // It is illegal for side-effects from x() or y() to occur. The simplest way to
804 // enforce that rule is to avoid inlining the right side entirely. However, it is
805 // safe for other types of binary expression to inline both sides.
806 Operator op = binaryExpr.getOperator();
807 bool shortCircuitable = (op.kind() == Operator::Kind::LOGICALAND ||
808 op.kind() == Operator::Kind::LOGICALOR);
809 if (!shortCircuitable) {
810 this->visitExpression(&binaryExpr.right());
811 }
812 break;
813 }
814 case Expression::Kind::kChildCall: {
815 ChildCall& childCallExpr = (*expr)->as<ChildCall>();
816 for (std::unique_ptr<Expression>& arg : childCallExpr.arguments()) {
817 this->visitExpression(&arg);
818 }
819 break;
820 }
821 case Expression::Kind::kConstructorArray:
822 case Expression::Kind::kConstructorArrayCast:
823 case Expression::Kind::kConstructorCompound:
824 case Expression::Kind::kConstructorCompoundCast:
825 case Expression::Kind::kConstructorDiagonalMatrix:
826 case Expression::Kind::kConstructorMatrixResize:
827 case Expression::Kind::kConstructorScalarCast:
828 case Expression::Kind::kConstructorSplat:
829 case Expression::Kind::kConstructorStruct: {
830 AnyConstructor& constructorExpr = (*expr)->asAnyConstructor();
831 for (std::unique_ptr<Expression>& arg : constructorExpr.argumentSpan()) {
832 this->visitExpression(&arg);
833 }
834 break;
835 }
836 case Expression::Kind::kFunctionCall: {
837 FunctionCall& funcCallExpr = (*expr)->as<FunctionCall>();
838 for (std::unique_ptr<Expression>& arg : funcCallExpr.arguments()) {
839 this->visitExpression(&arg);
840 }
841 this->addInlineCandidate(expr);
842 break;
843 }
844 case Expression::Kind::kIndex: {
845 IndexExpression& indexExpr = (*expr)->as<IndexExpression>();
846 this->visitExpression(&indexExpr.base());
847 this->visitExpression(&indexExpr.index());
848 break;
849 }
850 case Expression::Kind::kPostfix: {
851 PostfixExpression& postfixExpr = (*expr)->as<PostfixExpression>();
852 this->visitExpression(&postfixExpr.operand());
853 break;
854 }
855 case Expression::Kind::kPrefix: {
856 PrefixExpression& prefixExpr = (*expr)->as<PrefixExpression>();
857 this->visitExpression(&prefixExpr.operand());
858 break;
859 }
860 case Expression::Kind::kSwizzle: {
861 Swizzle& swizzleExpr = (*expr)->as<Swizzle>();
862 this->visitExpression(&swizzleExpr.base());
863 break;
864 }
865 case Expression::Kind::kTernary: {
866 TernaryExpression& ternaryExpr = (*expr)->as<TernaryExpression>();
867 // The test expression is a candidate for inlining.
868 this->visitExpression(&ternaryExpr.test());
869 // The true- and false-expressions cannot be inlined, because we are only allowed to
870 // evaluate one side.
871 break;
872 }
873 default:
874 SkUNREACHABLE;
875 }
876 }
877
addInlineCandidate(std::unique_ptr<Expression> * candidate)878 void addInlineCandidate(std::unique_ptr<Expression>* candidate) {
879 fCandidateList->fCandidates.push_back(
880 InlineCandidate{fSymbolTableStack.back(),
881 find_parent_statement(fEnclosingStmtStack),
882 fEnclosingStmtStack.back(),
883 candidate,
884 fEnclosingFunction});
885 }
886 };
887
candidate_func(const InlineCandidate & candidate)888 static const FunctionDeclaration& candidate_func(const InlineCandidate& candidate) {
889 return (*candidate.fCandidateExpr)->as<FunctionCall>().function();
890 }
891
candidateCanBeInlined(const InlineCandidate & candidate,const ProgramUsage & usage,InlinabilityCache * cache)892 bool Inliner::candidateCanBeInlined(const InlineCandidate& candidate,
893 const ProgramUsage& usage,
894 InlinabilityCache* cache) {
895 const FunctionDeclaration& funcDecl = candidate_func(candidate);
896 if (const bool* cachedInlinability = cache->find(&funcDecl)) {
897 return *cachedInlinability;
898 }
899 bool inlinability = this->isSafeToInline(funcDecl.definition(), usage);
900 cache->set(&funcDecl, inlinability);
901 return inlinability;
902 }
903
getFunctionSize(const FunctionDeclaration & funcDecl,FunctionSizeCache * cache)904 int Inliner::getFunctionSize(const FunctionDeclaration& funcDecl, FunctionSizeCache* cache) {
905 if (const int* cachedSize = cache->find(&funcDecl)) {
906 return *cachedSize;
907 }
908 int size = Analysis::NodeCountUpToLimit(*funcDecl.definition(),
909 this->settings().fInlineThreshold);
910 cache->set(&funcDecl, size);
911 return size;
912 }
913
buildCandidateList(const std::vector<std::unique_ptr<ProgramElement>> & elements,std::shared_ptr<SymbolTable> symbols,ProgramUsage * usage,InlineCandidateList * candidateList)914 void Inliner::buildCandidateList(const std::vector<std::unique_ptr<ProgramElement>>& elements,
915 std::shared_ptr<SymbolTable> symbols, ProgramUsage* usage,
916 InlineCandidateList* candidateList) {
917 // This is structured much like a ProgramVisitor, but does not actually use ProgramVisitor.
918 // The analyzer needs to keep track of the `unique_ptr<T>*` of statements and expressions so
919 // that they can later be replaced, and ProgramVisitor does not provide this; it only provides a
920 // `const T&`.
921 InlineCandidateAnalyzer analyzer;
922 analyzer.visit(elements, symbols, candidateList);
923
924 // Early out if there are no inlining candidates.
925 std::vector<InlineCandidate>& candidates = candidateList->fCandidates;
926 if (candidates.empty()) {
927 return;
928 }
929
930 // Remove candidates that are not safe to inline.
931 InlinabilityCache cache;
932 candidates.erase(std::remove_if(candidates.begin(),
933 candidates.end(),
934 [&](const InlineCandidate& candidate) {
935 return !this->candidateCanBeInlined(
936 candidate, *usage, &cache);
937 }),
938 candidates.end());
939
940 // If the inline threshold is unlimited, or if we have no candidates left, our candidate list is
941 // complete.
942 if (this->settings().fInlineThreshold == INT_MAX || candidates.empty()) {
943 return;
944 }
945
946 // Remove candidates on a per-function basis if the effect of inlining would be to make more
947 // than `inlineThreshold` nodes. (i.e. if Func() would be inlined six times and its size is
948 // 10 nodes, it should be inlined if the inlineThreshold is 60 or higher.)
949 FunctionSizeCache functionSizeCache;
950 FunctionSizeCache candidateTotalCost;
951 for (InlineCandidate& candidate : candidates) {
952 const FunctionDeclaration& fnDecl = candidate_func(candidate);
953 candidateTotalCost[&fnDecl] += this->getFunctionSize(fnDecl, &functionSizeCache);
954 }
955
956 candidates.erase(std::remove_if(candidates.begin(), candidates.end(),
957 [&](const InlineCandidate& candidate) {
958 const FunctionDeclaration& fnDecl = candidate_func(candidate);
959 if (fnDecl.modifiers().fFlags & Modifiers::kInline_Flag) {
960 // Functions marked `inline` ignore size limitations.
961 return false;
962 }
963 if (usage->get(fnDecl) == 1) {
964 // If a function is only used once, it's cost-free to inline.
965 return false;
966 }
967 if (candidateTotalCost[&fnDecl] <= this->settings().fInlineThreshold) {
968 // We won't exceed the inline threshold by inlining this.
969 return false;
970 }
971 // Inlining this function will add too many IRNodes.
972 return true;
973 }),
974 candidates.end());
975 }
976
analyze(const std::vector<std::unique_ptr<ProgramElement>> & elements,std::shared_ptr<SymbolTable> symbols,ProgramUsage * usage)977 bool Inliner::analyze(const std::vector<std::unique_ptr<ProgramElement>>& elements,
978 std::shared_ptr<SymbolTable> symbols,
979 ProgramUsage* usage) {
980 // A threshold of zero indicates that the inliner is completely disabled, so we can just return.
981 if (this->settings().fInlineThreshold <= 0) {
982 return false;
983 }
984
985 // Enforce a limit on inlining to avoid pathological cases. (inliner/ExponentialGrowth.sksl)
986 if (fInlinedStatementCounter >= kInlinedStatementLimit) {
987 return false;
988 }
989
990 InlineCandidateList candidateList;
991 this->buildCandidateList(elements, symbols, usage, &candidateList);
992
993 // Inline the candidates where we've determined that it's safe to do so.
994 using StatementRemappingTable = SkTHashMap<std::unique_ptr<Statement>*,
995 std::unique_ptr<Statement>*>;
996 StatementRemappingTable statementRemappingTable;
997
998 bool madeChanges = false;
999 for (const InlineCandidate& candidate : candidateList.fCandidates) {
1000 const FunctionCall& funcCall = (*candidate.fCandidateExpr)->as<FunctionCall>();
1001
1002 // Convert the function call to its inlined equivalent.
1003 InlinedCall inlinedCall = this->inlineCall(funcCall, candidate.fSymbols, *usage,
1004 &candidate.fEnclosingFunction->declaration());
1005
1006 // Stop if an error was detected during the inlining process.
1007 if (!inlinedCall.fInlinedBody && !inlinedCall.fReplacementExpr) {
1008 break;
1009 }
1010
1011 // Ensure that the inlined body has a scope if it needs one.
1012 this->ensureScopedBlocks(inlinedCall.fInlinedBody.get(), candidate.fParentStmt->get());
1013
1014 // Add references within the inlined body
1015 usage->add(inlinedCall.fInlinedBody.get());
1016
1017 // Look up the enclosing statement; remap it if necessary.
1018 std::unique_ptr<Statement>* enclosingStmt = candidate.fEnclosingStmt;
1019 for (;;) {
1020 std::unique_ptr<Statement>** remappedStmt = statementRemappingTable.find(enclosingStmt);
1021 if (!remappedStmt) {
1022 break;
1023 }
1024 enclosingStmt = *remappedStmt;
1025 }
1026
1027 // Move the enclosing statement to the end of the unscoped Block containing the inlined
1028 // function, then replace the enclosing statement with that Block.
1029 // Before:
1030 // fInlinedBody = Block{ stmt1, stmt2, stmt3 }
1031 // fEnclosingStmt = stmt4
1032 // After:
1033 // fInlinedBody = null
1034 // fEnclosingStmt = Block{ stmt1, stmt2, stmt3, stmt4 }
1035 inlinedCall.fInlinedBody->children().push_back(std::move(*enclosingStmt));
1036 *enclosingStmt = std::move(inlinedCall.fInlinedBody);
1037
1038 // Replace the candidate function call with our replacement expression.
1039 usage->remove(candidate.fCandidateExpr->get());
1040 usage->add(inlinedCall.fReplacementExpr.get());
1041 *candidate.fCandidateExpr = std::move(inlinedCall.fReplacementExpr);
1042 madeChanges = true;
1043
1044 // If anything else pointed at our enclosing statement, it's now pointing at a Block
1045 // containing many other statements as well. Maintain a fix-up table to account for this.
1046 statementRemappingTable.set(enclosingStmt,&(*enclosingStmt)->as<Block>().children().back());
1047
1048 // Stop inlining if we've reached our hard cap on new statements.
1049 if (fInlinedStatementCounter >= kInlinedStatementLimit) {
1050 break;
1051 }
1052
1053 // Note that nothing was destroyed except for the FunctionCall. All other nodes should
1054 // remain valid.
1055 }
1056
1057 return madeChanges;
1058 }
1059
1060 } // namespace SkSL
1061
1062 #endif // SK_ENABLE_OPTIMIZE_SIZE
1063