Coverage Report

Created: 2026-07-13 07:08

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
14.0k
{
45
14.0k
  std::optional<BuiltinHandle> memoryStoreFunctionHandle = _dialect.memoryStoreFunctionHandle();
46
14.0k
  yulAssert(memoryStoreFunctionHandle);
47
14.0k
  std::vector<Statement> result;
48
14.0k
  result.emplace_back(ExpressionStatement{_debugData, FunctionCall{
49
14.0k
    _debugData,
50
14.0k
    BuiltinName{_debugData, *memoryStoreFunctionHandle},
51
14.0k
    {
52
14.0k
      Literal{_debugData, LiteralKind::Number, _mpos},
53
14.0k
      std::move(_value)
54
14.0k
    }
55
14.0k
  }});
56
14.0k
  return result;
57
14.0k
}
58
59
FunctionCall generateMemoryLoad(Dialect const& _dialect, langutil::DebugData::ConstPtr const& _debugData, LiteralValue const& _mpos)
60
18.7k
{
61
18.7k
  std::optional<BuiltinHandle> const& memoryLoadHandle = _dialect.memoryLoadFunctionHandle();
62
18.7k
  yulAssert(memoryLoadHandle);
63
18.7k
  return FunctionCall{
64
18.7k
    _debugData,
65
18.7k
    BuiltinName{_debugData, *memoryLoadHandle}, {
66
18.7k
      Literal{
67
18.7k
        _debugData,
68
18.7k
        LiteralKind::Number,
69
18.7k
        _mpos
70
18.7k
      }
71
18.7k
    }
72
18.7k
  };
73
18.7k
}
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
45.2k
{
84
45.2k
  VariableMemoryOffsetTracker memoryOffsetTracker(_reservedMemory, _memorySlots, _numRequiredSlots);
85
45.2k
  StackToMemoryMover stackToMemoryMover(
86
45.2k
    _context,
87
45.2k
    memoryOffsetTracker,
88
45.2k
    util::applyMap(
89
45.2k
      allFunctionDefinitions(_block),
90
132k
      util::mapTuple([](YulName _name, FunctionDefinition const* _funDef) {
91
132k
        return make_pair(_name, _funDef->returnVariables);
92
132k
      }),
93
45.2k
      std::map<YulName, NameWithDebugDataList>{}
94
45.2k
    )
95
45.2k
  );
96
45.2k
  stackToMemoryMover(_block);
97
45.2k
  _block.statements += std::move(stackToMemoryMover.m_newFunctionDefinitions);
98
45.2k
}
99
100
StackToMemoryMover::StackToMemoryMover(
101
  OptimiserStepContext& _context,
102
  VariableMemoryOffsetTracker const& _memoryOffsetTracker,
103
  std::map<YulName, NameWithDebugDataList> _functionReturnVariables
104
):
105
45.2k
m_context(_context),
106
45.2k
m_memoryOffsetTracker(_memoryOffsetTracker),
107
45.2k
m_nameDispenser(_context.dispenser),
108
45.2k
m_functionReturnVariables(std::move(_functionReturnVariables))
109
45.2k
{
110
45.2k
  auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
111
45.2k
  yulAssert(
112
45.2k
    evmDialect && evmDialect->providesObjectAccess(),
113
45.2k
    "StackToMemoryMover can only be run on objects using the EVMDialect with object access."
114
45.2k
  );
115
45.2k
}
116
117
void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition)
118
132k
{
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
132k
  ASTModifier::operator()(_functionDefinition);
122
123
132k
  std::vector<Statement> memoryVariableInits;
124
125
  // All function parameters with a memory slot are moved at the beginning of the function body.
126
132k
  for (NameWithDebugData const& param: _functionDefinition.parameters)
127
129k
    if (auto slot = m_memoryOffsetTracker(param.name))
128
653
      memoryVariableInits += generateMemoryStore(
129
653
        m_context.dialect,
130
653
        param.debugData,
131
653
        *slot,
132
653
        Identifier{param.debugData, param.name}
133
653
      );
134
135
  // All memory return variables have to be initialized to zero in memory.
136
132k
  for (NameWithDebugData const& returnVariable: _functionDefinition.returnVariables)
137
109k
    if (auto slot = m_memoryOffsetTracker(returnVariable.name))
138
1.04k
      memoryVariableInits += generateMemoryStore(
139
1.04k
        m_context.dialect,
140
1.04k
        returnVariable.debugData,
141
1.04k
        *slot,
142
1.04k
        Literal{returnVariable.debugData, LiteralKind::Number, LiteralValue(u256{0})}
143
1.04k
      );
144
145
  // Special case of a function with a single return argument that needs to move to memory.
146
132k
  if (_functionDefinition.returnVariables.size() == 1 && m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name))
