/src/solidity/libyul/YulStack.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 | | * Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and |
20 | | * Ewasm as output. |
21 | | */ |
22 | | |
23 | | |
24 | | #include <libyul/YulStack.h> |
25 | | |
26 | | #include <libyul/AsmAnalysis.h> |
27 | | #include <libyul/AsmAnalysisInfo.h> |
28 | | #include <libyul/backends/evm/EthAssemblyAdapter.h> |
29 | | #include <libyul/backends/evm/EVMCodeTransform.h> |
30 | | #include <libyul/backends/evm/EVMDialect.h> |
31 | | #include <libyul/backends/evm/EVMObjectCompiler.h> |
32 | | #include <libyul/backends/evm/EVMMetrics.h> |
33 | | #include <libyul/backends/wasm/WasmDialect.h> |
34 | | #include <libyul/backends/wasm/WasmObjectCompiler.h> |
35 | | #include <libyul/backends/wasm/EVMToEwasmTranslator.h> |
36 | | #include <libyul/ObjectParser.h> |
37 | | #include <libyul/optimiser/Suite.h> |
38 | | |
39 | | #include <libevmasm/Assembly.h> |
40 | | #include <liblangutil/Scanner.h> |
41 | | #include <boost/algorithm/string.hpp> |
42 | | #include <optional> |
43 | | |
44 | | using namespace std; |
45 | | using namespace solidity; |
46 | | using namespace solidity::yul; |
47 | | using namespace solidity::langutil; |
48 | | |
49 | | namespace |
50 | | { |
51 | | Dialect const& languageToDialect(YulStack::Language _language, EVMVersion _version) |
52 | 58.7k | { |
53 | 58.7k | switch (_language) |
54 | 58.7k | { |
55 | 0 | case YulStack::Language::Assembly: |
56 | 58.7k | case YulStack::Language::StrictAssembly: |
57 | 58.7k | return EVMDialect::strictAssemblyForEVMObjects(_version); |
58 | 0 | case YulStack::Language::Yul: |
59 | 0 | return EVMDialectTyped::instance(_version); |
60 | 0 | case YulStack::Language::Ewasm: |
61 | 0 | return WasmDialect::instance(); |
62 | 58.7k | } |
63 | 0 | yulAssert(false, ""); |
64 | 0 | return Dialect::yulDeprecated(); |
65 | 0 | } |
66 | | |
67 | | // Duplicated from libsolidity/codegen/CompilerContext.cpp |
68 | | // TODO: refactor and remove duplication |
69 | | evmasm::Assembly::OptimiserSettings translateOptimiserSettings( |
70 | | frontend::OptimiserSettings const& _settings, |
71 | | langutil::EVMVersion _evmVersion |
72 | | ) |
73 | 0 | { |
74 | | // Constructing it this way so that we notice changes in the fields. |
75 | 0 | evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, _evmVersion, 0}; |
76 | 0 | asmSettings.runInliner = _settings.runInliner; |
77 | 0 | asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; |
78 | 0 | asmSettings.runPeephole = _settings.runPeephole; |
79 | 0 | asmSettings.runDeduplicate = _settings.runDeduplicate; |
80 | 0 | asmSettings.runCSE = _settings.runCSE; |
81 | 0 | asmSettings.runConstantOptimiser = _settings.runConstantOptimiser; |
82 | 0 | asmSettings.expectedExecutionsPerDeployment = _settings.expectedExecutionsPerDeployment; |
83 | 0 | asmSettings.evmVersion = _evmVersion; |
84 | |
|
85 | 0 | return asmSettings; |
86 | 0 | } |
87 | | |
88 | | } |
89 | | |
90 | | |
91 | | CharStream const& YulStack::charStream(string const& _sourceName) const |
92 | 0 | { |
93 | 0 | yulAssert(m_charStream, ""); |
94 | 0 | yulAssert(m_charStream->name() == _sourceName, ""); |
95 | 0 | return *m_charStream; |
96 | 0 | } |
97 | | |
98 | | bool YulStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source) |
99 | 17.9k | { |
100 | 17.9k | m_errors.clear(); |
101 | 17.9k | m_analysisSuccessful = false; |
102 | 17.9k | m_charStream = make_unique<CharStream>(_source, _sourceName); |
103 | 17.9k | shared_ptr<Scanner> scanner = make_shared<Scanner>(*m_charStream); |
104 | 17.9k | m_parserResult = ObjectParser(m_errorReporter, languageToDialect(m_language, m_evmVersion)).parse(scanner, false); |
105 | 17.9k | if (!m_errorReporter.errors().empty()) |
106 | 3.12k | return false; |
107 | 14.7k | yulAssert(m_parserResult, ""); |
108 | 14.7k | yulAssert(m_parserResult->code, ""); |
109 | | |
110 | 14.7k | return analyzeParsed(); |
111 | 14.7k | } |
112 | | |
113 | | void YulStack::optimize() |
114 | 12.6k | { |
115 | 12.6k | if (!m_optimiserSettings.runYulOptimiser) |
116 | 0 | return; |
117 | | |
118 | 12.6k | yulAssert(m_analysisSuccessful, "Analysis was not successful."); |
119 | | |
120 | 12.6k | m_analysisSuccessful = false; |
121 | 12.6k | yulAssert(m_parserResult, ""); |
122 | 12.6k | optimize(*m_parserResult, true); |
123 | 12.6k | yulAssert(analyzeParsed(), "Invalid source code after optimization."); |
124 | 12.6k | } |
125 | | |
126 | | void YulStack::translate(YulStack::Language _targetLanguage) |
127 | 0 | { |
128 | 0 | if (m_language == _targetLanguage) |
129 | 0 | return; |
130 | | |
131 | 0 | yulAssert( |
132 | 0 | m_language == Language::StrictAssembly && _targetLanguage == Language::Ewasm, |
133 | 0 | "Invalid language combination" |
134 | 0 | ); |
135 | | |
136 | 0 | *m_parserResult = EVMToEwasmTranslator( |
137 | 0 | languageToDialect(m_language, m_evmVersion), |
138 | 0 | *this |
139 | 0 | ).run(*parserResult()); |
140 | |
|
141 | 0 | m_language = _targetLanguage; |
142 | 0 | } |
143 | | |
144 | | bool YulStack::analyzeParsed() |
145 | 27.4k | { |
146 | 27.4k | yulAssert(m_parserResult, ""); |
147 | 27.4k | m_analysisSuccessful = analyzeParsed(*m_parserResult); |
148 | 27.4k | return m_analysisSuccessful; |
149 | 27.4k | } |
150 | | |
151 | | bool YulStack::analyzeParsed(Object& _object) |
152 | 27.9k | { |
153 | 27.9k | yulAssert(_object.code, ""); |
154 | 27.9k | _object.analysisInfo = make_shared<AsmAnalysisInfo>(); |
155 | | |
156 | 27.9k | AsmAnalyzer analyzer( |
157 | 27.9k | *_object.analysisInfo, |
158 | 27.9k | m_errorReporter, |
159 | 27.9k | languageToDialect(m_language, m_evmVersion), |
160 | 27.9k | {}, |
161 | 27.9k | _object.qualifiedDataNames() |
162 | 27.9k | ); |
163 | 27.9k | bool success = analyzer.analyze(*_object.code); |
164 | 27.9k | for (auto& subNode: _object.subObjects) |
165 | 791 | if (auto subObject = dynamic_cast<Object*>(subNode.get())) |
166 | 515 | if (!analyzeParsed(*subObject)) |
167 | 84 | success = false; |
168 | 27.9k | return success; |
169 | 27.9k | } |
170 | | |
171 | | void YulStack::compileEVM(AbstractAssembly& _assembly, bool _optimize) const |
172 | 0 | { |
173 | 0 | EVMDialect const* dialect = nullptr; |
174 | 0 | switch (m_language) |
175 | 0 | { |
176 | 0 | case Language::Assembly: |
177 | 0 | case Language::StrictAssembly: |
178 | 0 | dialect = &EVMDialect::strictAssemblyForEVMObjects(m_evmVersion); |
179 | 0 | break; |
180 | 0 | case Language::Yul: |
181 | 0 | dialect = &EVMDialectTyped::instance(m_evmVersion); |
182 | 0 | break; |
183 | 0 | default: |
184 | 0 | yulAssert(false, "Invalid language."); |
185 | 0 | break; |
186 | 0 | } |
187 | | |
188 | 0 | EVMObjectCompiler::compile(*m_parserResult, _assembly, *dialect, _optimize); |
189 | 0 | } |
190 | | |
191 | | void YulStack::optimize(Object& _object, bool _isCreation) |
192 | 12.8k | { |
193 | 12.8k | yulAssert(_object.code, ""); |
194 | 12.8k | yulAssert(_object.analysisInfo, ""); |
195 | 12.8k | for (auto& subNode: _object.subObjects) |
196 | 322 | if (auto subObject = dynamic_cast<Object*>(subNode.get())) |
197 | 202 | { |
198 | 202 | bool isCreation = !boost::ends_with(subObject->name.str(), "_deployed"); |
199 | 202 | optimize(*subObject, isCreation); |
200 | 202 | } |
201 | | |
202 | 12.8k | Dialect const& dialect = languageToDialect(m_language, m_evmVersion); |
203 | 12.8k | unique_ptr<GasMeter> meter; |
204 | 12.8k | if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&dialect)) |
205 | 12.8k | meter = make_unique<GasMeter>(*evmDialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); |
206 | 12.8k | OptimiserSuite::run( |
207 | 12.8k | dialect, |
208 | 12.8k | meter.get(), |
209 | 12.8k | _object, |
210 | 12.8k | m_optimiserSettings.optimizeStackAllocation, |
211 | 12.8k | m_optimiserSettings.yulOptimiserSteps, |
212 | 12.8k | _isCreation ? nullopt : make_optional(m_optimiserSettings.expectedExecutionsPerDeployment), |
213 | 12.8k | {} |
214 | 12.8k | ); |
215 | 12.8k | } |
216 | | |
217 | | MachineAssemblyObject YulStack::assemble(Machine _machine) const |
218 | 0 | { |
219 | 0 | yulAssert(m_analysisSuccessful, ""); |
220 | 0 | yulAssert(m_parserResult, ""); |
221 | 0 | yulAssert(m_parserResult->code, ""); |
222 | 0 | yulAssert(m_parserResult->analysisInfo, ""); |
223 | | |
224 | 0 | switch (_machine) |
225 | 0 | { |
226 | 0 | case Machine::EVM: |
227 | 0 | return assembleWithDeployed().first; |
228 | 0 | case Machine::Ewasm: |
229 | 0 | { |
230 | 0 | yulAssert(m_language == Language::Ewasm, ""); |
231 | 0 | Dialect const& dialect = languageToDialect(m_language, EVMVersion{}); |
232 | |
|
233 | 0 | MachineAssemblyObject object; |
234 | 0 | auto result = WasmObjectCompiler::compile(*m_parserResult, dialect); |
235 | 0 | object.assembly = std::move(result.first); |
236 | 0 | object.bytecode = make_shared<evmasm::LinkerObject>(); |
237 | 0 | object.bytecode->bytecode = std::move(result.second); |
238 | 0 | return object; |
239 | 0 | } |
240 | 0 | } |
241 | | // unreachable |
242 | 0 | return MachineAssemblyObject(); |
243 | 0 | } |
244 | | |
245 | | std::pair<MachineAssemblyObject, MachineAssemblyObject> |
246 | | YulStack::assembleWithDeployed(optional<string_view> _deployName) const |
247 | 0 | { |
248 | 0 | auto [creationAssembly, deployedAssembly] = assembleEVMWithDeployed(_deployName); |
249 | 0 | yulAssert(creationAssembly, ""); |
250 | 0 | yulAssert(m_charStream, ""); |
251 | | |
252 | 0 | MachineAssemblyObject creationObject; |
253 | 0 | creationObject.bytecode = make_shared<evmasm::LinkerObject>(creationAssembly->assemble()); |
254 | 0 | yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables."); |
255 | 0 | creationObject.assembly = creationAssembly->assemblyString(m_debugInfoSelection); |
256 | 0 | creationObject.sourceMappings = make_unique<string>( |
257 | 0 | evmasm::AssemblyItem::computeSourceMapping( |
258 | 0 | creationAssembly->items(), |
259 | 0 | {{m_charStream->name(), 0}} |
260 | 0 | ) |
261 | 0 | ); |
262 | |
|
263 | 0 | MachineAssemblyObject deployedObject; |
264 | 0 | if (deployedAssembly) |
265 | 0 | { |
266 | 0 | deployedObject.bytecode = make_shared<evmasm::LinkerObject>(deployedAssembly->assemble()); |
267 | 0 | deployedObject.assembly = deployedAssembly->assemblyString(m_debugInfoSelection); |
268 | 0 | deployedObject.sourceMappings = make_unique<string>( |
269 | 0 | evmasm::AssemblyItem::computeSourceMapping( |
270 | 0 | deployedAssembly->items(), |
271 | 0 | {{m_charStream->name(), 0}} |
272 | 0 | ) |
273 | 0 | ); |
274 | 0 | } |
275 | |
|
276 | 0 | return {std::move(creationObject), std::move(deployedObject)}; |
277 | 0 | } |
278 | | |
279 | | std::pair<std::shared_ptr<evmasm::Assembly>, std::shared_ptr<evmasm::Assembly>> |
280 | | YulStack::assembleEVMWithDeployed(optional<string_view> _deployName) const |
281 | 0 | { |
282 | 0 | yulAssert(m_analysisSuccessful, ""); |
283 | 0 | yulAssert(m_parserResult, ""); |
284 | 0 | yulAssert(m_parserResult->code, ""); |
285 | 0 | yulAssert(m_parserResult->analysisInfo, ""); |
286 | | |
287 | 0 | evmasm::Assembly assembly(true, {}); |
288 | 0 | EthAssemblyAdapter adapter(assembly); |
289 | 0 | compileEVM(adapter, m_optimiserSettings.optimizeStackAllocation); |
290 | |
|
291 | 0 | assembly.optimise(translateOptimiserSettings(m_optimiserSettings, m_evmVersion)); |
292 | |
|
293 | 0 | optional<size_t> subIndex; |
294 | | |
295 | | // Pick matching assembly if name was given |
296 | 0 | if (_deployName.has_value()) |
297 | 0 | { |
298 | 0 | for (size_t i = 0; i < assembly.numSubs(); i++) |
299 | 0 | if (assembly.sub(i).name() == _deployName) |
300 | 0 | { |
301 | 0 | subIndex = i; |
302 | 0 | break; |
303 | 0 | } |
304 | |
|
305 | 0 | solAssert(subIndex.has_value(), "Failed to find object to be deployed."); |
306 | 0 | } |
307 | | // Otherwise use heuristic: If there is a single sub-assembly, this is likely the object to be deployed. |
308 | 0 | else if (assembly.numSubs() == 1) |
309 | 0 | subIndex = 0; |
310 | | |
311 | 0 | if (subIndex.has_value()) |
312 | 0 | { |
313 | 0 | evmasm::Assembly& runtimeAssembly = assembly.sub(*subIndex); |
314 | 0 | return {make_shared<evmasm::Assembly>(assembly), make_shared<evmasm::Assembly>(runtimeAssembly)}; |
315 | 0 | } |
316 | | |
317 | 0 | return {make_shared<evmasm::Assembly>(assembly), {}}; |
318 | 0 | } |
319 | | |
320 | | string YulStack::print( |
321 | | CharStreamProvider const* _soliditySourceProvider |
322 | | ) const |
323 | 0 | { |
324 | 0 | yulAssert(m_parserResult, ""); |
325 | 0 | yulAssert(m_parserResult->code, ""); |
326 | 0 | return m_parserResult->toString(&languageToDialect(m_language, m_evmVersion), m_debugInfoSelection, _soliditySourceProvider) + "\n"; |
327 | 0 | } |
328 | | |
329 | | shared_ptr<Object> YulStack::parserResult() const |
330 | 54.5k | { |
331 | 54.5k | yulAssert(m_analysisSuccessful, "Analysis was not successful."); |
332 | 54.5k | yulAssert(m_parserResult, ""); |
333 | 54.5k | yulAssert(m_parserResult->code, ""); |
334 | 54.5k | return m_parserResult; |
335 | 54.5k | } |