Add inlining for old optimizer.

This commit is contained in:
Daniel Kirchner 2021-01-14 13:02:14 +01:00
parent e777cad78a
commit cb74a45fd6
43 changed files with 1463 additions and 74 deletions

View File

@ -5,6 +5,7 @@ Language Features:
Compiler Features: Compiler Features:
* AST: Export NatSpec comments above each statement as their documentation. * 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: Bugfixes:

View File

@ -80,6 +80,8 @@ explanatory purposes.
details: { details: {
// peephole defaults to "true" // peephole defaults to "true"
peephole: true, peephole: true,
// inliner defaults to "true"
inliner: true,
// jumpdestRemover defaults to "true" // jumpdestRemover defaults to "true"
jumpdestRemover: true, jumpdestRemover: true,
orderLiterals: false, orderLiterals: false,

View File

@ -246,6 +246,9 @@ Input Description
// The peephole optimizer is always on if no details are given, // The peephole optimizer is always on if no details are given,
// use details to switch it off. // use details to switch it off.
"peephole": true, "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, // The unused jumpdest remover is always on if no details are given,
// use details to switch it off. // use details to switch it off.
"jumpdestRemover": true, "jumpdestRemover": true,

View File

@ -25,6 +25,7 @@
#include <libevmasm/CommonSubexpressionEliminator.h> #include <libevmasm/CommonSubexpressionEliminator.h>
#include <libevmasm/ControlFlowGraph.h> #include <libevmasm/ControlFlowGraph.h>
#include <libevmasm/PeepholeOptimiser.h> #include <libevmasm/PeepholeOptimiser.h>
#include <libevmasm/Inliner.h>
#include <libevmasm/JumpdestRemover.h> #include <libevmasm/JumpdestRemover.h>
#include <libevmasm/BlockDeduplicator.h> #include <libevmasm/BlockDeduplicator.h>
#include <libevmasm/ConstantOptimiser.h> #include <libevmasm/ConstantOptimiser.h>
@ -375,6 +376,7 @@ Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreat
{ {
OptimiserSettings settings; OptimiserSettings settings;
settings.isCreation = _isCreation; settings.isCreation = _isCreation;
settings.runInliner = true;
settings.runJumpdestRemover = true; settings.runJumpdestRemover = true;
settings.runPeephole = true; settings.runPeephole = true;
if (_enable) if (_enable)
@ -421,6 +423,15 @@ map<u256, u256> Assembly::optimiseInternal(
{ {
count = 0; count = 0;
if (_settings.runInliner)
Inliner{
m_items,
_tagsReferencedFromOutside,
_settings.expectedExecutionsPerDeployment,
_settings.isCreation,
_settings.evmVersion
}.optimise();
if (_settings.runJumpdestRemover) if (_settings.runJumpdestRemover)
{ {
JumpdestRemover jumpdestOpt{m_items}; JumpdestRemover jumpdestOpt{m_items};

View File

@ -106,6 +106,7 @@ public:
struct OptimiserSettings struct OptimiserSettings
{ {
bool isCreation = false; bool isCreation = false;
bool runInliner = false;
bool runJumpdestRemover = false; bool runJumpdestRemover = false;
bool runPeephole = false; bool runPeephole = false;
bool runDeduplicate = false; bool runDeduplicate = false;

View File

@ -16,6 +16,8 @@ set(sources
ExpressionClasses.h ExpressionClasses.h
GasMeter.cpp GasMeter.cpp
GasMeter.h GasMeter.h
Inliner.cpp
Inliner.h
Instruction.cpp Instruction.cpp
Instruction.h Instruction.h
JumpdestRemover.cpp JumpdestRemover.cpp

View File

@ -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."); assertThrow(gas < bigint(u256(-1)), OptimizerException, "Gas cost exceeds 256 bits.");
return u256(gas); 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);
}

View File

@ -125,6 +125,12 @@ public:
static GasConsumption infinite() { return GasConsumption(0, true); } static GasConsumption infinite() { return GasConsumption(0, true); }
GasConsumption& operator+=(GasConsumption const& _other); GasConsumption& operator+=(GasConsumption const& _other);
GasConsumption operator+(GasConsumption const& _other) const
{
GasConsumption result = *this;
result += _other;
return result;
}
bool operator<(GasConsumption const& _other) const bool operator<(GasConsumption const& _other) const
{ {
return std::make_pair(isInfinite, value) < std::make_pair(_other.isInfinite, _other.value); 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. /// otherwise code will be stored and have to pay "createDataGas" cost.
static u256 dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion); 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: private:
/// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise. /// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise.
GasConsumption wordGas(u256 const& _multiplier, ExpressionClasses::Id _value); GasConsumption wordGas(u256 const& _multiplier, ExpressionClasses::Id _value);

259
libevmasm/Inliner.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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 <libevmasm/Inliner.h>
#include <libevmasm/AssemblyItem.h>
#include <libevmasm/GasMeter.h>
#include <libevmasm/KnownState.h>
#include <libevmasm/SemanticInformation.h>
#include <libsolutil/CommonData.h>
#include <range/v3/numeric/accumulate.hpp>
#include <range/v3/view/drop_last.hpp>
#include <range/v3/view/enumerate.hpp>
#include <range/v3/view/slice.hpp>
#include <range/v3/view/transform.hpp>
#include <optional>
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<typename RangeType>
u256 executionCost(RangeType const& _itemRange, langutil::EVMVersion _evmVersion)
{
GasMeter gasMeter{std::make_shared<KnownState>(), _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<u256>::max();
else
return gasConsumption.value;
}
/// @returns an estimation of the code size in bytes needed for the AssemblyItems in @a _itemRange.
template<typename RangeType>
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<size_t> getLocalTag(AssemblyItem const& _item)
{
if (_item.type() != PushTag && _item.type() != Tag)
return nullopt;
auto [subId, tag] = _item.splitForeignPushTag();
if (subId != numeric_limits<size_t>::max())
return nullopt;
return tag;
}
}
bool Inliner::isInlineCandidate(size_t _tag, ranges::span<AssemblyItem const> _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<size_t, Inliner::InlinableBlock> Inliner::determineInlinableBlocks(AssemblyItems const& _items) const
{
std::map<size_t, ranges::span<AssemblyItem const>> inlinableBlockItems;
std::map<size_t, uint64_t> numPushTags;
std::optional<size_t> 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<size_t> 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<AssemblyItem const> block = _items | ranges::views::slice(*lastTag + 1, index + 1);
if (optional<size_t> 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<size_t, InlinableBlock> 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<AssemblyItem const> _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<AssemblyItem::JumpType> 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<size_t, InlinableBlock> 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<size_t> 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<size_t> 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);
}

83
libevmasm/Inliner.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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 <libsolutil/Common.h>
#include <libevmasm/AssemblyItem.h>
#include <liblangutil/EVMVersion.h>
#include <range/v3/view/span.hpp>
#include <map>
#include <set>
#include <vector>
namespace solidity::evmasm
{
class Inliner
{
public:
explicit Inliner(
AssemblyItems& _items,
std::set<size_t> 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<AssemblyItem const> 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<AssemblyItem::JumpType> 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<AssemblyItem const> _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<AssemblyItem const> _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<size_t, InlinableBlock> determineInlinableBlocks(AssemblyItems const& _items) const;
AssemblyItems& m_items;
std::set<size_t> const& m_tagsReferencedFromOutside;
size_t const m_runs = 200;
bool const m_isCreation = false;
langutil::EVMVersion const m_evmVersion;
};
}

View File

@ -570,8 +570,9 @@ void CompilerContext::updateSourceLocation()
evmasm::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings) evmasm::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings)
{ {
// Constructing it this way so that we notice changes in the fields. // 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.isCreation = true;
asmSettings.runInliner = _settings.runInliner;
asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; asmSettings.runJumpdestRemover = _settings.runJumpdestRemover;
asmSettings.runPeephole = _settings.runPeephole; asmSettings.runPeephole = _settings.runPeephole;
asmSettings.runDeduplicate = _settings.runDeduplicate; asmSettings.runDeduplicate = _settings.runDeduplicate;

View File

@ -1445,6 +1445,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
Json::Value details{Json::objectValue}; Json::Value details{Json::objectValue};
details["orderLiterals"] = m_optimiserSettings.runOrderLiterals; details["orderLiterals"] = m_optimiserSettings.runOrderLiterals;
details["inliner"] = m_optimiserSettings.runInliner;
details["jumpdestRemover"] = m_optimiserSettings.runJumpdestRemover; details["jumpdestRemover"] = m_optimiserSettings.runJumpdestRemover;
details["peephole"] = m_optimiserSettings.runPeephole; details["peephole"] = m_optimiserSettings.runPeephole;
details["deduplicate"] = m_optimiserSettings.runDeduplicate; details["deduplicate"] = m_optimiserSettings.runDeduplicate;

View File

@ -67,6 +67,7 @@ struct OptimiserSettings
{ {
OptimiserSettings s; OptimiserSettings s;
s.runOrderLiterals = true; s.runOrderLiterals = true;
s.runInliner = true;
s.runJumpdestRemover = true; s.runJumpdestRemover = true;
s.runPeephole = true; s.runPeephole = true;
s.runDeduplicate = true; s.runDeduplicate = true;
@ -87,6 +88,7 @@ struct OptimiserSettings
{ {
return return
runOrderLiterals == _other.runOrderLiterals && runOrderLiterals == _other.runOrderLiterals &&
runInliner == _other.runInliner &&
runJumpdestRemover == _other.runJumpdestRemover && runJumpdestRemover == _other.runJumpdestRemover &&
runPeephole == _other.runPeephole && runPeephole == _other.runPeephole &&
runDeduplicate == _other.runDeduplicate && runDeduplicate == _other.runDeduplicate &&
@ -101,6 +103,8 @@ struct OptimiserSettings
/// Move literals to the right of commutative binary operators during code generation. /// Move literals to the right of commutative binary operators during code generation.
/// This helps exploiting associativity. /// This helps exploiting associativity.
bool runOrderLiterals = false; bool runOrderLiterals = false;
/// Inliner
bool runInliner = false;
/// Non-referenced jump destination remover. /// Non-referenced jump destination remover.
bool runJumpdestRemover = false; bool runJumpdestRemover = false;
/// Peephole optimizer /// Peephole optimizer

View File

@ -580,6 +580,8 @@ std::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Value
if (auto error = checkOptimizerDetail(details, "peephole", settings.runPeephole)) if (auto error = checkOptimizerDetail(details, "peephole", settings.runPeephole))
return *error; return *error;
if (auto error = checkOptimizerDetail(details, "inliner", settings.runInliner))
return *error;
if (auto error = checkOptimizerDetail(details, "jumpdestRemover", settings.runJumpdestRemover)) if (auto error = checkOptimizerDetail(details, "jumpdestRemover", settings.runJumpdestRemover))
return *error; return *error;
if (auto error = checkOptimizerDetail(details, "orderLiterals", settings.runOrderLiterals)) if (auto error = checkOptimizerDetail(details, "orderLiterals", settings.runOrderLiterals))

View File

@ -66,12 +66,12 @@ sub_0: assembly {
dup1 dup1
0x2e1fb2bc 0x2e1fb2bc
eq eq
tag_3 tag_4
jumpi jumpi
dup1 dup1
0x4753a67d 0x4753a67d
eq eq
tag_3 tag_4
jumpi jumpi
tag_2: tag_2:
0x00 0x00
@ -79,11 +79,11 @@ sub_0: assembly {
revert revert
/* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */ /* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */
tag_3: tag_3:
tag_6
tag_7
jump // in
tag_6:
stop 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: tag_7:
jump // out jump // out

View File

@ -0,0 +1 @@
--optimize --asm --metadata-hash none

View File

@ -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)))
{
}
}
}

View File

@ -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: <AUXDATA REMOVED>
}

View File

@ -0,0 +1 @@
--optimize --asm --metadata-hash none

View File

@ -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; }
}

View File

@ -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: <AUXDATA REMOVED>
}

View File

@ -0,0 +1 @@
--optimize --asm --metadata-hash none

View File

@ -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; }
}

View File

@ -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: <AUXDATA REMOVED>
}

View File

@ -0,0 +1 @@
--optimize --asm --metadata-hash none

View File

@ -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; }
}

View File

@ -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: <AUXDATA REMOVED>
}

View File

@ -0,0 +1 @@
--optimize --asm --metadata-hash none

View File

@ -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))
{
}
}
}

