Coverage Report

Created: 2026-09-14 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/optimiser/StackToMemoryMover.cpp
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
#include <libyul/optimiser/StackToMemoryMover.h>
18
#include <libyul/optimiser/NameCollector.h>
19
#include <libyul/optimiser/NameDispenser.h>
20
#include <libyul/backends/evm/EVMDialect.h>
21
22
#include <libyul/AST.h>
23
#include <libyul/Utilities.h>
24
25
#include <libsolutil/CommonData.h>
26
27
#include <range/v3/algorithm/none_of.hpp>
28
#include <range/v3/view/filter.hpp>
29
#include <range/v3/view/transform.hpp>
30
#include <range/v3/view/zip.hpp>
31
#include <range/v3/range/conversion.hpp>
32
33
using namespace solidity;
34
using namespace solidity::yul;
35
36
namespace
37
{
38
std::vector<Statement> generateMemoryStore(
39
  Dialect const& _dialect,
40
  langutil::DebugData::ConstPtr const& _debugData,
41
  LiteralValue const& _mpos,
42
  Expression _value
43
)
44
16.0k
{
45
16.0k
  std::optional<BuiltinHandle> memoryStoreFunctionHandle = _dialect.memoryStoreFunctionHandle();
46
16.0k
  yulAssert(memoryStoreFunctionHandle);
47
16.0k
  std::vector<Statement> result;
48
16.0k
  result.emplace_back(ExpressionStatement{_debugData, FunctionCall{
49
16.0k
    _debugData,
50
16.0k
    BuiltinName{_debugData, *memoryStoreFunctionHandle},
51
16.0k
    {
52
16.0k
      Literal{_debugData, LiteralKind::Number, _mpos},
53
16.0k
      std::move(_value)
54
16.0k
    }
55
16.0k
  }});
56
16.0k
  return result;
57
16.0k
}
58
59
FunctionCall generateMemoryLoad(Dialect const& _dialect, langutil::DebugData::ConstPtr const& _debugData, LiteralValue const& _mpos)
60
23.2k
{
61
23.2k
  std::optional<BuiltinHandle> const& memoryLoadHandle = _dialect.memoryLoadFunctionHandle();
62
23.2k
  yulAssert(memoryLoadHandle);
63
23.2k
  return FunctionCall{
64
23.2k
    _debugData,
65
23.2k
    BuiltinName{_debugData, *memoryLoadHandle}, {
66
23.2k
      Literal{
67
23.2k
        _debugData,
68
23.2k
        LiteralKind::Number,
69
23.2k
        _mpos
70
23.2k
      }
71
23.2k
    }
72
23.2k
  };
73
23.2k
}
74
}
75
76
void StackToMemoryMover::run(
77
  OptimiserStepContext& _context,
78
  u256 _reservedMemory,
79
  std::map<YulName, uint64_t> const& _memorySlots,
80
  uint64_t _numRequiredSlots,
81
  Block& _block
82
)
83
46.3k
{
84
46.3k
  VariableMemoryOffsetTracker memoryOffsetTracker(_reservedMemory, _memorySlots, _numRequiredSlots);
85
46.3k
  StackToMemoryMover stackToMemoryMover(
86
46.3k
    _context,
87
46.3k
    memoryOffsetTracker,
88
46.3k
    util::applyMap(
89
46.3k
      allFunctionDefinitions(_block),
90
147k
      util::mapTuple([](YulName _name, FunctionDefinition const* _funDef) {
91
147k
        return make_pair(_name, _funDef->returnVariables);
92
147k
      }),
93
46.3k
      std::map<YulName, NameWithDebugDataList>{}
94
46.3k
    )
95
46.3k
  );
96
46.3k
  stackToMemoryMover(_block);
97
46.3k
  _block.statements += std::move(stackToMemoryMover.m_newFunctionDefinitions);
98
46.3k
}
99
100
StackToMemoryMover::StackToMemoryMover(
101
  OptimiserStepContext& _context,
102
  VariableMemoryOffsetTracker const& _memoryOffsetTracker,
103
  std::map<YulName, NameWithDebugDataList> _functionReturnVariables
104
):
105
46.3k
m_context(_context),
106
46.3k
m_memoryOffsetTracker(_memoryOffsetTracker),
107
46.3k
m_nameDispenser(_context.dispenser),
108
46.3k
m_functionReturnVariables(std::move(_functionReturnVariables))
109
46.3k
{
110
46.3k
  auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
111
46.3k
  yulAssert(
112
46.3k
    evmDialect && evmDialect->providesObjectAccess(),
113
46.3k
    "StackToMemoryMover can only be run on objects using the EVMDialect with object access."
114
46.3k
  );
115
46.3k
}
116
117
void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition)
118
147k
{
119
  // It is important to first visit the function body, so that it doesn't replace the memory inits for
120
  // variable arguments we might generate below.
121
147k
  ASTModifier::operator()(_functionDefinition);
122
123
147k
  std::vector<Statement> memoryVariableInits;
124
125
  // All function parameters with a memory slot are moved at the beginning of the function body.
126
147k
  for (NameWithDebugData const& param: _functionDefinition.parameters)
127
143k
    if (auto slot = m_memoryOffsetTracker(param.name))
128
615
      memoryVariableInits += generateMemoryStore(
129
615
        m_context.dialect,
130
615
        param.debugData,
131
615
        *slot,
132
615
        Identifier{param.debugData, param.name}
133
615
      );
134
135
  // All memory return variables have to be initialized to zero in memory.
136
147k
  for (NameWithDebugData const& returnVariable: _functionDefinition.returnVariables)
137
121k
    if (auto slot = m_memoryOffsetTracker(returnVariable.name))
138
887
      memoryVariableInits += generateMemoryStore(
139
887
        m_context.dialect,
140
887
        returnVariable.debugData,
141
887
        *slot,
142
887
        Literal{returnVariable.debugData, LiteralKind::Number, LiteralValue(u256{0})}
143
887
      );
144
145
  // Special case of a function with a single return argument that needs to move to memory.
146
147k
  if (_functionDefinition.returnVariables.size() == 1 && m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name))