147
843
  {
148
843
    NameWithDebugDataList stackParameters = _functionDefinition.parameters | ranges::views::filter(
149
843
      std::not_fn(m_memoryOffsetTracker)
150
843
    ) | ranges::to<NameWithDebugDataList>;
151
    // Generate new function without return variable and with only the non-moved parameters.
152
843
    YulName newFunctionName = m_context.dispenser.newName(_functionDefinition.name);
153
843
    m_newFunctionDefinitions.emplace_back(FunctionDefinition{
154
843
      _functionDefinition.debugData,
155
843
      newFunctionName,
156
843
      stackParameters,
157
843
      {},
158
843
      std::move(_functionDefinition.body)
159
843
    });
160
    // Generate new names for the arguments to maintain disambiguation.
161
843
    std::map<YulName, YulName> newArgumentNames;
162
843
    for (NameWithDebugData const& _var: stackParameters)
163
662
      newArgumentNames[_var.name] = m_context.dispenser.newName(_var.name);
164
843
    for (auto& parameter: _functionDefinition.parameters)
165
895
      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
843
    _functionDefinition.body = Block{_functionDefinition.debugData, std::move(memoryVariableInits)};
168
843
    _functionDefinition.body.statements.emplace_back(ExpressionStatement{
169
843
      _functionDefinition.debugData,
170
843
      FunctionCall{
171
843
        _functionDefinition.debugData,
172
843
        Identifier{_functionDefinition.debugData, newFunctionName},
173
843
        stackParameters | ranges::views::transform([&](NameWithDebugData const& _arg) {
174
662
          return Expression{Identifier{_arg.debugData, newArgumentNames.at(_arg.name)}};
175
662
        }) | ranges::to<std::vector<Expression>>
176
843
      }
177
843
    });
178
843
    _functionDefinition.body.statements.emplace_back(Assignment{
179
843
      _functionDefinition.debugData,
180
843
      {Identifier{_functionDefinition.debugData, _functionDefinition.returnVariables.front().name}},
181
843
      std::make_unique<Expression>(generateMemoryLoad(
182
843
        m_context.dialect,
183
843
        _functionDefinition.debugData,
184
843
        *m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name)
185
843
      ))
186
843
    });
187
843
    return;
188
843
  }
189
190
132k
  if (!memoryVariableInits.empty())
191
469
    _functionDefinition.body.statements = std::move(memoryVariableInits) + std::move(_functionDefinition.body.statements);
192
193
132k
  _functionDefinition.returnVariables = _functionDefinition.returnVariables | ranges::views::filter(
194
132k
    std::not_fn(m_memoryOffsetTracker)
195
132k
  ) | ranges::to<NameWithDebugDataList>;
196
132k
}
197
198
void StackToMemoryMover::operator()(Block& _block)
199
661k
{
200
661k
  using OptionalStatements = std::optional<std::vector<Statement>>;
201
202
661k
  auto rewriteAssignmentOrVariableDeclarationLeftHandSide = [this](
203
661k
    auto& _stmt,
204
661k
    auto& _lhsVars
205
661k
  ) -> OptionalStatements {
206
500k
    using StatementType = std::decay_t<decltype(_stmt)>;
207
208
500k
    auto debugData = _stmt.debugData;
209
500k
    if (_lhsVars.size() == 1)
210
484k
    {
211
484k
      if (auto offset = m_memoryOffsetTracker(_lhsVars.front().name))
212
11.9k
        return generateMemoryStore(
213
11.9k
          m_context.dialect,
214
11.9k
          debugData,
215
11.9k
          *offset,
216
11.9k
          _stmt.value ? *std::move(_stmt.value) : Literal{debugData, LiteralKind::Number, LiteralValue(u256{0})}
217
11.9k
        );
218
472k
      else
219
472k
        return {};
220
484k
    }
221
15.9k
    std::vector<std::optional<LiteralValue>> rhsMemorySlots;
222
15.9k
    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.40k
    else
238
2.40k
      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.9k
    if (
243
52.0k
      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
2.76k
      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
49.2k
      ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) &&
244
15.6k
      !util::contains_if(_lhsVars, m_memoryOffsetTracker)
245
15.9k
    )
246
15.3k
      return {};
247
248
561
    std::vector<Statement> memoryAssignments;
249
561
    std::vector<Statement> variableAssignments;
250
561
    VariableDeclaration tempDecl{debugData, {}, std::move(_stmt.value)};
251
252
561
    yulAssert(rhsMemorySlots.size() == _lhsVars.size(), "");
253
561
    for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots))
254
1.90k
    {
255
1.90k
      std::unique_ptr<Expression> rhs;
256
1.90k
      if (rhsSlot)
257
430
        rhs = std::make_unique<Expression>(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot));
258
1.47k
      else
