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 #ifndef SKSL_INLINER
9 #define SKSL_INLINER
10 
11 #include <memory>
12 #include <unordered_map>
13 
14 #include "src/sksl/SkSLMangler.h"
15 #include "src/sksl/ir/SkSLProgram.h"
16 #include "src/sksl/ir/SkSLVariableReference.h"
17 
18 namespace SkSL {
19 
20 class Block;
21 class Context;
22 class Expression;
23 class FunctionCall;
24 class FunctionDefinition;
25 struct InlineCandidate;
26 struct InlineCandidateList;
27 class ModifiersPool;
28 class Statement;
29 class SymbolTable;
30 class Variable;
31 
32 /**
33  * Converts a FunctionCall in the IR to a set of statements to be injected ahead of the function
34  * call, and a replacement expression. Can also detect cases where inlining isn't cleanly possible
35  * (e.g. return statements nested inside of a loop construct). The inliner isn't able to guarantee
36  * identical-to-GLSL execution order if the inlined function has visible side effects.
37  */
38 class Inliner {
39 public:
Inliner(const Context * context)40     Inliner(const Context* context) : fContext(context) {}
41 
42     void reset();
43 
44     /** Inlines any eligible functions that are found. Returns true if any changes are made. */
45     bool analyze(const std::vector<std::unique_ptr<ProgramElement>>& elements,
46                  std::shared_ptr<SymbolTable> symbols,
47                  ProgramUsage* usage);
48 
49 private:
50     using VariableRewriteMap = std::unordered_map<const Variable*, std::unique_ptr<Expression>>;
51 
52     enum class ReturnComplexity {
53         kSingleSafeReturn,
54         kScopedReturns,
55         kEarlyReturns,
56     };
57 
settings()58     const Program::Settings& settings() const { return fContext->fConfig->fSettings; }
59 
60     void buildCandidateList(const std::vector<std::unique_ptr<ProgramElement>>& elements,
61                             std::shared_ptr<SymbolTable> symbols, ProgramUsage* usage,
62                             InlineCandidateList* candidateList);
63 
64     std::unique_ptr<Expression> inlineExpression(int offset,
65                                                  VariableRewriteMap* varMap,
66                                                  SymbolTable* symbolTableForExpression,
67                                                  const Expression& expression);
68     std::unique_ptr<Statement> inlineStatement(int offset,
69                                                VariableRewriteMap* varMap,
70                                                SymbolTable* symbolTableForStatement,
71                                                std::unique_ptr<Expression>* resultExpr,
72                                                ReturnComplexity returnComplexity,
73                                                const Statement& statement,
74                                                bool isBuiltinCode);
75 
76     /** Determines if a given function has multiple and/or early returns. */
77     static ReturnComplexity GetReturnComplexity(const FunctionDefinition& funcDef);
78 
79     using InlinabilityCache = std::unordered_map<const FunctionDeclaration*, bool>;
80     bool candidateCanBeInlined(const InlineCandidate& candidate, InlinabilityCache* cache);
81 
82     using FunctionSizeCache = std::unordered_map<const FunctionDeclaration*, int>;
83     int getFunctionSize(const FunctionDeclaration& fnDecl, FunctionSizeCache* cache);
84 
85     /**
86      * Processes the passed-in FunctionCall expression. The FunctionCall expression should be
87      * replaced with `fReplacementExpr`. If non-null, `fInlinedBody` should be inserted immediately
88      * above the statement containing the inlined expression.
89      */
90     struct InlinedCall {
91         std::unique_ptr<Block> fInlinedBody;
92         std::unique_ptr<Expression> fReplacementExpr;
93     };
94     InlinedCall inlineCall(FunctionCall*,
95                            std::shared_ptr<SymbolTable>,
96                            const ProgramUsage&,
97                            const FunctionDeclaration* caller);
98 
99     /** Creates a scratch variable for the inliner to use. */
100     struct InlineVariable {
101         const Variable*             fVarSymbol;
102         std::unique_ptr<Statement>  fVarDecl;
103     };
104     InlineVariable makeInlineVariable(const String& baseName,
105                                       const Type* type,
106                                       SymbolTable* symbolTable,
107                                       Modifiers modifiers,
108                                       bool isBuiltinCode,
109                                       std::unique_ptr<Expression>* initialValue);
110 
111     /** Adds a scope to inlined bodies returned by `inlineCall`, if one is required. */
112     void ensureScopedBlocks(Statement* inlinedBody, Statement* parentStmt);
113 
114     /** Checks whether inlining is viable for a FunctionCall, modulo recursion and function size. */
115     bool isSafeToInline(const FunctionDefinition* functionDef);
116 
modifiersPool()117     ModifiersPool& modifiersPool() const { return *fContext->fModifiersPool; }
118 
119     const Context* fContext = nullptr;
120     Mangler fMangler;
121     int fInlinedStatementCounter = 0;
122 };
123 
124 }  // namespace SkSL
125 
126 #endif  // SKSL_INLINER
127