147
660
  {
148
660
    NameWithDebugDataList stackParameters = _functionDefinition.parameters | ranges::views::filter(
149
660
      std::not_fn(m_memoryOffsetTracker)
150
660
    ) | ranges::to<NameWithDebugDataList>;
151
    // Generate new function without return variable and with only the non-moved parameters.
152
660
    YulName newFunctionName = m_context.dispenser.newName(_functionDefinition.name);
153
660
    m_newFunctionDefinitions.emplace_back(FunctionDefinition{
154
660
      _functionDefinition.debugData,
155
660
      newFunctionName,
156
660
      stackParameters,
157
660
      {},
158
660
      std::move(_functionDefinition.body)
159
660
    });
160
    // Generate new names for the arguments to maintain disambiguation.
161
660
    std::map<YulName, YulName> newArgumentNames;
162
660
    for (NameWithDebugData const& _var: stackParameters)
163
508
      newArgumentNames[_var.name] = m_context.dispenser.newName(_var.name);
164
660
    for (auto& parameter: _functionDefinition.parameters)
165
693
      parameter.name = util::valueOrDefault(newArgumentNames, parameter.name, parameter.name);
166
    // Replace original function by a call to the new function and an assignment to the return variable from memory.
167
660
    _functionDefinition.body = Block{_functionDefinition.debugData, std::move(memoryVariableInits)};
168
660
    _functionDefinition.body.statements.emplace_back(ExpressionStatement{
169
660
      _functionDefinition.debugData,
170
660
      FunctionCall{
171
660
        _functionDefinition.debugData,
172
660
        Identifier{_functionDefinition.debugData, newFunctionName},
173
660
        stackParameters | ranges::views::transform([&](NameWithDebugData const& _arg) {
174
508
          return Expression{Identifier{_arg.debugData, newArgumentNames.at(_arg.name)}};
175
508
        }) | ranges::to<std::vector<Expression>>
176
660
      }
177
660
    });
178
660
    _functionDefinition.body.statements.emplace_back(Assignment{
179
660
      _functionDefinition.debugData,
180
660
      {Identifier{_functionDefinition.debugData, _functionDefinition.returnVariables.front().name}},
181
660
      std::make_unique<Expression>(generateMemoryLoad(
182
660
        m_context.dialect,
183
660
        _functionDefinition.debugData,
184
660
        *m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name)
185
660
      ))
186
660
    });
