From cb74a45fd61ad46775fa5afb7b1cd90bcc992fd3 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 14 Jan 2021 13:02:14 +0100 Subject: [PATCH] Add inlining for old optimizer. --- Changelog.md | 1 + docs/metadata.rst | 2 + docs/using-the-compiler.rst | 3 + libevmasm/Assembly.cpp | 11 + libevmasm/Assembly.h | 1 + libevmasm/CMakeLists.txt | 2 + libevmasm/GasMeter.cpp | 8 + libevmasm/GasMeter.h | 11 + libevmasm/Inliner.cpp | 259 ++++++++++++++++++ libevmasm/Inliner.h | 83 ++++++ libsolidity/codegen/CompilerContext.cpp | 3 +- libsolidity/interface/CompilerStack.cpp | 1 + libsolidity/interface/OptimiserSettings.h | 4 + libsolidity/interface/StandardCompiler.cpp | 2 + .../optimizer_BlockDeDuplicator/output | 12 +- test/cmdlineTests/optimizer_inliner_add/args | 1 + .../optimizer_inliner_add/input.sol | 17 ++ .../cmdlineTests/optimizer_inliner_add/output | 86 ++++++ .../args | 1 + .../input.sol | 9 + .../output | 80 ++++++ .../optimizer_inliner_dynamic_reference/args | 1 + .../input.sol | 11 + .../output | 198 +++++++++++++ .../args | 1 + .../input.sol | 11 + .../output | 203 ++++++++++++++ test/cmdlineTests/optimizer_inliner_inc/args | 1 + .../optimizer_inliner_inc/input.sol | 17 ++ .../cmdlineTests/optimizer_inliner_inc/output | 83 ++++++ .../optimizer_inliner_multireturn/args | 1 + .../optimizer_inliner_multireturn/input.sol | 17 ++ .../optimizer_inliner_multireturn/output | 89 ++++++ test/libevmasm/Optimiser.cpp | 172 +++++++++++- test/libsolidity/Assembly.cpp | 9 +- test/libsolidity/GasCosts.cpp | 12 +- test/libsolidity/GasMeter.cpp | 4 +- test/libsolidity/StandardCompiler.cpp | 2 +- test/libsolidity/gasTests/abiv2_optimised.sol | 10 +- .../gasTests/dispatch_large_optimised.sol | 50 ++-- .../gasTests/dispatch_medium_optimised.sol | 24 +- .../gasTests/dispatch_small_optimised.sol | 10 +- test/libsolidity/gasTests/exp_optimized.sol | 14 +- 43 files changed, 1463 insertions(+), 74 deletions(-) create mode 100644 libevmasm/Inliner.cpp create mode 100644 libevmasm/Inliner.h create mode 100644 test/cmdlineTests/optimizer_inliner_add/args create mode 100644 test/cmdlineTests/optimizer_inliner_add/input.sol create mode 100644 test/cmdlineTests/optimizer_inliner_add/output create mode 100644 test/cmdlineTests/optimizer_inliner_call_from_constructor/args create mode 100644 test/cmdlineTests/optimizer_inliner_call_from_constructor/input.sol create mode 100644 test/cmdlineTests/optimizer_inliner_call_from_constructor/output create mode 100644 test/cmdlineTests/optimizer_inliner_dynamic_reference/args create mode 100644 test/cmdlineTests/optimizer_inliner_dynamic_reference/input.sol create mode 100644 test/cmdlineTests/optimizer_inliner_dynamic_reference/output create mode 100644 test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/args create mode 100644 test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/input.sol create mode 100644 test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/output create mode 100644 test/cmdlineTests/optimizer_inliner_inc/args create mode 100644 test/cmdlineTests/optimizer_inliner_inc/input.sol create mode 100644 test/cmdlineTests/optimizer_inliner_inc/output create mode 100644 test/cmdlineTests/optimizer_inliner_multireturn/args create mode 100644 test/cmdlineTests/optimizer_inliner_multireturn/input.sol create mode 100644 test/cmdlineTests/optimizer_inliner_multireturn/output diff --git a/Changelog.md b/Changelog.md index b2ca62659..4b04965c1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Language Features: Compiler Features: * AST: Export NatSpec comments above each statement as their documentation. + * Optimizer: Simple inlining when jumping to small blocks that jump again after a few side-effect free opcodes. Bugfixes: diff --git a/docs/metadata.rst b/docs/metadata.rst index bd827a0d8..e58b2fc79 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -80,6 +80,8 @@ explanatory purposes. details: { // peephole defaults to "true" peephole: true, + // inliner defaults to "true" + inliner: true, // jumpdestRemover defaults to "true" jumpdestRemover: true, orderLiterals: false, diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 680681382..eff1f9a4e 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -246,6 +246,9 @@ Input Description // The peephole optimizer is always on if no details are given, // use details to switch it off. "peephole": true, + // The inliner is always on if no details are given, + // use details to switch it off. + "inliner": true, // The unused jumpdest remover is always on if no details are given, // use details to switch it off. "jumpdestRemover": true, diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 3abcafc55..b0ac01381 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -375,6 +376,7 @@ Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreat { OptimiserSettings settings; settings.isCreation = _isCreation; + settings.runInliner = true; settings.runJumpdestRemover = true; settings.runPeephole = true; if (_enable) @@ -421,6 +423,15 @@ map Assembly::optimiseInternal( { count = 0; + if (_settings.runInliner) + Inliner{ + m_items, + _tagsReferencedFromOutside, + _settings.expectedExecutionsPerDeployment, + _settings.isCreation, + _settings.evmVersion + }.optimise(); + if (_settings.runJumpdestRemover) { JumpdestRemover jumpdestOpt{m_items}; diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 354304dcb..f45738203 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -106,6 +106,7 @@ public: struct OptimiserSettings { bool isCreation = false; + bool runInliner = false; bool runJumpdestRemover = false; bool runPeephole = false; bool runDeduplicate = false; diff --git a/libevmasm/CMakeLists.txt b/libevmasm/CMakeLists.txt index 9ef622f09..5b3afcdb6 100644 --- a/libevmasm/CMakeLists.txt +++ b/libevmasm/CMakeLists.txt @@ -16,6 +16,8 @@ set(sources ExpressionClasses.h GasMeter.cpp GasMeter.h + Inliner.cpp + Inliner.h Instruction.cpp Instruction.h JumpdestRemover.cpp diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index eb259ba62..089a371c6 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -286,3 +286,11 @@ u256 GasMeter::dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersio assertThrow(gas < bigint(u256(-1)), OptimizerException, "Gas cost exceeds 256 bits."); return u256(gas); } + + +u256 GasMeter::dataGas(uint64_t _length, bool _inCreation, langutil::EVMVersion _evmVersion) +{ + bigint gas = bigint(_length) * (_inCreation ? GasCosts::txDataNonZeroGas(_evmVersion) : GasCosts::createDataGas); + assertThrow(gas < bigint(u256(-1)), OptimizerException, "Gas cost exceeds 256 bits."); + return u256(gas); +} diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index ddba491aa..ef7bc7073 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -125,6 +125,12 @@ public: static GasConsumption infinite() { return GasConsumption(0, true); } GasConsumption& operator+=(GasConsumption const& _other); + GasConsumption operator+(GasConsumption const& _other) const + { + GasConsumption result = *this; + result += _other; + return result; + } bool operator<(GasConsumption const& _other) const { return std::make_pair(isInfinite, value) < std::make_pair(_other.isInfinite, _other.value); @@ -154,6 +160,11 @@ public: /// otherwise code will be stored and have to pay "createDataGas" cost. static u256 dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion); + /// @returns the gas cost of non-zero data of the supplied length, depending whether it is in creation code, or not. + /// In case of @a _inCreation, the data is only sent as a transaction and is not stored, whereas + /// otherwise code will be stored and have to pay "createDataGas" cost. + static u256 dataGas(uint64_t _length, bool _inCreation, langutil::EVMVersion _evmVersion); + private: /// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise. GasConsumption wordGas(u256 const& _multiplier, ExpressionClasses::Id _value); diff --git a/libevmasm/Inliner.cpp b/libevmasm/Inliner.cpp new file mode 100644 index 000000000..3bb93ab18 --- /dev/null +++ b/libevmasm/Inliner.cpp @@ -0,0 +1,259 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * @file Inliner.cpp + * Inlines small code snippets by replacing JUMP with a copy of the code jumped to. + */ + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + + +using namespace std; +using namespace solidity; +using namespace solidity::evmasm; + + +namespace +{ +/// @returns an estimation of the runtime gas cost of the AsssemblyItems in @a _itemRange. +template +u256 executionCost(RangeType const& _itemRange, langutil::EVMVersion _evmVersion) +{ + GasMeter gasMeter{std::make_shared(), _evmVersion}; + auto gasConsumption = ranges::accumulate(_itemRange | ranges::views::transform( + [&gasMeter](auto const& _item) { return gasMeter.estimateMax(_item, false); } + ), GasMeter::GasConsumption()); + if (gasConsumption.isInfinite) + return numeric_limits::max(); + else + return gasConsumption.value; +} +/// @returns an estimation of the code size in bytes needed for the AssemblyItems in @a _itemRange. +template +uint64_t codeSize(RangeType const& _itemRange) +{ + return ranges::accumulate(_itemRange | ranges::views::transform( + [](auto const& _item) { return _item.bytesRequired(2); } + ), 0u); +} +/// @returns the tag id, if @a _item is a PushTag or Tag into the current subassembly, nullopt otherwise. +optional getLocalTag(AssemblyItem const& _item) +{ + if (_item.type() != PushTag && _item.type() != Tag) + return nullopt; + auto [subId, tag] = _item.splitForeignPushTag(); + if (subId != numeric_limits::max()) + return nullopt; + return tag; +} +} + +bool Inliner::isInlineCandidate(size_t _tag, ranges::span _items) const +{ + assertThrow(_items.size() > 0, OptimizerException, ""); + + // Only consider blocks that end in a JUMP for now. This can e.g. be extended to include transaction terminating + // instructions as well in the future. + if (_items.back() != Instruction::JUMP) + return false; + + // Never inline tags that reference themselves. + for (AssemblyItem const& item: _items) + if (item.type() == PushTag) + if (getLocalTag(item) == _tag) + return false; + + return true; +} + +map Inliner::determineInlinableBlocks(AssemblyItems const& _items) const +{ + std::map> inlinableBlockItems; + std::map numPushTags; + std::optional lastTag; + for (auto&& [index, item]: _items | ranges::views::enumerate) + { + // The number of PushTags approximates the number of calls to a block. + if (item.type() == PushTag) + if (optional tag = getLocalTag(item)) + ++numPushTags[*tag]; + + // We can only inline blocks with straight control flow that end in a jump. + // Using breaksCSEAnalysisBlock will hopefully allow the return jump to be optimized after inlining. + if (lastTag && SemanticInformation::breaksCSEAnalysisBlock(item, false)) + { + ranges::span block = _items | ranges::views::slice(*lastTag + 1, index + 1); + if (optional tag = getLocalTag(_items[*lastTag])) + if (isInlineCandidate(*tag, block)) + inlinableBlockItems[*tag] = block; + lastTag.reset(); + } + + if (item.type() == Tag) + { + assertThrow(getLocalTag(item), OptimizerException, ""); + lastTag = index; + } + } + + // Store the number of PushTags alongside the assembly items and discard tags that are never pushed. + map result; + for (auto&& [tag, items]: inlinableBlockItems) + if (uint64_t const* numPushes = util::valueOrNullptr(numPushTags, tag)) + result.emplace(tag, InlinableBlock{items, *numPushes}); + return result; +} + +bool Inliner::shouldInlineFullFunctionBody(size_t _tag, ranges::span _block, uint64_t _pushTagCount) const +{ + // Accumulate size of the inline candidate block in bytes (without the return jump). + uint64_t functionBodySize = codeSize(ranges::views::drop_last(_block, 1)); + + // Use the number of push tags as approximation of the average number of calls to the function per run. + uint64_t numberOfCalls = _pushTagCount; + // Also use the number of push tags as approximation of the number of call sites to the function. + uint64_t numberOfCallSites = _pushTagCount; + + static AssemblyItems const uninlinedCallSitePattern = { + AssemblyItem{PushTag}, + AssemblyItem{PushTag}, + AssemblyItem{Instruction::JUMP}, + AssemblyItem{Tag} + }; + static AssemblyItems const uninlinedFunctionPattern = { + AssemblyItem{Tag}, + // Actual function body of size functionBodySize. Handled separately below. + AssemblyItem{Instruction::JUMP} + }; + + // Both the call site and jump site pattern is executed for each call. + // Since the function body has to be executed equally often both with and without inlining, + // it can be ignored. + bigint uninlinedExecutionCost = numberOfCalls * ( + executionCost(uninlinedCallSitePattern, m_evmVersion) + + executionCost(uninlinedFunctionPattern, m_evmVersion) + ); + // Each call site deposits the call site pattern, whereas the jump site pattern and the function itself are deposited once. + bigint uninlinedDepositCost = GasMeter::dataGas( + numberOfCallSites * codeSize(uninlinedCallSitePattern) + + codeSize(uninlinedFunctionPattern) + + functionBodySize, + m_isCreation, + m_evmVersion + ); + // When inlining the execution cost beyond the actual function execution is zero, + // but for each call site a copy of the function is deposited. + bigint inlinedDepositCost = GasMeter::dataGas( + numberOfCallSites * functionBodySize, + m_isCreation, + m_evmVersion + ); + // If the block is referenced from outside the current subassembly, the original function cannot be removed. + // Note that the function also cannot always be removed, if it is not referenced from outside, but in that case + // the heuristics is optimistic. + if (m_tagsReferencedFromOutside.count(_tag)) + inlinedDepositCost += GasMeter::dataGas( + uninlinedFunctionPattern.size() + functionBodySize, + m_isCreation, + m_evmVersion + ); + + // If the estimated runtime cost over the lifetime of the contract plus the deposit cost in the uninlined case + // exceed the inlined deposit costs, it is beneficial to inline. + if (bigint(m_runs) * uninlinedExecutionCost + uninlinedDepositCost > inlinedDepositCost) + return true; + + return false; +} + + +optional Inliner::shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const +{ + AssemblyItem exitJump = _block.items.back(); + assertThrow(_jump == Instruction::JUMP && exitJump == Instruction::JUMP, OptimizerException, ""); + + if ( + _jump.getJumpType() == AssemblyItem::JumpType::IntoFunction && + exitJump.getJumpType() == AssemblyItem::JumpType::OutOfFunction + ) + return + shouldInlineFullFunctionBody(_tag, _block.items, _block.pushTagCount) ? + make_optional(AssemblyItem::JumpType::Ordinary) : nullopt; + + return nullopt; +} + + +void Inliner::optimise() +{ + std::map inlinableBlocks = determineInlinableBlocks(m_items); + + if (inlinableBlocks.empty()) + return; + + AssemblyItems newItems; + for (auto it = m_items.begin(); it != m_items.end(); ++it) + { + AssemblyItem const& item = *it; + if (next(it) != m_items.end()) + { + AssemblyItem const& nextItem = *next(it); + if (item.type() == PushTag && nextItem == Instruction::JUMP) + { + if (optional tag = getLocalTag(item)) + if (auto* inlinableBlock = util::valueOrNullptr(inlinableBlocks, *tag)) + if (auto exitJumpType = shouldInline(*tag, nextItem, *inlinableBlock)) + { + newItems += inlinableBlock->items; + newItems.back().setJumpType(*exitJumpType); + + // We are removing one push tag to the block we inline. + --inlinableBlock->pushTagCount; + // We might increase the number of push tags to other blocks. + for (AssemblyItem const& inlinedItem: inlinableBlock->items) + if (inlinedItem.type() == PushTag) + if (optional duplicatedTag = getLocalTag(inlinedItem)) + if (auto* block = util::valueOrNullptr(inlinableBlocks, *duplicatedTag)) + ++block->pushTagCount; + + // Skip the original jump to the inlined tag and continue. + ++it; + continue; + } + } + } + newItems.emplace_back(item); + } + + m_items = move(newItems); +} diff --git a/libevmasm/Inliner.h b/libevmasm/Inliner.h new file mode 100644 index 000000000..651ff8510 --- /dev/null +++ b/libevmasm/Inliner.h @@ -0,0 +1,83 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * @file Inliner.h + * Inlines small code snippets by replacing JUMP with a copy of the code jumped to. + */ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace solidity::evmasm +{ + +class Inliner +{ +public: + explicit Inliner( + AssemblyItems& _items, + std::set const& _tagsReferencedFromOutside, + size_t _runs, + bool _isCreation, + langutil::EVMVersion _evmVersion + ): + m_items(_items), + m_tagsReferencedFromOutside(_tagsReferencedFromOutside), + m_runs(_runs), + m_isCreation(_isCreation), + m_evmVersion(_evmVersion) + { + } + virtual ~Inliner() = default; + + void optimise(); + +private: + struct InlinableBlock + { + ranges::span items; + uint64_t pushTagCount = 0; + }; + + /// @returns the exit jump type for the block to be inlined, if a particular jump to it should be inlined, otherwise nullopt. + std::optional shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const; + /// @returns true, if the full function at tag @a _tag with body @a _block that is referenced @a _pushTagCount times + /// should be inlined, false otherwise. @a _block should start at the first instruction after the function entry tag + /// up to and including the return jump. + bool shouldInlineFullFunctionBody(size_t _tag, ranges::span _block, uint64_t _pushTagCount) const; + /// @returns true, if the @a _items at @a _tag are a potential candidate for inlining. + bool isInlineCandidate(size_t _tag, ranges::span _items) const; + /// @returns a map from tags that can potentially be inlined to the inlinable item range behind that tag and the + /// number of times the tag in question was referenced. + std::map determineInlinableBlocks(AssemblyItems const& _items) const; + + AssemblyItems& m_items; + std::set const& m_tagsReferencedFromOutside; + size_t const m_runs = 200; + bool const m_isCreation = false; + langutil::EVMVersion const m_evmVersion; +}; + +} diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 1b65e31c7..d934cb8dd 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -570,8 +570,9 @@ void CompilerContext::updateSourceLocation() evmasm::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings) { // Constructing it this way so that we notice changes in the fields. - evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, m_evmVersion, 0}; + evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, false, m_evmVersion, 0}; asmSettings.isCreation = true; + asmSettings.runInliner = _settings.runInliner; asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; asmSettings.runPeephole = _settings.runPeephole; asmSettings.runDeduplicate = _settings.runDeduplicate; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 4a0fbbd09..37b023c24 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1445,6 +1445,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const Json::Value details{Json::objectValue}; details["orderLiterals"] = m_optimiserSettings.runOrderLiterals; + details["inliner"] = m_optimiserSettings.runInliner; details["jumpdestRemover"] = m_optimiserSettings.runJumpdestRemover; details["peephole"] = m_optimiserSettings.runPeephole; details["deduplicate"] = m_optimiserSettings.runDeduplicate; diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index 4b1cd7c4e..a1d6bbf55 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -67,6 +67,7 @@ struct OptimiserSettings { OptimiserSettings s; s.runOrderLiterals = true; + s.runInliner = true; s.runJumpdestRemover = true; s.runPeephole = true; s.runDeduplicate = true; @@ -87,6 +88,7 @@ struct OptimiserSettings { return runOrderLiterals == _other.runOrderLiterals && + runInliner == _other.runInliner && runJumpdestRemover == _other.runJumpdestRemover && runPeephole == _other.runPeephole && runDeduplicate == _other.runDeduplicate && @@ -101,6 +103,8 @@ struct OptimiserSettings /// Move literals to the right of commutative binary operators during code generation. /// This helps exploiting associativity. bool runOrderLiterals = false; + /// Inliner + bool runInliner = false; /// Non-referenced jump destination remover. bool runJumpdestRemover = false; /// Peephole optimizer diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index eba5f1375..f544a9bc8 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -580,6 +580,8 @@ std::variant parseOptimizerSettings(Json::Value if (auto error = checkOptimizerDetail(details, "peephole", settings.runPeephole)) return *error; + if (auto error = checkOptimizerDetail(details, "inliner", settings.runInliner)) + return *error; if (auto error = checkOptimizerDetail(details, "jumpdestRemover", settings.runJumpdestRemover)) return *error; if (auto error = checkOptimizerDetail(details, "orderLiterals", settings.runOrderLiterals)) diff --git a/test/cmdlineTests/optimizer_BlockDeDuplicator/output b/test/cmdlineTests/optimizer_BlockDeDuplicator/output index 3af48a2f0..b68e11e1a 100644 --- a/test/cmdlineTests/optimizer_BlockDeDuplicator/output +++ b/test/cmdlineTests/optimizer_BlockDeDuplicator/output @@ -66,12 +66,12 @@ sub_0: assembly { dup1 0x2e1fb2bc eq - tag_3 + tag_4 jumpi dup1 0x4753a67d eq - tag_3 + tag_4 jumpi tag_2: 0x00 @@ -79,11 +79,11 @@ sub_0: assembly { revert /* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */ tag_3: - tag_6 - tag_7 - jump // in - tag_6: stop + /* "optimizer_BlockDeDuplicator/input.sol":108:133 function fun_() public {} */ + tag_4: + jump(tag_3) + /* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */ tag_7: jump // out diff --git a/test/cmdlineTests/optimizer_inliner_add/args b/test/cmdlineTests/optimizer_inliner_add/args new file mode 100644 index 000000000..8942fcc35 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_add/args @@ -0,0 +1 @@ +--optimize --asm --metadata-hash none diff --git a/test/cmdlineTests/optimizer_inliner_add/input.sol b/test/cmdlineTests/optimizer_inliner_add/input.sol new file mode 100644 index 000000000..e90663844 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_add/input.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +function unsafe_add(uint x, uint y) pure returns (uint) +{ + unchecked { + return x + y; + } +} + +contract C { + function f() public pure { + for(uint x = 0; x < 10; x = unsafe_add(x, unsafe_add(x, 1))) + { + } + } +} diff --git a/test/cmdlineTests/optimizer_inliner_add/output b/test/cmdlineTests/optimizer_inliner_add/output new file mode 100644 index 000000000..648d20fe8 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_add/output @@ -0,0 +1,86 @@ + +======= optimizer_inliner_add/input.sol:C ======= +EVM assembly: + /* "optimizer_inliner_add/input.sol":165:305 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert +tag_1: + pop + dataSize(sub_0) + dup1 + dataOffset(sub_0) + 0x00 + codecopy + 0x00 + return +stop + +sub_0: assembly { + /* "optimizer_inliner_add/input.sol":165:305 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert + tag_1: + pop + jumpi(tag_2, lt(calldatasize, 0x04)) + shr(0xe0, calldataload(0x00)) + dup1 + 0x26121ff0 + eq + tag_3 + jumpi + tag_2: + 0x00 + dup1 + revert + /* "optimizer_inliner_add/input.sol":182:303 function f() public pure {... */ + tag_3: + tag_4 + tag_5 + jump // in + tag_4: + stop + tag_5: + /* "optimizer_inliner_add/input.sol":221:227 uint x */ + 0x00 + /* "optimizer_inliner_add/input.sol":217:297 for(uint x = 0; x < 10; x = unsafe_add(x, unsafe_add(x, 1)))... */ + tag_7: + /* "optimizer_inliner_add/input.sol":237:239 10 */ + 0x0a + /* "optimizer_inliner_add/input.sol":233:234 x */ + dup2 + /* "optimizer_inliner_add/input.sol":233:239 x < 10 */ + lt + /* "optimizer_inliner_add/input.sol":217:297 for(uint x = 0; x < 10; x = unsafe_add(x, unsafe_add(x, 1)))... */ + iszero + tag_8 + jumpi + /* "optimizer_inliner_add/input.sol":149:154 x + y */ + dup1 + add + /* "optimizer_inliner_add/input.sol":273:274 1 */ + 0x01 + /* "optimizer_inliner_add/input.sol":149:154 x + y */ + add + /* "optimizer_inliner_add/input.sol":217:297 for(uint x = 0; x < 10; x = unsafe_add(x, unsafe_add(x, 1)))... */ + jump(tag_7) + tag_8: + pop + /* "optimizer_inliner_add/input.sol":182:303 function f() public pure {... */ + jump // out + + auxdata: +} diff --git a/test/cmdlineTests/optimizer_inliner_call_from_constructor/args b/test/cmdlineTests/optimizer_inliner_call_from_constructor/args new file mode 100644 index 000000000..8942fcc35 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_call_from_constructor/args @@ -0,0 +1 @@ +--optimize --asm --metadata-hash none diff --git a/test/cmdlineTests/optimizer_inliner_call_from_constructor/input.sol b/test/cmdlineTests/optimizer_inliner_call_from_constructor/input.sol new file mode 100644 index 000000000..1ee1c0088 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_call_from_constructor/input.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + uint x; + constructor() { x = a(); } + function a() public pure returns (uint) { return f(); } // this should be inlined + function f() internal pure returns (uint) { return 6; } +} diff --git a/test/cmdlineTests/optimizer_inliner_call_from_constructor/output b/test/cmdlineTests/optimizer_inliner_call_from_constructor/output new file mode 100644 index 000000000..86c09e27e --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_call_from_constructor/output @@ -0,0 +1,80 @@ + +======= optimizer_inliner_call_from_constructor/input.sol:C ======= +EVM assembly: + /* "optimizer_inliner_call_from_constructor/input.sol":60:263 contract C {... */ + mstore(0x40, 0x80) + /* "optimizer_inliner_call_from_constructor/input.sol":89:115 constructor() { x = a(); } */ + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert +tag_1: + pop + /* "optimizer_inliner_call_from_constructor/input.sol":257:258 6 */ + 0x06 + /* "optimizer_inliner_call_from_constructor/input.sol":105:106 x */ + 0x00 + /* "optimizer_inliner_call_from_constructor/input.sol":105:112 x = a() */ + sstore + /* "optimizer_inliner_call_from_constructor/input.sol":60:263 contract C {... */ + dataSize(sub_0) + dup1 + dataOffset(sub_0) + 0x00 + codecopy + 0x00 + return +stop + +sub_0: assembly { + /* "optimizer_inliner_call_from_constructor/input.sol":60:263 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert + tag_1: + pop + jumpi(tag_2, lt(calldatasize, 0x04)) + shr(0xe0, calldataload(0x00)) + dup1 + 0x0dbe671f + eq + tag_3 + jumpi + tag_2: + 0x00 + dup1 + revert + /* "optimizer_inliner_call_from_constructor/input.sol":120:175 function a() public pure returns (uint) { return f(); } */ + tag_3: + /* "optimizer_inliner_call_from_constructor/input.sol":257:258 6 */ + 0x06 + /* "optimizer_inliner_call_from_constructor/input.sol":120:175 function a() public pure returns (uint) { return f(); } */ + mload(0x40) + /* "#utility.yul":160:185 */ + swap1 + dup2 + mstore + /* "#utility.yul":148:150 */ + 0x20 + /* "#utility.yul":133:151 */ + add + /* "optimizer_inliner_call_from_constructor/input.sol":120:175 function a() public pure returns (uint) { return f(); } */ + mload(0x40) + dup1 + swap2 + sub + swap1 + return + + auxdata: +} diff --git a/test/cmdlineTests/optimizer_inliner_dynamic_reference/args b/test/cmdlineTests/optimizer_inliner_dynamic_reference/args new file mode 100644 index 000000000..8942fcc35 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_dynamic_reference/args @@ -0,0 +1 @@ +--optimize --asm --metadata-hash none diff --git a/test/cmdlineTests/optimizer_inliner_dynamic_reference/input.sol b/test/cmdlineTests/optimizer_inliner_dynamic_reference/input.sol new file mode 100644 index 000000000..f6a92fbca --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_dynamic_reference/input.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + function() internal view returns (uint) x; + + function g() public { x = f; } + function a() public pure returns (uint) { return f(); } // this should be inlined + function h() public view returns (uint) { return x() + 1; } + function f() internal pure returns (uint) { return 6; } +} diff --git a/test/cmdlineTests/optimizer_inliner_dynamic_reference/output b/test/cmdlineTests/optimizer_inliner_dynamic_reference/output new file mode 100644 index 000000000..8e986b308 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_dynamic_reference/output @@ -0,0 +1,198 @@ + +======= optimizer_inliner_dynamic_reference/input.sol:C ======= +EVM assembly: + /* "optimizer_inliner_dynamic_reference/input.sol":60:367 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert +tag_1: + pop + dataSize(sub_0) + dup1 + dataOffset(sub_0) + 0x00 + codecopy + 0x00 + return +stop + +sub_0: assembly { + /* "optimizer_inliner_dynamic_reference/input.sol":60:367 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert + tag_1: + pop + jumpi(tag_2, lt(calldatasize, 0x04)) + shr(0xe0, calldataload(0x00)) + dup1 + 0x0dbe671f + eq + tag_3 + jumpi + dup1 + 0xb8c9d365 + eq + tag_4 + jumpi + dup1 + 0xe2179b8e + eq + tag_5 + jumpi + tag_2: + 0x00 + dup1 + revert + /* "optimizer_inliner_dynamic_reference/input.sol":160:215 function a() public pure returns (uint) { return f(); } */ + tag_3: + tag_6 + tag_7 + jump // in + tag_6: + mload(0x40) + /* "#utility.yul":160:185 */ + swap1 + dup2 + mstore + /* "#utility.yul":148:150 */ + 0x20 + /* "#utility.yul":133:151 */ + add + /* "optimizer_inliner_dynamic_reference/input.sol":160:215 function a() public pure returns (uint) { return f(); } */ + mload(0x40) + dup1 + swap2 + sub + swap1 + return + /* "optimizer_inliner_dynamic_reference/input.sol":246:305 function h() public view returns (uint) { return x() + 1; } */ + tag_4: + tag_6 + tag_11 + jump // in + /* "optimizer_inliner_dynamic_reference/input.sol":125:155 function g() public { x = f; } */ + tag_5: + /* "optimizer_inliner_dynamic_reference/input.sol":147:148 x */ + 0x00 + /* "optimizer_inliner_dynamic_reference/input.sol":147:152 x = f */ + dup1 + sload + not(0xffffffffffffffff) + and + /* "optimizer_inliner_dynamic_reference/input.sol":151:152 f */ + tag_17 + /* "optimizer_inliner_dynamic_reference/input.sol":147:152 x = f */ + or + swap1 + sstore + /* "optimizer_inliner_dynamic_reference/input.sol":125:155 function g() public { x = f; } */ + stop + /* "optimizer_inliner_dynamic_reference/input.sol":160:215 function a() public pure returns (uint) { return f(); } */ + tag_7: + /* "optimizer_inliner_dynamic_reference/input.sol":194:198 uint */ + 0x00 + /* "optimizer_inliner_dynamic_reference/input.sol":361:362 6 */ + 0x06 + /* "optimizer_inliner_dynamic_reference/input.sol":209:212 f() */ + tag_16: + /* "optimizer_inliner_dynamic_reference/input.sol":202:212 return f() */ + swap1 + pop + /* "optimizer_inliner_dynamic_reference/input.sol":160:215 function a() public pure returns (uint) { return f(); } */ + swap1 + jump // out + /* "optimizer_inliner_dynamic_reference/input.sol":246:305 function h() public view returns (uint) { return x() + 1; } */ + tag_11: + /* "optimizer_inliner_dynamic_reference/input.sol":280:284 uint */ + 0x00 + /* "optimizer_inliner_dynamic_reference/input.sol":295:296 x */ + dup1 + sload + /* "optimizer_inliner_dynamic_reference/input.sol":295:298 x() */ + tag_19 + swap1 + /* "optimizer_inliner_dynamic_reference/input.sol":295:296 x */ + dup1 + iszero + tag_20 + mul + or + /* "optimizer_inliner_dynamic_reference/input.sol":295:298 x() */ + 0xffffffff + and + jump // in + tag_19: + /* "optimizer_inliner_dynamic_reference/input.sol":295:302 x() + 1 */ + tag_16 + swap1 + /* "optimizer_inliner_dynamic_reference/input.sol":301:302 1 */ + 0x01 + /* "optimizer_inliner_dynamic_reference/input.sol":295:302 x() + 1 */ + tag_22 + jump // in + /* "optimizer_inliner_dynamic_reference/input.sol":310:365 function f() internal pure returns (uint) { return 6; } */ + tag_17: + /* "optimizer_inliner_dynamic_reference/input.sol":361:362 6 */ + 0x06 + /* "optimizer_inliner_dynamic_reference/input.sol":310:365 function f() internal pure returns (uint) { return 6; } */ + swap1 + jump // out + tag_20: + mstore(0x00, shl(0xe0, 0x4e487b71)) + mstore(0x04, 0x51) + revert(0x00, 0x24) + /* "#utility.yul":196:425 */ + tag_22: + 0x00 + /* "#utility.yul":267:268 */ + dup3 + /* "#utility.yul":263:269 */ + not + /* "#utility.yul":260:261 */ + dup3 + /* "#utility.yul":257:270 */ + gt + /* "#utility.yul":254:256 */ + iszero + tag_28 + jumpi + shl(0xe0, 0x4e487b71) + /* "#utility.yul":293:326 */ + dup2 + mstore + /* "#utility.yul":349:353 */ + 0x11 + /* "#utility.yul":346:347 */ + 0x04 + /* "#utility.yul":339:354 */ + mstore + /* "#utility.yul":379:383 */ + 0x24 + /* "#utility.yul":300:303 */ + dup2 + /* "#utility.yul":367:384 */ + revert + /* "#utility.yul":254:256 */ + tag_28: + pop + /* "#utility.yul":410:419 */ + add + swap1 + /* "#utility.yul":244:425 */ + jump // out + + auxdata: +} diff --git a/test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/args b/test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/args new file mode 100644 index 000000000..8942fcc35 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/args @@ -0,0 +1 @@ +--optimize --asm --metadata-hash none diff --git a/test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/input.sol b/test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/input.sol new file mode 100644 index 000000000..573648cfd --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/input.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + constructor() { x = f; } + function() internal view returns (uint) x; + + function a() public pure returns (uint) { return f(); } // this should be inlined + function h() public view returns (uint) { return x() + 1; } + function f() internal pure returns (uint) { return 6; } +} diff --git a/test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/output b/test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/output new file mode 100644 index 000000000..e9f43bdab --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_dynamic_reference_constructor/output @@ -0,0 +1,203 @@ + +======= optimizer_inliner_dynamic_reference_constructor/input.sol:C ======= +EVM assembly: + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":60:361 contract C {... */ + mstore(0x40, 0x80) + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":77:101 constructor() { x = f; } */ + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert +tag_1: + pop + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":93:94 x */ + 0x00 + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":93:98 x = f */ + dup1 + sload + not(sub(shl(0x40, 0x01), 0x01)) + and + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":97:98 f */ + or(tag_0_12, shl(0x20, tag_4)) + sub(shl(0x40, 0x01), 0x01) + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":93:98 x = f */ + and + or + swap1 + sstore + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":60:361 contract C {... */ + jump(tag_5) + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":304:359 function f() internal pure returns (uint) { return 6; } */ +tag_4: + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":355:356 6 */ + 0x06 + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":304:359 function f() internal pure returns (uint) { return 6; } */ + swap1 + jump // out + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":60:361 contract C {... */ +tag_5: + dataSize(sub_0) + dup1 + dataOffset(sub_0) + 0x00 + codecopy + 0x00 + return +stop + +sub_0: assembly { + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":60:361 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert + tag_1: + pop + jumpi(tag_2, lt(calldatasize, 0x04)) + shr(0xe0, calldataload(0x00)) + dup1 + 0x0dbe671f + eq + tag_3 + jumpi + dup1 + 0xb8c9d365 + eq + tag_4 + jumpi + tag_2: + 0x00 + dup1 + revert + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":154:209 function a() public pure returns (uint) { return f(); } */ + tag_3: + tag_5 + tag_6 + jump // in + tag_5: + mload(0x40) + /* "#utility.yul":160:185 */ + swap1 + dup2 + mstore + /* "#utility.yul":148:150 */ + 0x20 + /* "#utility.yul":133:151 */ + add + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":154:209 function a() public pure returns (uint) { return f(); } */ + mload(0x40) + dup1 + swap2 + sub + swap1 + return + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":240:299 function h() public view returns (uint) { return x() + 1; } */ + tag_4: + tag_5 + tag_10 + jump // in + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":154:209 function a() public pure returns (uint) { return f(); } */ + tag_6: + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":188:192 uint */ + 0x00 + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":355:356 6 */ + 0x06 + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":203:206 f() */ + tag_14: + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":196:206 return f() */ + swap1 + pop + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":154:209 function a() public pure returns (uint) { return f(); } */ + swap1 + jump // out + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":240:299 function h() public view returns (uint) { return x() + 1; } */ + tag_10: + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":274:278 uint */ + 0x00 + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":289:290 x */ + dup1 + sload + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":289:292 x() */ + tag_16 + swap1 + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":289:290 x */ + dup1 + iszero + tag_17 + mul + or + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":289:292 x() */ + 0xffffffff + and + jump // in + tag_16: + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":289:296 x() + 1 */ + tag_14 + swap1 + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":295:296 1 */ + 0x01 + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":289:296 x() + 1 */ + tag_19 + jump // in + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":304:359 function f() internal pure returns (uint) { return 6; } */ + tag_12: + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":355:356 6 */ + 0x06 + /* "optimizer_inliner_dynamic_reference_constructor/input.sol":304:359 function f() internal pure returns (uint) { return 6; } */ + swap1 + jump // out + tag_17: + mstore(0x00, shl(0xe0, 0x4e487b71)) + mstore(0x04, 0x51) + revert(0x00, 0x24) + /* "#utility.yul":196:425 */ + tag_19: + 0x00 + /* "#utility.yul":267:268 */ + dup3 + /* "#utility.yul":263:269 */ + not + /* "#utility.yul":260:261 */ + dup3 + /* "#utility.yul":257:270 */ + gt + /* "#utility.yul":254:256 */ + iszero + tag_24 + jumpi + shl(0xe0, 0x4e487b71) + /* "#utility.yul":293:326 */ + dup2 + mstore + /* "#utility.yul":349:353 */ + 0x11 + /* "#utility.yul":346:347 */ + 0x04 + /* "#utility.yul":339:354 */ + mstore + /* "#utility.yul":379:383 */ + 0x24 + /* "#utility.yul":300:303 */ + dup2 + /* "#utility.yul":367:384 */ + revert + /* "#utility.yul":254:256 */ + tag_24: + pop + /* "#utility.yul":410:419 */ + add + swap1 + /* "#utility.yul":244:425 */ + jump // out + + auxdata: +} diff --git a/test/cmdlineTests/optimizer_inliner_inc/args b/test/cmdlineTests/optimizer_inliner_inc/args new file mode 100644 index 000000000..8942fcc35 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_inc/args @@ -0,0 +1 @@ +--optimize --asm --metadata-hash none diff --git a/test/cmdlineTests/optimizer_inliner_inc/input.sol b/test/cmdlineTests/optimizer_inliner_inc/input.sol new file mode 100644 index 000000000..f4f011aab --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_inc/input.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +function unsafe_inc(uint x) pure returns (uint) +{ + unchecked { + return x + 1; + } +} + +contract C { + function f() public pure { + for(uint x = 0; x < 10; x = unsafe_inc(x)) + { + } + } +} diff --git a/test/cmdlineTests/optimizer_inliner_inc/output b/test/cmdlineTests/optimizer_inliner_inc/output new file mode 100644 index 000000000..04f5f745a --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_inc/output @@ -0,0 +1,83 @@ + +======= optimizer_inliner_inc/input.sol:C ======= +EVM assembly: + /* "optimizer_inliner_inc/input.sol":157:279 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert +tag_1: + pop + dataSize(sub_0) + dup1 + dataOffset(sub_0) + 0x00 + codecopy + 0x00 + return +stop + +sub_0: assembly { + /* "optimizer_inliner_inc/input.sol":157:279 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert + tag_1: + pop + jumpi(tag_2, lt(calldatasize, 0x04)) + shr(0xe0, calldataload(0x00)) + dup1 + 0x26121ff0 + eq + tag_3 + jumpi + tag_2: + 0x00 + dup1 + revert + /* "optimizer_inliner_inc/input.sol":174:277 function f() public pure {... */ + tag_3: + tag_4 + tag_5 + jump // in + tag_4: + stop + tag_5: + /* "optimizer_inliner_inc/input.sol":213:219 uint x */ + 0x00 + /* "optimizer_inliner_inc/input.sol":209:271 for(uint x = 0; x < 10; x = unsafe_inc(x))... */ + tag_7: + /* "optimizer_inliner_inc/input.sol":229:231 10 */ + 0x0a + /* "optimizer_inliner_inc/input.sol":225:226 x */ + dup2 + /* "optimizer_inliner_inc/input.sol":225:231 x < 10 */ + lt + /* "optimizer_inliner_inc/input.sol":209:271 for(uint x = 0; x < 10; x = unsafe_inc(x))... */ + iszero + tag_8 + jumpi + /* "optimizer_inliner_inc/input.sol":145:146 1 */ + 0x01 + /* "optimizer_inliner_inc/input.sol":141:146 x + 1 */ + add + /* "optimizer_inliner_inc/input.sol":209:271 for(uint x = 0; x < 10; x = unsafe_inc(x))... */ + jump(tag_7) + tag_8: + pop + /* "optimizer_inliner_inc/input.sol":174:277 function f() public pure {... */ + jump // out + + auxdata: +} diff --git a/test/cmdlineTests/optimizer_inliner_multireturn/args b/test/cmdlineTests/optimizer_inliner_multireturn/args new file mode 100644 index 000000000..8942fcc35 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_multireturn/args @@ -0,0 +1 @@ +--optimize --asm --metadata-hash none diff --git a/test/cmdlineTests/optimizer_inliner_multireturn/input.sol b/test/cmdlineTests/optimizer_inliner_multireturn/input.sol new file mode 100644 index 000000000..19da9f7c5 --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_multireturn/input.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +function test(uint x) pure returns (uint, uint) +{ + unchecked { + return (x + 1, x); + } +} + +contract C { + function f() public pure { + for((uint x, uint y) = (0, 1); x < 10; (x, y) = test(x)) + { + } + } +} diff --git a/test/cmdlineTests/optimizer_inliner_multireturn/output b/test/cmdlineTests/optimizer_inliner_multireturn/output new file mode 100644 index 000000000..ca7b0abdf --- /dev/null +++ b/test/cmdlineTests/optimizer_inliner_multireturn/output @@ -0,0 +1,89 @@ + +======= optimizer_inliner_multireturn/input.sol:C ======= +EVM assembly: + /* "optimizer_inliner_multireturn/input.sol":162:298 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert +tag_1: + pop + dataSize(sub_0) + dup1 + dataOffset(sub_0) + 0x00 + codecopy + 0x00 + return +stop + +sub_0: assembly { + /* "optimizer_inliner_multireturn/input.sol":162:298 contract C {... */ + mstore(0x40, 0x80) + callvalue + dup1 + iszero + tag_1 + jumpi + 0x00 + dup1 + revert + tag_1: + pop + jumpi(tag_2, lt(calldatasize, 0x04)) + shr(0xe0, calldataload(0x00)) + dup1 + 0x26121ff0 + eq + tag_3 + jumpi + tag_2: + 0x00 + dup1 + revert + /* "optimizer_inliner_multireturn/input.sol":179:296 function f() public pure {... */ + tag_3: + tag_4 + tag_5 + jump // in + tag_4: + stop + tag_5: + /* "optimizer_inliner_multireturn/input.sol":219:225 uint x */ + 0x00 + /* "optimizer_inliner_multireturn/input.sol":241:242 1 */ + 0x01 + /* "optimizer_inliner_multireturn/input.sol":214:290 for((uint x, uint y) = (0, 1); x < 10; (x, y) = test(x))... */ + tag_7: + /* "optimizer_inliner_multireturn/input.sol":249:251 10 */ + 0x0a + /* "optimizer_inliner_multireturn/input.sol":245:246 x */ + dup3 + /* "optimizer_inliner_multireturn/input.sol":245:251 x < 10 */ + lt + /* "optimizer_inliner_multireturn/input.sol":214:290 for((uint x, uint y) = (0, 1); x < 10; (x, y) = test(x))... */ + iszero + tag_8 + jumpi + pop + /* "optimizer_inliner_multireturn/input.sol":146:147 1 */ + 0x01 + /* "optimizer_inliner_multireturn/input.sol":142:147 x + 1 */ + dup2 + add + swap1 + /* "optimizer_inliner_multireturn/input.sol":214:290 for((uint x, uint y) = (0, 1); x < 10; (x, y) = test(x))... */ + jump(tag_7) + tag_8: + pop + pop + /* "optimizer_inliner_multireturn/input.sol":179:296 function f() public pure {... */ + jump // out + + auxdata: +} diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 155f17bdf..f6519646d 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -1233,7 +1234,17 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies) main.append(t1.toSubAssemblyTag(subId)); main.append(u256(8)); - main.optimise(true, solidity::test::CommonOptions::get().evmVersion(), false, 200); + Assembly::OptimiserSettings settings; + settings.isCreation = false; + settings.runInliner = false; + settings.runJumpdestRemover = true; + settings.runPeephole = true; + settings.runDeduplicate = true; + settings.runCSE = true; + settings.runConstantOptimiser = true; + settings.evmVersion = solidity::test::CommonOptions::get().evmVersion(); + settings.expectedExecutionsPerDeployment = 200; + main.optimise(settings); AssemblyItems expectationMain{ AssemblyItem(PushSubSize, 0), @@ -1467,6 +1478,165 @@ BOOST_AUTO_TEST_CASE(cse_replace_too_large_shift) }); } +BOOST_AUTO_TEST_CASE(inliner) +{ + AssemblyItem jumpInto{Instruction::JUMP}; + jumpInto.setJumpType(AssemblyItem::JumpType::IntoFunction); + AssemblyItem jumpOutOf{Instruction::JUMP}; + jumpOutOf.setJumpType(AssemblyItem::JumpType::OutOfFunction); + AssemblyItems items{ + AssemblyItem(PushTag, 1), + AssemblyItem(PushTag, 2), + jumpInto, + AssemblyItem(Tag, 1), + Instruction::STOP, + AssemblyItem(Tag, 2), + Instruction::CALLVALUE, + Instruction::SWAP1, + jumpOutOf, + }; + AssemblyItems expectation{ + AssemblyItem(PushTag, 1), + Instruction::CALLVALUE, + Instruction::SWAP1, + Instruction::JUMP, + AssemblyItem(Tag, 1), + Instruction::STOP, + AssemblyItem(Tag, 2), + Instruction::CALLVALUE, + Instruction::SWAP1, + jumpOutOf, + }; + Inliner{items, {}, 200, false, {}}.optimise(); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); +} + + +BOOST_AUTO_TEST_CASE(inliner_no_inline_type) +{ + // Will not inline due to jump types. + AssemblyItems items{ + AssemblyItem(PushTag, 1), + AssemblyItem(PushTag, 2), + Instruction::JUMP, + AssemblyItem(Tag, 1), + Instruction::STOP, + AssemblyItem(Tag, 2), + Instruction::CALLVALUE, + Instruction::SWAP1, + Instruction::JUMP, + }; + Inliner{items, {}, 200, false, {}}.optimise(); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + items.begin(), items.end() + ); +} + +BOOST_AUTO_TEST_CASE(inliner_no_inline) +{ + AssemblyItems items{ + AssemblyItem(PushTag, 1), + Instruction::JUMP, + AssemblyItem(Tag, 1), + Instruction::CALLVALUE, + Instruction::JUMPI, + Instruction::JUMP, + }; + AssemblyItems expectation{ + AssemblyItem(PushTag, 1), + Instruction::JUMP, + AssemblyItem(Tag, 1), + Instruction::CALLVALUE, + Instruction::JUMPI, + Instruction::JUMP, + }; + Inliner{items, {}, 200, false, {}}.optimise(); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); +} + + +BOOST_AUTO_TEST_CASE(inliner_single_jump) +{ + AssemblyItem jumpInto{Instruction::JUMP}; + jumpInto.setJumpType(AssemblyItem::JumpType::IntoFunction); + AssemblyItem jumpOutOf{Instruction::JUMP}; + jumpOutOf.setJumpType(AssemblyItem::JumpType::OutOfFunction); + AssemblyItems items{ + AssemblyItem(PushTag, 1), + AssemblyItem(PushTag, 2), + jumpInto, + AssemblyItem(Tag, 1), + Instruction::STOP, + AssemblyItem(Tag, 2), + jumpOutOf, + }; + AssemblyItems expectation{ + AssemblyItem(PushTag, 1), + Instruction::JUMP, + AssemblyItem(Tag, 1), + Instruction::STOP, + AssemblyItem(Tag, 2), + jumpOutOf, + }; + Inliner{items, {}, 200, false, {}}.optimise(); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); +} + +BOOST_AUTO_TEST_CASE(inliner_end_of_bytecode) +{ + AssemblyItem jumpInto{Instruction::JUMP}; + jumpInto.setJumpType(AssemblyItem::JumpType::IntoFunction); + // Cannot inline, since the block at Tag_2 does not end in a jump. + AssemblyItems items{ + AssemblyItem(PushTag, 1), + AssemblyItem(PushTag, 2), + jumpInto, + AssemblyItem(Tag, 1), + Instruction::STOP, + AssemblyItem(Tag, 2), + }; + Inliner{items, {}, 200, false, {}}.optimise(); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + items.begin(), items.end() + ); +} + + +BOOST_AUTO_TEST_CASE(inliner_cse_break) +{ + AssemblyItem jumpInto{Instruction::JUMP}; + jumpInto.setJumpType(AssemblyItem::JumpType::IntoFunction); + AssemblyItem jumpOutOf{Instruction::JUMP}; + jumpOutOf.setJumpType(AssemblyItem::JumpType::OutOfFunction); + // Could be inlined, but we only consider non-CSE-breaking blocks ending in JUMP so far. + AssemblyItems items{ + AssemblyItem(PushTag, 1), + AssemblyItem(PushTag, 2), + jumpInto, + AssemblyItem(Tag, 1), + Instruction::STOP, + AssemblyItem(Tag, 2), + Instruction::STOP, // CSE breaking instruction + jumpOutOf + }; + Inliner{items, {}, 200, false, {}}.optimise(); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + items.begin(), items.end() + ); +} + BOOST_AUTO_TEST_SUITE_END() } // end namespaces diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 5aae847aa..7803a0d0b 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -174,9 +174,9 @@ BOOST_AUTO_TEST_CASE(location_test) if (solidity::test::CommonOptions::get().optimize) locations = vector(31, SourceLocation{23, 103, sourceCode}) + - vector(21, SourceLocation{41, 100, sourceCode}) + + vector(1, SourceLocation{41, 100, sourceCode}) + vector(1, SourceLocation{93, 95, sourceCode}) + - vector(2, SourceLocation{41, 100, sourceCode}); + vector(15, SourceLocation{41, 100, sourceCode}); else locations = vector(hasShifts ? 31 : 32, SourceLocation{23, 103, sourceCode}) + @@ -209,7 +209,10 @@ BOOST_AUTO_TEST_CASE(jump_type) if (item.getJumpType() != AssemblyItem::JumpType::Ordinary) jumpTypes += item.getJumpTypeAsString() + "\n"; - BOOST_CHECK_EQUAL(jumpTypes, "[in]\n[out]\n[in]\n[out]\n"); + if (solidity::test::CommonOptions::get().optimize) + BOOST_CHECK_EQUAL(jumpTypes, "[in]\n[out]\n[in]\n[out]\n"); + else + BOOST_CHECK_EQUAL(jumpTypes, "[in]\n[out]\n[in]\n[out]\n"); } diff --git a/test/libsolidity/GasCosts.cpp b/test/libsolidity/GasCosts.cpp index 252663434..946c435b0 100644 --- a/test/libsolidity/GasCosts.cpp +++ b/test/libsolidity/GasCosts.cpp @@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(string_storage) if (CommonOptions::get().useABIEncoderV1) CHECK_DEPLOY_GAS(133045, 129731, evmVersion); else - CHECK_DEPLOY_GAS(155553, 135201, evmVersion); + CHECK_DEPLOY_GAS(155553, 132103, evmVersion); } // This is only correct on >=Constantinople. else if (!CommonOptions::get().useABIEncoderV1) @@ -110,9 +110,9 @@ BOOST_AUTO_TEST_CASE(string_storage) { // Costs with 0 are cases which cannot be triggered in tests. if (evmVersion < EVMVersion::istanbul()) - CHECK_DEPLOY_GAS(0, 122869, evmVersion); + CHECK_DEPLOY_GAS(0, 120189, evmVersion); else - CHECK_DEPLOY_GAS(0, 110701, evmVersion); + CHECK_DEPLOY_GAS(0, 108541, evmVersion); } else { @@ -131,16 +131,16 @@ BOOST_AUTO_TEST_CASE(string_storage) { callContractFunction("f()"); if (evmVersion == EVMVersion::byzantium()) - CHECK_GAS(21741, 21555, 20); + CHECK_GAS(21741, 21522, 20); // This is only correct on >=Constantinople. else if (!CommonOptions::get().useABIEncoderV1) { if (CommonOptions::get().optimize) { if (evmVersion < EVMVersion::istanbul()) - CHECK_GAS(0, 21567, 20); + CHECK_GAS(0, 21526, 20); else - CHECK_GAS(0, 21351, 20); + CHECK_GAS(0, 21318, 20); } else { diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 3e545ac63..43256d14f 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -170,8 +170,8 @@ BOOST_AUTO_TEST_CASE(branches) } } )"; - testCreationTimeGas(sourceCode); - testRunTimeGas("f(uint256)", vector{encodeArgs(2), encodeArgs(8)}); + testCreationTimeGas(sourceCode, 1); + testRunTimeGas("f(uint256)", vector{encodeArgs(2), encodeArgs(8)}, 1); } BOOST_AUTO_TEST_CASE(function_calls) diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 9ed17c0af..20a01ee7f 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -1230,7 +1230,7 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) ); BOOST_CHECK(optimizer["details"]["yulDetails"]["stackAllocation"].asBool() == true); BOOST_CHECK(optimizer["details"]["yulDetails"]["optimizerSteps"].asString() == OptimiserSettings::DefaultYulOptimiserSteps); - BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 8); + BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 9); BOOST_CHECK(optimizer["runs"].asUInt() == 600); } diff --git a/test/libsolidity/gasTests/abiv2_optimised.sol b/test/libsolidity/gasTests/abiv2_optimised.sol index 7a55b6b0a..965e4576e 100644 --- a/test/libsolidity/gasTests/abiv2_optimised.sol +++ b/test/libsolidity/gasTests/abiv2_optimised.sol @@ -17,13 +17,13 @@ contract C { // optimize-yul: true // ---- // creation: -// codeDepositCost: 588800 +// codeDepositCost: 583400 // executionCost: 619 -// totalCost: 589419 +// totalCost: 584019 // external: -// a(): 1029 -// b(uint256): 2084 -// f1(uint256): 351 +// a(): 985 +// b(uint256): 2052 +// f1(uint256): 307 // f2(uint256[],string[],uint16,address): infinite // f3(uint16[],string[],uint16,address): infinite // f4(uint32[],string[12],bytes[2][],address): infinite diff --git a/test/libsolidity/gasTests/dispatch_large_optimised.sol b/test/libsolidity/gasTests/dispatch_large_optimised.sol index 4108ca121..6603ada5d 100644 --- a/test/libsolidity/gasTests/dispatch_large_optimised.sol +++ b/test/libsolidity/gasTests/dispatch_large_optimised.sol @@ -27,29 +27,29 @@ contract Large { // optimize-runs: 2 // ---- // creation: -// codeDepositCost: 270600 -// executionCost: 312 -// totalCost: 270912 +// codeDepositCost: 267000 +// executionCost: 306 +// totalCost: 267306 // external: -// a(): 1028 -// b(uint256): 2370 -// f0(uint256): 399 -// f1(uint256): 41539 -// f2(uint256): 21605 -// f3(uint256): 21693 -// f4(uint256): 21671 -// f5(uint256): 21649 -// f6(uint256): 21561 -// f7(uint256): 21341 -// f8(uint256): 21473 -// f9(uint256): 21495 -// g0(uint256): 639 -// g1(uint256): 41251 -// g2(uint256): 21339 -// g3(uint256): 21427 -// g4(uint256): 21405 -// g5(uint256): 21493 -// g6(uint256): 21273 -// g7(uint256): 21383 -// g8(uint256): 21361 -// g9(uint256): 21207 +// a(): 983 +// b(uint256): 2337 +// f0(uint256): 366 +// f1(uint256): 41506 +// f2(uint256): 21572 +// f3(uint256): 21660 +// f4(uint256): 21638 +// f5(uint256): 21616 +// f6(uint256): 21528 +// f7(uint256): 21308 +// f8(uint256): 21440 +// f9(uint256): 21462 +// g0(uint256): 606 +// g1(uint256): 41218 +// g2(uint256): 21306 +// g3(uint256): 21394 +// g4(uint256): 21372 +// g5(uint256): 21460 +// g6(uint256): 21240 +// g7(uint256): 21350 +// g8(uint256): 21328 +// g9(uint256): 21174 diff --git a/test/libsolidity/gasTests/dispatch_medium_optimised.sol b/test/libsolidity/gasTests/dispatch_medium_optimised.sol index 2bc22cc58..d59a36d58 100644 --- a/test/libsolidity/gasTests/dispatch_medium_optimised.sol +++ b/test/libsolidity/gasTests/dispatch_medium_optimised.sol @@ -14,16 +14,16 @@ contract Medium { // optimize-runs: 2 // ---- // creation: -// codeDepositCost: 161000 -// executionCost: 208 -// totalCost: 161208 +// codeDepositCost: 157400 +// executionCost: 202 +// totalCost: 157602 // external: -// a(): 1028 -// b(uint256): 2128 -// f1(uint256): 41319 -// f2(uint256): 21363 -// f3(uint256): 21407 -// g0(uint256): 397 -// g7(uint256): 21273 -// g8(uint256): 21251 -// g9(uint256): 21207 +// a(): 983 +// b(uint256): 2095 +// f1(uint256): 41286 +// f2(uint256): 21330 +// f3(uint256): 21374 +// g0(uint256): 364 +// g7(uint256): 21240 +// g8(uint256): 21218 +// g9(uint256): 21174 diff --git a/test/libsolidity/gasTests/dispatch_small_optimised.sol b/test/libsolidity/gasTests/dispatch_small_optimised.sol index 3f595229a..f9da0e5bd 100644 --- a/test/libsolidity/gasTests/dispatch_small_optimised.sol +++ b/test/libsolidity/gasTests/dispatch_small_optimised.sol @@ -9,11 +9,11 @@ contract Small { // optimize-runs: 2 // ---- // creation: -// codeDepositCost: 76200 +// codeDepositCost: 72600 // executionCost: 123 -// totalCost: 76323 +// totalCost: 72723 // external: // fallback: 118 -// a(): 1006 -// b(uint256): 2018 -// f1(uint256): 41253 +// a(): 961 +// b(uint256): 1985 +// f1(uint256): 41220 diff --git a/test/libsolidity/gasTests/exp_optimized.sol b/test/libsolidity/gasTests/exp_optimized.sol index d2c02378d..1e0ebc2ba 100644 --- a/test/libsolidity/gasTests/exp_optimized.sol +++ b/test/libsolidity/gasTests/exp_optimized.sol @@ -19,11 +19,11 @@ contract C { // optimize-yul: true // ---- // creation: -// codeDepositCost: 53200 -// executionCost: 105 -// totalCost: 53305 +// codeDepositCost: 47800 +// executionCost: 99 +// totalCost: 47899 // external: -// exp_neg_one(uint256): 1962 -// exp_one(uint256): 1915 -// exp_two(uint256): 1893 -// exp_zero(uint256): 1937 +// exp_neg_one(uint256): 1917 +// exp_one(uint256): 1870 +// exp_two(uint256): 1848 +// exp_zero(uint256): 1892