Coverage Report

Created: 2025-09-08 08:10

/src/solidity/libyul/backends/evm/NoOutputAssembly.cpp
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
 * Assembly interface that ignores everything. Can be used as a backend for a compilation dry-run.
20
 */
21
22
#include <libyul/backends/evm/NoOutputAssembly.h>
23
24
#include <libyul/AST.h>
25
#include <libyul/Exceptions.h>
26
27
#include <libevmasm/Instruction.h>
28
29
#include <range/v3/view/enumerate.hpp>
30
#include <range/v3/view/iota.hpp>
31
32
using namespace solidity;
33
using namespace solidity::yul;
34
using namespace solidity::util;
35
using namespace solidity::langutil;
36
37
namespace
38
{
39
40
void modifyBuiltinToNoOutput(BuiltinFunctionForEVM& _builtin)
41
834
{
42
834
  _builtin.generateCode = [_builtin](FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&)
43
839k
  {
44
839k
    for (size_t i: ranges::views::iota(0u, _call.arguments.size()))
45
1.72M
      if (!_builtin.literalArgument(i))
46
1.69M
        _assembly.appendInstruction(evmasm::Instruction::POP);
47
48
1.41M
    for (size_t i = 0; i < _builtin.numReturns; i++)
49
573k
      _assembly.appendConstant(u256(0));
50
839k
  };
51
834
}
52
53
}
54
55
void NoOutputAssembly::appendInstruction(evmasm::Instruction _instr)
56
6.55M
{
57
6.55M
  m_stackHeight += instructionInfo(_instr, m_evmVersion).ret - instructionInfo(_instr, m_evmVersion).args;
58
6.55M
}
59
60
void NoOutputAssembly::appendConstant(u256 const&)
61
2.02M
{
62
2.02M
  appendInstruction(evmasm::pushInstruction(1));
63
2.02M
}
64
65
void NoOutputAssembly::appendLabel(LabelID)
66
532k
{
67
532k
  appendInstruction(evmasm::Instruction::JUMPDEST);
68
532k
}
69
70
void NoOutputAssembly::appendLabelReference(LabelID)
71
531k
{
72
531k
  appendInstruction(evmasm::pushInstruction(1));
73
531k
}
74
75
NoOutputAssembly::LabelID NoOutputAssembly::newLabelId()
76
532k
{
77
532k
  return 1;
78
532k
}
79
80
AbstractAssembly::LabelID NoOutputAssembly::namedLabel(std::string const&, size_t, size_t, std::optional<size_t>)
81
0
{
82
0
  return 1;
83
0
}
84
85
void NoOutputAssembly::appendLinkerSymbol(std::string const&)
86
0
{
87
0
  yulAssert(false, "Linker symbols not yet implemented.");
88
0
}
89
90
void NoOutputAssembly::appendVerbatim(bytes, size_t _arguments, size_t _returnVariables)
91
0
{
92
0
  m_stackHeight += static_cast<int>(_returnVariables) - static_cast<int>(_arguments);
93
0
}
94
95
void NoOutputAssembly::appendJump(int _stackDiffAfter, JumpType)
96
394k
{
97
394k
  appendInstruction(evmasm::Instruction::JUMP);
98
394k
  m_stackHeight += _stackDiffAfter;
99
394k
}
100
101
void NoOutputAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType)
102
300k
{
103
300k
  appendLabelReference(_labelId);
104
300k
  appendJump(_stackDiffAfter, _jumpType);
105
300k
}
106
107
void NoOutputAssembly::appendJumpToIf(LabelID _labelId, JumpType)
108
72.7k
{
109
72.7k
  appendLabelReference(_labelId);
110
72.7k
  appendInstruction(evmasm::Instruction::JUMPI);
111
72.7k
}
112
113
void NoOutputAssembly::appendAssemblySize()
114
0
{
115
0
  appendInstruction(evmasm::Instruction::PUSH1);
116
0
}
117
118
void NoOutputAssembly::appendDupN(size_t)
119
0
{
120
0
  m_stackHeight++;
121
0
}
122
123
std::pair<std::shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> NoOutputAssembly::createSubAssembly(bool, std::string)
124
0
{
125
0
  yulAssert(false, "Sub assemblies not implemented.");
126
0
  return {};
127
0
}
128
129
AbstractAssembly::FunctionID NoOutputAssembly::registerFunction(uint8_t _args, uint8_t _rets, bool)
130
0
{
131
0
  yulAssert(m_context.numFunctions <= std::numeric_limits<AbstractAssembly::FunctionID>::max());
132
0
  AbstractAssembly::FunctionID id = static_cast<AbstractAssembly::FunctionID>(m_context.numFunctions++);
133
0
  m_context.functionSignatures[id] = {_args, _rets};
134
0
  return id;
135
0
}
136
137
void NoOutputAssembly::beginFunction(FunctionID _functionID)
138
0
{
139
0
  yulAssert(m_currentFunctionID == 0, "Attempted to begin a function before ending the last one.");
140
0
  yulAssert(m_context.functionSignatures.count(_functionID) == 1, "Filling unregistered function.");
141
0
  yulAssert(m_stackHeight == 0, "Non-empty stack on beginFunction call.");
142
0
  m_currentFunctionID = _functionID;
143
0
}
144
145
void NoOutputAssembly::endFunction()
146
0
{
147
0
  yulAssert(m_currentFunctionID != 0, "End function without begin function.");
148
0
  auto const [_, rets] = m_context.functionSignatures.at(m_currentFunctionID);
149
0
  yulAssert(m_stackHeight == rets, "Stack height mismatch at function end.");
150
0
  m_currentFunctionID = 0;
151
0
}
152
153
void NoOutputAssembly::appendFunctionCall(FunctionID _functionID)
154
0
{
155
0
  auto [args, rets] = m_context.functionSignatures.at(_functionID);
156
0
  m_stackHeight += static_cast<int>(rets) - static_cast<int>(args);
157
0
  solAssert(m_stackHeight >= 0);
158
0
}
159
160
void NoOutputAssembly::appendFunctionReturn()
161
0
{
162
0
  yulAssert(m_currentFunctionID != 0, "End function without begin function.");
163
0
  auto const [_, rets] = m_context.functionSignatures.at(m_currentFunctionID);
164
0
  yulAssert(m_stackHeight == rets, "Stack height mismatch at function end.");
165
0
}
166
167
void NoOutputAssembly::appendDataOffset(std::vector<AbstractAssembly::SubID> const&)
168
0
{
169
0
  appendInstruction(evmasm::Instruction::PUSH1);
170
0
}
171
172
void NoOutputAssembly::appendDataSize(std::vector<AbstractAssembly::SubID> const&)
173
0
{
174
0
  appendInstruction(evmasm::Instruction::PUSH1);
175
0
}
176
177
AbstractAssembly::SubID NoOutputAssembly::appendData(bytes const&)
178
0
{
179
0
  return {1};
180
0
}
181
182
183
void NoOutputAssembly::appendImmutable(std::string const&)
184
0
{
185
0
  yulAssert(false, "loadimmutable not implemented.");
186
0
}
187
188
void NoOutputAssembly::appendImmutableAssignment(std::string const&)
189
0
{
190
0
  yulAssert(false, "setimmutable not implemented.");
191
0
}
192
193
void NoOutputAssembly::appendAuxDataLoadN(uint16_t)
194
0
{
195
0
  yulAssert(false, "auxdataloadn not implemented.");
196
0
}
197
198
void NoOutputAssembly::appendEOFCreate(ContainerID)
199
0
{
200
0
  yulAssert(false, "eofcreate not implemented.");
201
202
0
}
203
void NoOutputAssembly::appendReturnContract(ContainerID)
204
0
{
205
0
  yulAssert(false, "returncontract not implemented.");
206
0
}
207
208
NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom):
209
  EVMDialect(_copyFrom.evmVersion(), _copyFrom.eofVersion(), _copyFrom.providesObjectAccess())