187
660
    return;
188
660
  }
189
190
146k
  if (!memoryVariableInits.empty())
191
473
    _functionDefinition.body.statements = std::move(memoryVariableInits) + std::move(_functionDefinition.body.statements);
192
193
146k
  _functionDefinition.returnVariables = _functionDefinition.returnVariables | ranges::views::filter(
194
146k
    std::not_fn(m_memoryOffsetTracker)
195
146k
  ) | ranges::to<NameWithDebugDataList>;
196
146k
}
197
198
void StackToMemoryMover::operator()(Block& _block)
199
684k
{
200
684k
  using OptionalStatements = std::optional<std::vector<Statement>>;
201
202
684k
  auto rewriteAssignmentOrVariableDeclarationLeftHandSide = [this](
203
684k
    auto& _stmt,
204
684k
    auto& _lhsVars
205
684k
  ) -> OptionalStatements {
206
536k
    using StatementType = std::decay_t<decltype(_stmt)>;
207
208
536k
    auto debugData = _stmt.debugData;
209
536k
    if (_lhsVars.size() == 1)
210
519k
    {
211
519k
      if (auto offset = m_memoryOffsetTracker(_lhsVars.front().name))
212
14.1k
        return generateMemoryStore(
213
14.1k
          m_context.dialect,
214
14.1k
          debugData,
215
14.1k
          *offset,
216
14.1k
          _stmt.value ? *std::move(_stmt.value) : Literal{debugData, LiteralKind::Number, LiteralValue(u256{0})}
217
14.1k
        );
218
505k
      else
219
505k
        return {};
220
519k
    }
221
17.4k
    std::vector<std::optional<LiteralValue>> rhsMemorySlots;
222
17.4k
    if (_stmt.value)
223
15.1k
    {
224
15.1k
      FunctionCall const* functionCall = std::get_if<FunctionCall>(_stmt.value.get());
225
15.1k
      yulAssert(functionCall, "");
226
15.1k
      if (isBuiltinFunctionCall(*functionCall))
227
0
        rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt);
228
15.1k
      else
229
15.1k
      {
230
15.1k
        yulAssert(std::holds_alternative<Identifier>(functionCall->functionName));
231
15.1k
        rhsMemorySlots =
232
15.1k
          m_functionReturnVariables.at(std::get<Identifier>(functionCall->functionName).name) |
233
15.1k
          ranges::views::transform(m_memoryOffsetTracker) |
234
15.1k
          ranges::to<std::vector<std::optional<LiteralValue>>>;
235
15.1k
      }
236
15.1k
    }
237
2.25k
    else
238
2.25k
      rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt);
239
240
    // Nothing to do, if the right-hand-side remains entirely on the stack and
241
    // none of the variables in the left-hand-side are moved.
242
17.4k
    if (
243
55.8k
      ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) &&
StackToMemoryMover.cpp:solidity::yul::StackToMemoryMover::operator()(solidity::yul::Block&)::$_0::operator()<solidity::yul::Assignment, std::__1::vector<solidity::yul::Identifier, std::__1::allocator<solidity::yul::Identifier> > >(solidity::yul::Assignment&, std::__1::vector<solidity::yul::Identifier, std::__1::allocator<solidity::yul::Identifier> >&) const::{lambda(std::__1::optional<solidity::yul::LiteralValue> const&)#1}::operator()(std::__1::optional<solidity::yul::LiteralValue> const&) const
Line
Count
Source
243
3.25k
      ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) &&