View File

@ -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: <AUXDATA REMOVED>
}

View File

@ -0,0 +1 @@
--optimize --asm --metadata-hash none

View File

@ -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))
{
}
}
}

View File

@ -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: <AUXDATA REMOVED>
}

View File

@ -25,6 +25,7 @@
#include <libevmasm/CommonSubexpressionEliminator.h> #include <libevmasm/CommonSubexpressionEliminator.h>
#include <libevmasm/PeepholeOptimiser.h> #include <libevmasm/PeepholeOptimiser.h>
#include <libevmasm/Inliner.h>
#include <libevmasm/JumpdestRemover.h> #include <libevmasm/JumpdestRemover.h>
#include <libevmasm/ControlFlowGraph.h> #include <libevmasm/ControlFlowGraph.h>
#include <libevmasm/BlockDeduplicator.h> #include <libevmasm/BlockDeduplicator.h>
@ -1233,7 +1234,17 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies)
main.append(t1.toSubAssemblyTag(subId)); main.append(t1.toSubAssemblyTag(subId));
main.append(u256(8)); 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{ AssemblyItems expectationMain{
AssemblyItem(PushSubSize, 0), 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() BOOST_AUTO_TEST_SUITE_END()
} // end namespaces } // end namespaces

View File

@ -174,9 +174,9 @@ BOOST_AUTO_TEST_CASE(location_test)
if (solidity::test::CommonOptions::get().optimize) if (solidity::test::CommonOptions::get().optimize)
locations = locations =
vector<SourceLocation>(31, SourceLocation{23, 103, sourceCode}) + vector<SourceLocation>(31, SourceLocation{23, 103, sourceCode}) +
vector<SourceLocation>(21, SourceLocation{41, 100, sourceCode}) + vector<SourceLocation>(1, SourceLocation{41, 100, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{93, 95, sourceCode}) + vector<SourceLocation>(1, SourceLocation{93, 95, sourceCode}) +
vector<SourceLocation>(2, SourceLocation{41, 100, sourceCode}); vector<SourceLocation>(15, SourceLocation{41, 100, sourceCode});
else else
locations = locations =
vector<SourceLocation>(hasShifts ? 31 : 32, SourceLocation{23, 103, sourceCode}) + vector<SourceLocation>(hasShifts ? 31 : 32, SourceLocation{23, 103, sourceCode}) +
@ -209,7 +209,10 @@ BOOST_AUTO_TEST_CASE(jump_type)
if (item.getJumpType() != AssemblyItem::JumpType::Ordinary) if (item.getJumpType() != AssemblyItem::JumpType::Ordinary)
jumpTypes += item.getJumpTypeAsString() + "\n"; 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");
} }