259
1.47k
      {
260
1.47k
        YulName tempVarName = m_nameDispenser.newName(lhsVar.name);
261
1.47k
        tempDecl.variables.emplace_back(NameWithDebugData{lhsVar.debugData, tempVarName});
262
1.47k
        rhs = std::make_unique<Expression>(Identifier{debugData, tempVarName});
263
1.47k
      }
264
265
1.90k
      if (auto offset = m_memoryOffsetTracker(lhsVar.name))
266
378
        memoryAssignments += generateMemoryStore(
267
378
          m_context.dialect,
268
378
          _stmt.debugData,
269
378
          *offset,
270
378
          std::move(*rhs)
271
378
        );
272
1.52k
      else
273
1.52k
        variableAssignments.emplace_back(StatementType{
274
1.52k
          debugData,
275
1.52k
          { std::move(lhsVar) },
276
1.52k
          std::move(rhs)
277
1.52k
        });
278
1.90k
    }
279
280
561
    std::vector<Statement> result;
281
561
    if (tempDecl.variables.empty())
282
52
      result.emplace_back(ExpressionStatement{debugData, *std::move(tempDecl.value)});
283
509
    else
284
509
      result.emplace_back(std::move(tempDecl));
285
561
    reverse(memoryAssignments.begin(), memoryAssignments.end());
286
561
    result += std::move(memoryAssignments);
287
561
    reverse(variableAssignments.begin(), variableAssignments.end());
288
561
    result += std::move(variableAssignments);
289
561
    return OptionalStatements{std::move(result)};
290
15.9k
  };
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
237k
  ) -> OptionalStatements {
206
237k
    using StatementType = std::decay_t<decltype(_stmt)>;
207
208
237k
    auto debugData = _stmt.debugData;
209
237k
    if (_lhsVars.size() == 1)
210
236k
    {
211
236k
      if (auto offset = m_memoryOffsetTracker(_lhsVars.front().name))
212
2.48k
        return generateMemoryStore(
213
2.48k
          m_context.dialect,
214
2.48k
          debugData,
215
2.48k
          *offset,
216
2.48k
          _stmt.value ? *std::move(_stmt.value) : Literal{debugData, LiteralKind::Number, LiteralValue(u256{0})}
217
2.48k
        );
218
234k
      else
219
234k
        return {};
220
236k
    }
221
1.37k
    std::vector<std::optional<LiteralValue>> rhsMemorySlots;
222
1.37k
    if (_stmt.value)
223
1.37k
    {
224
1.37k
      FunctionCall const* functionCall = std::get_if<FunctionCall>(_stmt.value.get());
225
1.37k
      yulAssert(functionCall, "");
226
1.37k
      if (isBuiltinFunctionCall(*functionCall))
227
0
        rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt);
228
1.37k
      else
229
1.37k
      {
230
1.37k
        yulAssert(std::holds_alternative<Identifier>(functionCall->functionName));
231
1.37k
        rhsMemorySlots =
232
1.37k
          m_functionReturnVariables.at(std::get<Identifier>(functionCall->functionName).name) |
233
1.37k
          ranges::views::transform(m_memoryOffsetTracker) |
234
1.37k
          ranges::to<std::vector<std::optional<LiteralValue>>>;
235
1.37k
      }
236
1.37k
    }
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.37k
    if (
243
1.37k
      ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) &&
244
1.37k
      !util::contains_if(_lhsVars, m_memoryOffsetTracker)
245
1.37k
    )
246
1.37k
      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.37k
  };
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
262k
  ) -> OptionalStatements {
206
262k
    using StatementType = std::decay_t<decltype(_stmt)>;
207
208
262k
    auto debugData = _stmt.debugData;
209
262k
    if (_lhsVars.size() == 1)
210
248k
    {
211
248k
      if (auto offset = m_memoryOffsetTracker(_lhsVars.front().name))
212
9.50k
        return generateMemoryStore(
213
9.50k
          m_context.dialect,
214
9.50k
          debugData,
215
9.50k
          *offset,
216
9.50k
          _stmt.value ? *std::move(_stmt.value) : Literal{debugData, LiteralKind::Number, LiteralValue(u256{0})}
217
9.50k
        );
218
238k
      else
219
238k
        return {};
220
248k
    }
221
14.5k
    std::vector<std::optional<LiteralValue>> rhsMemorySlots;
222
14.5k
    if (_stmt.value)
223
12.1k
    {
224
12.1k
      FunctionCall const* functionCall = std::get_if<FunctionCall>(_stmt.value.get());
225
12.1k
      yulAssert(functionCall, "");
226
12.1k
      if (isBuiltinFunctionCall(*functionCall))
227
0
        rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt);
228
12.1k
      else
229
12.1k
      {
230
12.1k
        yulAssert(std::holds_alternative<Identifier>(functionCall->functionName));
231
12.1k
        rhsMemorySlots =
232
12.1k
          m_functionReturnVariables.at(std::get<Identifier>(functionCall->functionName).name) |
233
12.1k
          ranges::views::transform(m_memoryOffsetTracker) |
234
12.1k
          ranges::to<std::vector<std::optional<LiteralValue>>>;
235
12.1k
      }
236
12.1k
    }