StackToMemoryMover.cpp:solidity::yul::StackToMemoryMover::operator()(solidity::yul::Block&)::$_0::operator()<solidity::yul::VariableDeclaration, std::__1::vector<solidity::yul::NameWithDebugData, std::__1::allocator<solidity::yul::NameWithDebugData> > >(solidity::yul::VariableDeclaration&, std::__1::vector<solidity::yul::NameWithDebugData, std::__1::allocator<solidity::yul::NameWithDebugData> >&) const::{lambda(std::__1::optional<solidity::yul::LiteralValue> const&)#1}::operator()(std::__1::optional<solidity::yul::LiteralValue> const&) const
Line
Count
Source
243
52.6k
      ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) &&
244
17.1k
      !util::contains_if(_lhsVars, m_memoryOffsetTracker)
245
17.4k
    )
246
16.8k
      return {};
247
248
600
    std::vector<Statement> memoryAssignments;
249
600
    std::vector<Statement> variableAssignments;
250
600
    VariableDeclaration tempDecl{debugData, {}, std::move(_stmt.value)};
251
252
600
    yulAssert(rhsMemorySlots.size() == _lhsVars.size(), "");
253
600
    for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots))
254
1.96k
    {
255
1.96k
      std::unique_ptr<Expression> rhs;
256
1.96k
      if (rhsSlot)
257
420
        rhs = std::make_unique<Expression>(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot));
258
1.54k
      else
259
1.54k
      {
260
1.54k
        YulName tempVarName = m_nameDispenser.newName(lhsVar.name);
261
1.54k
        tempDecl.variables.emplace_back(NameWithDebugData{lhsVar.debugData, tempVarName});
262
1.54k
        rhs = std::make_unique<Expression>(Identifier{debugData, tempVarName});
263
1.54k
      }
264
265
1.96k
      if (auto offset = m_memoryOffsetTracker(lhsVar.name))
266
459
        memoryAssignments += generateMemoryStore(
267
459
          m_context.dialect,
268
459
          _stmt.debugData,
269
459
          *offset,
270
459
          std::move(*rhs)
271
459
        );
272
1.51k
      else
273
1.51k
        variableAssignments.emplace_back(StatementType{
274
1.51k
          debugData,
275
1.51k
          { std::move(lhsVar) },
276
1.51k
          std::move(rhs)
277
1.51k
        });
278
1.96k
    }
279
280
600
    std::vector<Statement> result;
281
600
    if (tempDecl.variables.empty())
282
83
      result.emplace_back(ExpressionStatement{debugData, *std::move(tempDecl.value)});
283
517
    else
284
517
      result.emplace_back(std::move(tempDecl));
285
600
    reverse(memoryAssignments.begin(), memoryAssignments.end());
286
600
    result += std::move(memoryAssignments);
287
600
    reverse(variableAssignments.begin(), variableAssignments.end());
288
600
    result += std::move(variableAssignments);
289
600
    return OptionalStatements{std::move(result)};
290
17.4k
  };