View File

@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(string_storage)
if (CommonOptions::get().useABIEncoderV1) if (CommonOptions::get().useABIEncoderV1)
CHECK_DEPLOY_GAS(133045, 129731, evmVersion); CHECK_DEPLOY_GAS(133045, 129731, evmVersion);
else else
CHECK_DEPLOY_GAS(155553, 135201, evmVersion); CHECK_DEPLOY_GAS(155553, 132103, evmVersion);
} }
// This is only correct on >=Constantinople. // This is only correct on >=Constantinople.
else if (!CommonOptions::get().useABIEncoderV1) 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. // Costs with 0 are cases which cannot be triggered in tests.
if (evmVersion < EVMVersion::istanbul()) if (evmVersion < EVMVersion::istanbul())
CHECK_DEPLOY_GAS(0, 122869, evmVersion); CHECK_DEPLOY_GAS(0, 120189, evmVersion);
else else
CHECK_DEPLOY_GAS(0, 110701, evmVersion); CHECK_DEPLOY_GAS(0, 108541, evmVersion);
} }
else else
{ {
@ -131,16 +131,16 @@ BOOST_AUTO_TEST_CASE(string_storage)
{ {
callContractFunction("f()"); callContractFunction("f()");
if (evmVersion == EVMVersion::byzantium()) if (evmVersion == EVMVersion::byzantium())
CHECK_GAS(21741, 21555, 20); CHECK_GAS(21741, 21522, 20);
// This is only correct on >=Constantinople. // This is only correct on >=Constantinople.
else if (!CommonOptions::get().useABIEncoderV1) else if (!CommonOptions::get().useABIEncoderV1)
{ {
if (CommonOptions::get().optimize) if (CommonOptions::get().optimize)
{ {
if (evmVersion < EVMVersion::istanbul()) if (evmVersion < EVMVersion::istanbul())
CHECK_GAS(0, 21567, 20); CHECK_GAS(0, 21526, 20);
else else
CHECK_GAS(0, 21351, 20); CHECK_GAS(0, 21318, 20);
} }
else else
{ {

View File

@ -170,8 +170,8 @@ BOOST_AUTO_TEST_CASE(branches)
} }
} }
)"; )";
testCreationTimeGas(sourceCode); testCreationTimeGas(sourceCode, 1);
testRunTimeGas("f(uint256)", vector<bytes>{encodeArgs(2), encodeArgs(8)}); testRunTimeGas("f(uint256)", vector<bytes>{encodeArgs(2), encodeArgs(8)}, 1);
} }
BOOST_AUTO_TEST_CASE(function_calls) BOOST_AUTO_TEST_CASE(function_calls)

View File

@ -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"]["stackAllocation"].asBool() == true);
BOOST_CHECK(optimizer["details"]["yulDetails"]["optimizerSteps"].asString() == OptimiserSettings::DefaultYulOptimiserSteps); 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); BOOST_CHECK(optimizer["runs"].asUInt() == 600);
} }