210
21.5k
{
211
  // save the modified functions here - note that this only has to be done once because we modify all of
212
  // them in one go, later reference pointers to this static vector
213
21.5k
  static std::vector<BuiltinFunctionForEVM> noOutputBuiltins = defineNoOutputBuiltins();
214
215
21.5k
  yulAssert(m_functions.size() == noOutputBuiltins.size(), "Function count mismatch.");
216
21.5k
  for (auto const& [index, builtinFunction]: m_functions | ranges::views::enumerate)
217
2.99M
  {
218
2.99M
    if (builtinFunction)
219
1.70M
      m_functions[index] = &noOutputBuiltins[index];
220
1.28M
    else
221
1.28M
      m_functions[index] = nullptr;
222
2.99M
  }
223
21.5k
}
224
225
BuiltinFunctionForEVM const& NoOutputEVMDialect::builtin(BuiltinHandle const& _handle) const
226
2.54M
{
227
2.54M
  if (isVerbatimHandle(_handle))
228
    // for verbatims the modification is performed lazily as they are stored in a lookup table fashion
229
0
    if (
230
0
      auto& builtin = m_verbatimFunctions[_handle.id];
231
0
      !builtin
232
0
    )
233
0
    {
234
0
      builtin = std::make_unique<BuiltinFunctionForEVM>(createVerbatimFunctionFromHandle(_handle));
235
0
      modifyBuiltinToNoOutput(*builtin);
236
0
    }
237
2.54M
  return EVMDialect::builtin(_handle);
238
2.54M
}
239
240
std::vector<BuiltinFunctionForEVM> NoOutputEVMDialect::defineNoOutputBuiltins()
241
6
{
242
6
  std::vector<BuiltinFunctionForEVM> modifiedBuiltins;
243
6
  modifiedBuiltins.reserve(allBuiltins().functions().size());
244
245
6
  for (auto const& [_, builtin]: allBuiltins().functions())
246
834
  {
247
834
    auto noOutputFunction = builtin;
248
834
    modifyBuiltinToNoOutput(noOutputFunction);
249
834
    modifiedBuiltins.push_back(std::move(noOutputFunction));
250
834
  }
251
252
6
  return modifiedBuiltins;
253
6
}