StackToMemoryMover.cpp:std::__1::optional<std::__1::vector<std::__1::variant<solidity::yul::ExpressionStatement, solidity::yul::Assignment, solidity::yul::VariableDeclaration, solidity::yul::FunctionDefinition, solidity::yul::If, solidity::yul::Switch, solidity::yul::ForLoop, solidity::yul::Break, solidity::yul::Continue, solidity::yul::Leave, solidity::yul::Block>, std::__1::allocator<std::__1::variant<solidity::yul::ExpressionStatement, solidity::yul::Assignment, solidity::yul::VariableDeclaration, solidity::yul::FunctionDefinition, solidity::yul::If, solidity::yul::Switch, solidity::yul::ForLoop, solidity::yul::Break, solidity::yul::Continue, solidity::yul::Leave, solidity::yul::Block> > > > solidity::yul::StackToMemoryMover::operator()(solidity::yul::Block&)::$_0::operator()<solidity::yul::Assignment, std::__1::vector<solidity::yul::Identifier, std::__1::allocator<solidity::yul::Identifier> > >(solidity::yul::Assignment&, std::__1::vector<solidity::yul::Identifier, std::__1::allocator<solidity::yul::Identifier> >&) const
Line
Count
Source
205
254k
  ) -> OptionalStatements {
206
254k
    using StatementType = std::decay_t<decltype(_stmt)>;
207
208
254k
    auto debugData = _stmt.debugData;
209
254k
    if (_lhsVars.size() == 1)
210
252k
    {
211
252k
      if (auto offset = m_memoryOffsetTracker(_lhsVars.front().name))
212
3.05k
        return generateMemoryStore(
213
3.05k
          m_context.dialect,
214
3.05k
          debugData,
215
3.05k
          *offset,
216
3.05k
          _stmt.value ? *std::move(_stmt.value) : Literal{debugData, LiteralKind::Number, LiteralValue(u256{0})}
217
3.05k
        );
218
249k
      else
219
249k
        return {};
220
252k
    }
221
1.61k
    std::vector<std::optional<LiteralValue>> rhsMemorySlots;
222
1.61k
    if (_stmt.value)
223
1.61k
    {
224
1.61k
      FunctionCall const* functionCall = std::get_if<FunctionCall>(_stmt.value.get());
225
1.61k
      yulAssert(functionCall, "");
226
1.61k
      if (isBuiltinFunctionCall(*functionCall))
227
0
        rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt);
228
1.61k
      else
229
1.61k
      {
230
1.61k
        yulAssert(std::holds_alternative<Identifier>(functionCall->functionName));
231
1.61k
        rhsMemorySlots =
232
1.61k
          m_functionReturnVariables.at(std::get<Identifier>(functionCall->functionName).name) |
233
1.61k
          ranges::views::transform(m_memoryOffsetTracker) |
234
1.61k
          ranges::to<std::vector<std::optional<LiteralValue>>>;
235
1.61k
      }
236
1.61k
    }
237
0
    else
238
0
      rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt);
239
240
    // Nothing to do, if the right-hand-side remains entirely on the stack and
241
    // none of the variables in the left-hand-side are moved.
242
1.61k
    if (
243
1.61k
      ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) &&
244
1.61k
      !util::contains_if(_lhsVars, m_memoryOffsetTracker)
245
1.61k
    )
246
1.61k
      return {};
247
248
0
    std::vector<Statement> memoryAssignments;
249
0
    std::vector<Statement> variableAssignments;
250
0
    VariableDeclaration tempDecl{debugData, {}, std::move(_stmt.value)};
251
252
0
    yulAssert(rhsMemorySlots.size() == _lhsVars.size(), "");
253
0
    for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots))
254
0
    {
255
0
      std::unique_ptr<Expression> rhs;
256
0
      if (rhsSlot)
257
0
        rhs = std::make_unique<Expression>(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot));
258
0
      else
259
0
      {
260
0
        YulName tempVarName = m_nameDispenser.newName(lhsVar.name);
261
0
        tempDecl.variables.emplace_back(NameWithDebugData{lhsVar.debugData, tempVarName});
262
0
        rhs = std::make_unique<Expression>(Identifier{debugData, tempVarName});
263
0
      }
264
265
0
      if (auto offset = m_memoryOffsetTracker(lhsVar.name))
266
0
        memoryAssignments += generateMemoryStore(
267
0
          m_context.dialect,
268
0
          _stmt.debugData,
269
0
          *offset,
270
0
          std::move(*rhs)
271
0
        );
272
0
      else
273
0
        variableAssignments.emplace_back(StatementType{
274
0
          debugData,
275
0
          { std::move(lhsVar) },
276
0
          std::move(rhs)
277
0
        });
278
0
    }
279
280
0
    std::vector<Statement> result;
281
0
    if (tempDecl.variables.empty())
282
0
      result.emplace_back(ExpressionStatement{debugData, *std::move(tempDecl.value)});
283
0
    else
284
0
      result.emplace_back(std::move(tempDecl));