View File

@ -17,13 +17,13 @@ contract C {
// optimize-yul: true // optimize-yul: true
// ---- // ----
// creation: // creation:
// codeDepositCost: 588800 // codeDepositCost: 583400
// executionCost: 619 // executionCost: 619
// totalCost: 589419 // totalCost: 584019
// external: // external:
// a(): 1029 // a(): 985
// b(uint256): 2084 // b(uint256): 2052
// f1(uint256): 351 // f1(uint256): 307
// f2(uint256[],string[],uint16,address): infinite // f2(uint256[],string[],uint16,address): infinite
// f3(uint16[],string[],uint16,address): infinite // f3(uint16[],string[],uint16,address): infinite
// f4(uint32[],string[12],bytes[2][],address): infinite // f4(uint32[],string[12],bytes[2][],address): infinite

View File

@ -27,29 +27,29 @@ contract Large {
// optimize-runs: 2 // optimize-runs: 2
// ---- // ----
// creation: // creation:
// codeDepositCost: 270600 // codeDepositCost: 267000
// executionCost: 312 // executionCost: 306
// totalCost: 270912 // totalCost: 267306
// external: // external:
// a(): 1028 // a(): 983
// b(uint256): 2370 // b(uint256): 2337
// f0(uint256): 399 // f0(uint256): 366
// f1(uint256): 41539 // f1(uint256): 41506
// f2(uint256): 21605 // f2(uint256): 21572
// f3(uint256): 21693 // f3(uint256): 21660
// f4(uint256): 21671 // f4(uint256): 21638
// f5(uint256): 21649 // f5(uint256): 21616
// f6(uint256): 21561 // f6(uint256): 21528
// f7(uint256): 21341 // f7(uint256): 21308
// f8(uint256): 21473 // f8(uint256): 21440
// f9(uint256): 21495 // f9(uint256): 21462
// g0(uint256): 639 // g0(uint256): 606
// g1(uint256): 41251 // g1(uint256): 41218
// g2(uint256): 21339 // g2(uint256): 21306
// g3(uint256): 21427 // g3(uint256): 21394
// g4(uint256): 21405 // g4(uint256): 21372
// g5(uint256): 21493 // g5(uint256): 21460
// g6(uint256): 21273 // g6(uint256): 21240
// g7(uint256): 21383 // g7(uint256): 21350
// g8(uint256): 21361 // g8(uint256): 21328
// g9(uint256): 21207 // g9(uint256): 21174

View File

@ -14,16 +14,16 @@ contract Medium {
// optimize-runs: 2 // optimize-runs: 2
// ---- // ----
// creation: // creation:
// codeDepositCost: 161000 // codeDepositCost: 157400
// executionCost: 208 // executionCost: 202
// totalCost: 161208 // totalCost: 157602
// external: // external:
// a(): 1028 // a(): 983
// b(uint256): 2128 // b(uint256): 2095
// f1(uint256): 41319 // f1(uint256): 41286
// f2(uint256): 21363 // f2(uint256): 21330
// f3(uint256): 21407 // f3(uint256): 21374
// g0(uint256): 397 // g0(uint256): 364
// g7(uint256): 21273 // g7(uint256): 21240
// g8(uint256): 21251 // g8(uint256): 21218
// g9(uint256): 21207 // g9(uint256): 21174

View File

@ -9,11 +9,11 @@ contract Small {
// optimize-runs: 2 // optimize-runs: 2
// ---- // ----
// creation: // creation:
// codeDepositCost: 76200 // codeDepositCost: 72600
// executionCost: 123 // executionCost: 123
// totalCost: 76323 // totalCost: 72723
// external: // external:
// fallback: 118 // fallback: 118
// a(): 1006 // a(): 961
// b(uint256): 2018 // b(uint256): 1985
// f1(uint256): 41253 // f1(uint256): 41220

View File

@ -19,11 +19,11 @@ contract C {
// optimize-yul: true // optimize-yul: true
// ---- // ----
// creation: // creation:
// codeDepositCost: 53200 // codeDepositCost: 47800
// executionCost: 105 // executionCost: 99
// totalCost: 53305 // totalCost: 47899
// external: // external:
// exp_neg_one(uint256): 1962 // exp_neg_one(uint256): 1917
// exp_one(uint256): 1915 // exp_one(uint256): 1870
// exp_two(uint256): 1893 // exp_two(uint256): 1848
// exp_zero(uint256): 1937 // exp_zero(uint256): 1892