Coverage Report

Created: 2025-09-08 08:10

/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
}