/src/solidity/libyul/optimiser/FullInliner.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | /** |
19 | | * Optimiser component that performs function inlining for arbitrary functions. |
20 | | */ |
21 | | #pragma once |
22 | | |
23 | | #include <libyul/ASTForward.h> |
24 | | |
25 | | #include <libyul/optimiser/ASTCopier.h> |
26 | | #include <libyul/optimiser/ASTWalker.h> |
27 | | #include <libyul/optimiser/NameDispenser.h> |
28 | | #include <libyul/optimiser/OptimiserStep.h> |
29 | | #include <libyul/Exceptions.h> |
30 | | |
31 | | #include <liblangutil/SourceLocation.h> |
32 | | |
33 | | #include <optional> |
34 | | #include <set> |
35 | | #include <utility> |
36 | | |
37 | | namespace solidity::yul |
38 | | { |
39 | | |
40 | | class NameCollector; |
41 | | |
42 | | |
43 | | /** |
44 | | * Optimiser component that modifies an AST in place, inlining functions. |
45 | | * Expressions are expected to be split, i.e. the component will only inline |
46 | | * function calls that are at the root of the expression and that only contains |
47 | | * variables as arguments. More specifically, it will inline |
48 | | * - let x1, ..., xn := f(a1, ..., am) |
49 | | * - x1, ..., xn := f(a1, ..., am) |
50 | | * f(a1, ..., am) |
51 | | * |
52 | | * The transform changes code of the form |
53 | | * |
54 | | * function f(a, b) -> c { ... } |
55 | | * let z := f(x, y) |
56 | | * |
57 | | * into |
58 | | * |
59 | | * function f(a, b) -> c { ... } |
60 | | * |
61 | | * let f_a := x |
62 | | * let f_b := y |
63 | | * let f_c |
64 | | * code of f, with replacements: a -> f_a, b -> f_b, c -> f_c |
65 | | * let z := f_c |
66 | | * |
67 | | * Prerequisites: Disambiguator |
68 | | * More efficient if run after: Function Hoister, Expression Splitter |
69 | | */ |
70 | | class FullInliner: public ASTModifier |
71 | | { |
72 | | public: |
73 | | static constexpr char const* name{"FullInliner"}; |
74 | | static void run(OptimiserStepContext& _context, Block& _ast); |
75 | | |
76 | | /// Inlining heuristic. |
77 | | /// @param _callSite the name of the function in which the function call is located. |
78 | | bool shallInline(FunctionCall const& _funCall, YulString _callSite); |
79 | | |
80 | | FunctionDefinition* function(YulString _name) |
81 | 0 | { |
82 | 0 | auto it = m_functions.find(_name); |
83 | 0 | if (it != m_functions.end()) |
84 | 0 | return it->second; |
85 | 0 | return nullptr; |
86 | 0 | } |
87 | | |
88 | | /// Adds the size of _funCall to the size of _callSite. This is just |
89 | | /// a rough estimate that is done during inlining. The proper size |
90 | | /// should be determined after inlining is completed. |
91 | | void tentativelyUpdateCodeSize(YulString _function, YulString _callSite); |
92 | | |
93 | | private: |
94 | | enum Pass { InlineTiny, InlineRest }; |
95 | | |
96 | | FullInliner(Block& _ast, NameDispenser& _dispenser, Dialect const& _dialect); |
97 | | void run(Pass _pass); |
98 | | |
99 | | /// @returns a map containing the maximum depths of a call chain starting at each |
100 | | /// function. For recursive functions, the value is one larger than for all others. |
101 | | std::map<YulString, size_t> callDepths() const; |
102 | | |
103 | | void updateCodeSize(FunctionDefinition const& _fun); |
104 | | void handleBlock(YulString _currentFunctionName, Block& _block); |
105 | | bool recursive(FunctionDefinition const& _fun) const; |
106 | | |
107 | | Pass m_pass; |
108 | | /// The AST to be modified. The root block itself will not be modified, because |
109 | | /// we store pointers to functions. |
110 | | Block& m_ast; |
111 | | std::map<YulString, FunctionDefinition*> m_functions; |
112 | | /// Functions not to be inlined (because they contain the ``leave`` statement). |
113 | | std::set<YulString> m_noInlineFunctions; |
114 | | /// True, if the code contains a ``memoryguard`` and we can expect to be able to move variables to memory later. |
115 | | bool m_hasMemoryGuard = false; |
116 | | /// Set of recursive functions. |
117 | | std::set<YulString> m_recursiveFunctions; |
118 | | /// Names of functions to always inline. |
119 | | std::set<YulString> m_singleUse; |
120 | | /// Variables that are constants (used for inlining heuristic) |
121 | | std::set<YulString> m_constants; |
122 | | std::map<YulString, size_t> m_functionSizes; |
123 | | NameDispenser& m_nameDispenser; |
124 | | Dialect const& m_dialect; |
125 | | }; |
126 | | |
127 | | /** |
128 | | * Class that walks the AST of a block that does not contain function definitions and perform |
129 | | * the actual code modifications. |
130 | | */ |
131 | | class InlineModifier: public ASTModifier |
132 | | { |
133 | | public: |
134 | | InlineModifier(FullInliner& _driver, NameDispenser& _nameDispenser, YulString _functionName, Dialect const& _dialect): |
135 | | m_currentFunction(std::move(_functionName)), |
136 | | m_driver(_driver), |
137 | | m_nameDispenser(_nameDispenser), |
138 | | m_dialect(_dialect) |
139 | 0 | { } |
140 | | |
141 | | void operator()(Block& _block) override; |
142 | | |
143 | | private: |
144 | | std::optional<std::vector<Statement>> tryInlineStatement(Statement& _statement); |
145 | | std::vector<Statement> performInline(Statement& _statement, FunctionCall& _funCall); |
146 | | |
147 | | YulString m_currentFunction; |
148 | | FullInliner& m_driver; |
149 | | NameDispenser& m_nameDispenser; |
150 | | Dialect const& m_dialect; |
151 | | }; |
152 | | |
153 | | /** |
154 | | * Creates a copy of a block that is supposed to be the body of a function. |
155 | | * Applies replacements to referenced variables and creates new names for |
156 | | * variable declarations. |
157 | | */ |
158 | | class BodyCopier: public ASTCopier |
159 | | { |
160 | | public: |
161 | | BodyCopier( |
162 | | NameDispenser& _nameDispenser, |
163 | | std::map<YulString, YulString> _variableReplacements |
164 | | ): |
165 | | m_nameDispenser(_nameDispenser), |
166 | | m_variableReplacements(std::move(_variableReplacements)) |
167 | 0 | {} |
168 | | |
169 | | using ASTCopier::operator (); |
170 | | |
171 | | Statement operator()(VariableDeclaration const& _varDecl) override; |
172 | | Statement operator()(FunctionDefinition const& _funDef) override; |
173 | | |
174 | | YulString translateIdentifier(YulString _name) override; |
175 | | |
176 | | NameDispenser& m_nameDispenser; |
177 | | std::map<YulString, YulString> m_variableReplacements; |
178 | | }; |
179 | | |
180 | | |
181 | | } |