/src/solidity/libyul/backends/evm/StackLayoutGenerator.h
Line | Count | Source |
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 | | * Stack layout generator for Yul to EVM code generation. |
20 | | */ |
21 | | |
22 | | #pragma once |
23 | | |
24 | | #include <libyul/backends/evm/ControlFlowGraph.h> |
25 | | #include <libyul/backends/evm/EVMDialect.h> |
26 | | |
27 | | #include <map> |
28 | | |
29 | | namespace solidity::yul |
30 | | { |
31 | | |
32 | | struct StackLayout |
33 | | { |
34 | | struct BlockInfo |
35 | | { |
36 | | /// Complete stack layout that is required for entering a block. |
37 | | Stack entryLayout; |
38 | | /// The resulting stack layout after executing the block. |
39 | | Stack exitLayout; |
40 | | }; |
41 | | std::map<CFG::BasicBlock const*, BlockInfo> blockInfos; |
42 | | /// For each operation the complete stack layout that: |
43 | | /// - has the slots required for the operation at the stack top. |
44 | | /// - will have the operation result in a layout that makes it easy to achieve the next desired layout. |
45 | | std::map<CFG::Operation const*, Stack> operationEntryLayout; |
46 | | }; |
47 | | |
48 | | class StackLayoutGenerator |
49 | | { |
50 | | public: |
51 | | struct StackTooDeep |
52 | | { |
53 | | /// Number of slots that need to be saved. |
54 | | size_t deficit = 0; |
55 | | /// Set of variables, eliminating which would decrease the stack deficit. |
56 | | std::vector<YulName> variableChoices; |
57 | | }; |
58 | | |
59 | | static StackLayout run(CFG const& _cfg, EVMDialect const& _evmDialect); |
60 | | /// @returns a map from function names to the stack too deep errors occurring in that function. |
61 | | /// Requires @a _cfg to be a control flow graph generated from disambiguated Yul. |
62 | | /// The empty string is mapped to the stack too deep errors of the main entry point. |
63 | | static std::map<YulName, std::vector<StackTooDeep>> reportStackTooDeep( |
64 | | CFG const& _cfg, |
65 | | EVMDialect const& _evmDialect |
66 | | ); |
67 | | /// @returns all stack too deep errors in the function named @a _functionName. |
68 | | /// Requires @a _cfg to be a control flow graph generated from disambiguated Yul. |
69 | | /// If @a _functionName is empty, the stack too deep errors of the main entry point are reported instead. |
70 | | static std::vector<StackTooDeep> reportStackTooDeep( |
71 | | CFG const& _cfg, |
72 | | YulName _functionName, |
73 | | EVMDialect const& _evmDialect |
74 | | ); |
75 | | |
76 | | private: |
77 | | StackLayoutGenerator( |
78 | | StackLayout& _context, |
79 | | CFG::FunctionInfo const* _functionInfo, |
80 | | EVMDialect const& _evmDialect |
81 | | ); |
82 | | |
83 | | /// @returns the optimal entry stack layout, s.t. @a _operation can be applied to it and |
84 | | /// the result can be transformed to @a _exitStack with minimal stack shuffling. |
85 | | /// Simultaneously stores the entry layout required for executing the operation in m_layout. |
86 | | Stack propagateStackThroughOperation(Stack _exitStack, CFG::Operation const& _operation, bool _aggressiveStackCompression = false); |
87 | | |
88 | | /// @returns the desired stack layout at the entry of @a _block, assuming the layout after |
89 | | /// executing the block should be @a _exitStack. |
90 | | Stack propagateStackThroughBlock(Stack _exitStack, CFG::BasicBlock const& _block, bool _aggressiveStackCompression = false); |
91 | | |
92 | | /// Main algorithm walking the graph from entry to exit and propagating back the stack layouts to the entries. |
93 | | /// Iteratively reruns itself along backwards jumps until the layout is stabilized. |
94 | | void processEntryPoint(CFG::BasicBlock const& _entry, CFG::FunctionInfo const* _functionInfo = nullptr); |
95 | | |
96 | | /// @returns the best known exit layout of @a _block, if all dependencies are already @a _visited. |
97 | | /// If not, adds the dependencies to @a _dependencyList and @returns std::nullopt. |
98 | | std::optional<Stack> getExitLayoutOrStageDependencies( |
99 | | CFG::BasicBlock const& _block, |
100 | | std::set<CFG::BasicBlock const*> const& _visited, |
101 | | std::list<CFG::BasicBlock const*>& _dependencyList |
102 | | ) const; |
103 | | |
104 | | /// @returns a pair of ``{jumpingBlock, targetBlock}`` for each backwards jump in the graph starting at @a _entry. |
105 | | std::list<std::pair<CFG::BasicBlock const*, CFG::BasicBlock const*>> collectBackwardsJumps(CFG::BasicBlock const& _entry) const; |
106 | | |
107 | | /// After the main algorithms, layouts at conditional jumps are merely compatible, i.e. the exit layout of the |
108 | | /// jumping block is a superset of the entry layout of the target block. This function modifies the entry layouts |
109 | | /// of conditional jump targets, s.t. the entry layout of target blocks match the exit layout of the jumping block |
110 | | /// exactly, except that slots not required after the jump are marked as `JunkSlot`s. |
111 | | void stitchConditionalJumps(CFG::BasicBlock const& _block); |
112 | | |
113 | | /// Calculates the ideal stack layout, s.t. both @a _stack1 and @a _stack2 can be achieved with minimal |
114 | | /// stack shuffling when starting from the returned layout. |
115 | | static Stack combineStack(Stack const& _stack1, Stack const& _stack2, size_t _reachableStackDepth); |
116 | | |
117 | | /// Walks through the CFG and reports any stack too deep errors that would occur when generating code for it |
118 | | /// without countermeasures. |
119 | | std::vector<StackTooDeep> reportStackTooDeep(CFG::BasicBlock const& _entry) const; |
120 | | |
121 | | /// @returns a copy of @a _stack stripped of all duplicates and slots that can be freely generated. |
122 | | /// Attempts to create a layout that requires a minimal amount of operations to reconstruct the original |
123 | | /// stack @a _stack. |
124 | | static Stack compressStack(Stack _stack, size_t _reachableStackDepth); |
125 | | |
126 | | //// Fills in junk when entering branches that do not need a clean stack in case the result is cheaper. |
127 | | void fillInJunk(CFG::BasicBlock const& _block, CFG::FunctionInfo const* _functionInfo = nullptr); |
128 | | |
129 | | /// True if it simulates functions with jumps. False otherwise. True for legacy bytecode. |
130 | | bool simulateFunctionsWithJumps() const |
131 | 417k | { |
132 | 417k | return !m_evmDialect.eofVersion().has_value(); |
133 | 417k | } |
134 | | size_t reachableStackDepth() const |
135 | 46.0M | { |
136 | 46.0M | return m_evmDialect.reachableStackDepth(); |
137 | 46.0M | } |
138 | | |
139 | | StackLayout& m_layout; |
140 | | CFG::FunctionInfo const* m_currentFunctionInfo = nullptr; |
141 | | EVMDialect const& m_evmDialect; |
142 | | }; |
143 | | |
144 | | } |