285
0
    reverse(memoryAssignments.begin(), memoryAssignments.end());
286
0
    result += std::move(memoryAssignments);
287
0
    reverse(variableAssignments.begin(), variableAssignments.end());
288
0
    result += std::move(variableAssignments);
289
0
    return OptionalStatements{std::move(result)};
290
1.61k
  };
StackToMemoryMover.cpp:std::__1::optional<std::__1::vector<std::__1::variant<solidity::yul::ExpressionStatement, solidity::yul::Assignment, solidity::yul::VariableDeclaration, solidity::yul::FunctionDefinition, solidity::yul::If, solidity::yul::Switch, solidity::yul::ForLoop, solidity::yul::Break, solidity::yul::Continue, solidity::yul::Leave, solidity::yul::Block>, std::__1::allocator<std::__1::variant<solidity::yul::ExpressionStatement, solidity::yul::Assignment, solidity::yul::VariableDeclaration, solidity::yul::FunctionDefinition, solidity::yul::If, solidity::yul::Switch, solidity::yul::ForLoop, solidity::yul::Break, solidity::yul::Continue, solidity::yul::Leave, solidity::yul::Block> > > > solidity::yul::StackToMemoryMover::operator()(solidity::yul::Block&)::$_0::operator()<solidity::yul::VariableDeclaration, std::__1::vector<solidity::yul::NameWithDebugData, std::__1::allocator<solidity::yul::NameWithDebugData> > >(solidity::yul::VariableDeclaration&, std::__1::vector<solidity::yul::NameWithDebugData, std::__1::allocator<solidity::yul::NameWithDebugData> >&) const
Line
Count
Source
205
282k
  ) -> OptionalStatements {
206
282k
    using StatementType = std::decay_t<decltype(_stmt)>;
207
208
282k
    auto debugData = _stmt.debugData;
209
282k
    if (_lhsVars.size() == 1)
210
266k
    {
211
266k
      if (auto offset = m_memoryOffsetTracker(_lhsVars.front().name))
212
11.0k
        return generateMemoryStore(
213
11.0k
          m_context.dialect,
214
11.0k
          debugData,
215
11.0k
          *offset,
216
11.0k
          _stmt.value ? *std::move(_stmt.value) : Literal{debugData, LiteralKind::Number, LiteralValue(u256{0})}
217
11.0k
        );
218
255k
      else
219
255k
        return {};
220
266k
    }
221
15.7k
    std::vector<std::optional<LiteralValue>> rhsMemorySlots;
222
15.7k
    if (_stmt.value)
223
13.5k
    {
224
13.5k
      FunctionCall const* functionCall = std::get_if<FunctionCall>(_stmt.value.get());
225
13.5k
      yulAssert(functionCall, "");
226
13.5k
      if (isBuiltinFunctionCall(*functionCall))
227
0
        rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt);
228
13.5k
      else
229
13.5k
      {
230
13.5k
        yulAssert(std::holds_alternative<Identifier>(functionCall->functionName));
231
13.5k
        rhsMemorySlots =
232
13.5k
          m_functionReturnVariables.at(std::get<Identifier>(functionCall->functionName).name) |
233
13.5k
          ranges::views::transform(m_memoryOffsetTracker) |
234
13.5k
          ranges::to<std::vector<std::optional<LiteralValue>>>;
235
13.5k
      }
236
13.5k
    }
237
2.25k
    else
238
2.25k
      rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt);
239
240
    // Nothing to do, if the right-hand-side remains entirely on the stack and
241
    // none of the variables in the left-hand-side are moved.
242
15.7k
    if (
243
15.7k
      ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) &&
244
15.5k
      !util::contains_if(_lhsVars, m_memoryOffsetTracker)
245
15.7k
    )
246
15.1k
      return {};
247
248
600
    std::vector<Statement> memoryAssignments;
249
600
    std::vector<Statement> variableAssignments;
250
600
    VariableDeclaration tempDecl{debugData, {}, std::move(_stmt.value)};
251
252
600
    yulAssert(rhsMemorySlots.size() == _lhsVars.size(), "");