237
2.40k
    else
238
2.40k
      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
14.5k
    if (
243
14.5k
      ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) &&
244
14.2k
      !util::contains_if(_lhsVars, m_memoryOffsetTracker)
245
14.5k
    )
246
13.9k
      return {};
247
248
561
    std::vector<Statement> memoryAssignments;
249
561
    std::vector<Statement> variableAssignments;
250
561
    VariableDeclaration tempDecl{debugData, {}, std::move(_stmt.value)};
251
252
561
    yulAssert(rhsMemorySlots.size() == _lhsVars.size(), "");
253
561
    for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots))
254
1.90k
    {
255
1.90k
      std::unique_ptr<Expression> rhs;
256
1.90k
      if (rhsSlot)
257
430
        rhs = std::make_unique<Expression>(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot));
258
1.47k
      else
259
1.47k
      {
260
1.47k
        YulName tempVarName = m_nameDispenser.newName(lhsVar.name);
261
1.47k
        tempDecl.variables.emplace_back(NameWithDebugData{lhsVar.debugData, tempVarName});
262
1.47k
        rhs = std::make_unique<Expression>(Identifier{debugData, tempVarName});
263
1.47k
      }
264
265
1.90k
      if (auto offset = m_memoryOffsetTracker(lhsVar.name))
266
378
        memoryAssignments += generateMemoryStore(
267
378
          m_context.dialect,
268
378
          _stmt.debugData,
269
378
          *offset,
270
378
          std::move(*rhs)
271
378
        );
272
1.52k
      else
273
1.52k
        variableAssignments.emplace_back(StatementType{
274
1.52k
          debugData,
275
1.52k
          { std::move(lhsVar) },
276
1.52k
          std::move(rhs)
277
1.52k
        });
278
1.90k
    }
279
280
561
    std::vector<Statement> result;
281
561
    if (tempDecl.variables.empty())
282
52
      result.emplace_back(ExpressionStatement{debugData, *std::move(tempDecl.value)});
283
509
    else
284
509
      result.emplace_back(std::move(tempDecl));
285
561
    reverse(memoryAssignments.begin(), memoryAssignments.end());
286
561
    result += std::move(memoryAssignments);
287
561
    reverse(variableAssignments.begin(), variableAssignments.end());
288
561
    result += std::move(variableAssignments);
289
561
    return OptionalStatements{std::move(result)};
290
14.5k
  };
291
292
661k
  util::iterateReplacing(
293
661k
    _block.statements,
294
661k
    [&](Statement& _statement) -> OptionalStatements
295
1.55M
    {
296
1.55M
      visit(_statement);
297
1.55M
      if (auto* assignment = std::get_if<Assignment>(&_statement))
298
237k
        return rewriteAssignmentOrVariableDeclarationLeftHandSide(*assignment, assignment->variableNames);
299
1.31M
      else if (auto* varDecl = std::get_if<VariableDeclaration>(&_statement))
300
262k
        return rewriteAssignmentOrVariableDeclarationLeftHandSide(*varDecl, varDecl->variables);
301
1.05M
      return {};
302
1.55M
    }
303
661k
  );
304
661k
}
305
306
void StackToMemoryMover::visit(Expression& _expression)
307
5.64M
{
308
5.64M
  ASTModifier::visit(_expression);
309
5.64M
  if (Identifier* identifier = std::get_if<Identifier>(&_expression))
310
708k
    if (auto offset = m_memoryOffsetTracker(identifier->name))
311
17.4k
      _expression = generateMemoryLoad(m_context.dialect, identifier->debugData, *offset);
312
5.64M
}
313
314
std::optional<LiteralValue> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(YulName const& _variable) const
315
1.74M
{
316
1.74M
  if (m_memorySlots.count(_variable))
317
34.4k
  {
318
34.4k
    uint64_t slot = m_memorySlots.at(_variable);
319
34.4k
    yulAssert(slot < m_numRequiredSlots, "");
320
34.4k
    auto const memoryOffset = m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1);
321
34.4k
    return valueOfNumberLiteral(toCompactHexWithPrefix(memoryOffset));
322
34.4k
  }
323
1.70M
  else
324
1.70M
    return std::nullopt;
325
1.74M
}
326
327
std::optional<LiteralValue> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(NameWithDebugData const& _variable) const
328
222k
{
329
222k
  return (*this)(_variable.name);
330
222k
}
331
332
std::optional<LiteralValue> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(Identifier const& _variable) const
333
2.76k
{
334
2.76k
  return (*this)(_variable.name);
335
2.76k
}