253
600
    for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots))
254
1.96k
    {
255
1.96k
      std::unique_ptr<Expression> rhs;
256
1.96k
      if (rhsSlot)
257
420
        rhs = std::make_unique<Expression>(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot));
258
1.54k
      else
259
1.54k
      {
260
1.54k
        YulName tempVarName = m_nameDispenser.newName(lhsVar.name);
261
1.54k
        tempDecl.variables.emplace_back(NameWithDebugData{lhsVar.debugData, tempVarName});
262
1.54k
        rhs = std::make_unique<Expression>(Identifier{debugData, tempVarName});
263
1.54k
      }
264
265
1.96k
      if (auto offset = m_memoryOffsetTracker(lhsVar.name))
266
459
        memoryAssignments += generateMemoryStore(
267
459
          m_context.dialect,
268
459
          _stmt.debugData,
269
459
          *offset,
270
459
          std::move(*rhs)
271
459
        );
272
1.51k
      else
273
1.51k
        variableAssignments.emplace_back(StatementType{
274
1.51k
          debugData,
275
1.51k
          { std::move(lhsVar) },
276
1.51k
          std::move(rhs)
277
1.51k
        });
278
1.96k
    }
279
280
600
    std::vector<Statement> result;
281
600
    if (tempDecl.variables.empty())
282
83
      result.emplace_back(ExpressionStatement{debugData, *std::move(tempDecl.value)});
283
517
    else
284
517
      result.emplace_back(std::move(tempDecl));
285
600
    reverse(memoryAssignments.begin(), memoryAssignments.end());
286
600
    result += std::move(memoryAssignments);
287
600
    reverse(variableAssignments.begin(), variableAssignments.end());
288
600
    result += std::move(variableAssignments);
289
600
    return OptionalStatements{std::move(result)};
290
15.7k
  };
291
292
684k
  util::iterateReplacing(
293
684k
    _block.statements,
294
684k
    [&](Statement& _statement) -> OptionalStatements
295
1.65M
    {
296
1.65M
      visit(_statement);
297
1.65M
      if (auto* assignment = std::get_if<Assignment>(&_statement))
298
254k
        return rewriteAssignmentOrVariableDeclarationLeftHandSide(*assignment, assignment->variableNames);
299
1.40M
      else if (auto* varDecl = std::get_if<VariableDeclaration>(&_statement))
300
282k
        return rewriteAssignmentOrVariableDeclarationLeftHandSide(*varDecl, varDecl->variables);
301
1.12M
      return {};
302
1.65M
    }
303
684k
  );
304
684k
}
305
306
void StackToMemoryMover::visit(Expression& _expression)
307
6.07M
{
308
6.07M
  ASTModifier::visit(_expression);
309
6.07M
  if (Identifier* identifier = std::get_if<Identifier>(&_expression))
310
760k
    if (auto offset = m_memoryOffsetTracker(identifier->name))
311
22.1k
      _expression = generateMemoryLoad(m_context.dialect, identifier->debugData, *offset);
312
6.07M
}
313
314
std::optional<LiteralValue> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(YulName const& _variable) const
315
1.88M
{
316
1.88M
  if (m_memorySlots.count(_variable))
317
40.8k
  {
318
40.8k
    uint64_t slot = m_memorySlots.at(_variable);
319
40.8k
    yulAssert(slot < m_numRequiredSlots, "");
320
40.8k
    auto const memoryOffset = m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1);
321
40.8k
    return valueOfNumberLiteral(toCompactHexWithPrefix(memoryOffset));
322
40.8k
  }
323
1.84M
  else
324
1.84M
    return std::nullopt;
325
1.88M
}
326
327
std::optional<LiteralValue> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(NameWithDebugData const& _variable) const
328
244k
{
329
244k
  return (*this)(_variable.name);
330
244k
}
331
332
std::optional<LiteralValue> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(Identifier const& _variable) const
333
3.25k
{
334
3.25k
  return (*this)(_variable.name);
335
3.25k
}