#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>
#include <limits>
using namespace std;
using namespace solidity;
using namespace solidity::evmasm;
namespace
{
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;
}
template<typename RangeType>
uint64_t codeSize(RangeType const& _itemRange)
{
return ranges::accumulate(_itemRange | ranges::views::transform(
[](auto const& _item) { return _item.bytesRequired(2, Precision::Approximate); }
), 0u);
}
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, "");
if (_items.back().type() != Operation)
return false;
if (
_items.back() != Instruction::JUMP &&
!SemanticInformation::terminatesControlFlow(_items.back().instruction())
)
return false;
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)
{
if (item.type() == PushTag)
if (optional<size_t> tag = getLocalTag(item))
++numPushTags[*tag];
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;
}
}
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
{
uint64_t functionBodySize = codeSize(ranges::views::drop_last(_block, 1));
uint64_t numberOfCalls = _pushTagCount;
uint64_t numberOfCallSites = _pushTagCount;
static AssemblyItems const uninlinedCallSitePattern = {
AssemblyItem{PushTag},
AssemblyItem{PushTag},
AssemblyItem{Instruction::JUMP},
AssemblyItem{Tag}
};
static AssemblyItems const uninlinedFunctionPattern = {
AssemblyItem{Tag},
AssemblyItem{Instruction::JUMP}
};
bigint uninlinedExecutionCost = numberOfCalls * (
executionCost(uninlinedCallSitePattern, m_evmVersion) +
executionCost(uninlinedFunctionPattern, m_evmVersion)
);
bigint uninlinedDepositCost = GasMeter::dataGas(
numberOfCallSites * codeSize(uninlinedCallSitePattern) +
codeSize(uninlinedFunctionPattern) +
functionBodySize,
m_isCreation,
m_evmVersion
);
bigint inlinedDepositCost = GasMeter::dataGas(
numberOfCallSites * functionBodySize,
m_isCreation,
m_evmVersion
);
if (m_tagsReferencedFromOutside.count(_tag))
inlinedDepositCost += GasMeter::dataGas(
codeSize(uninlinedFunctionPattern) + functionBodySize,
m_isCreation,
m_evmVersion
);
if (bigint(m_runs) * uninlinedExecutionCost + uninlinedDepositCost > inlinedDepositCost)
return true;
return false;
}
optional<AssemblyItem> Inliner::shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const
{
assertThrow(_jump == Instruction::JUMP, OptimizerException, "");
AssemblyItem blockExit = _block.items.back();
if (
_jump.getJumpType() == AssemblyItem::JumpType::IntoFunction &&
blockExit == Instruction::JUMP &&
blockExit.getJumpType() == AssemblyItem::JumpType::OutOfFunction &&
shouldInlineFullFunctionBody(_tag, _block.items, _block.pushTagCount)
)
{
blockExit.setJumpType(AssemblyItem::JumpType::Ordinary);
return blockExit;
}
if (
_jump.getJumpType() == AssemblyItem::JumpType::Ordinary ||
SemanticInformation::terminatesControlFlow(blockExit.instruction())
)
{
static AssemblyItems const jumpPattern = {
AssemblyItem{PushTag},
AssemblyItem{Instruction::JUMP},
};
if (
GasMeter::dataGas(codeSize(_block.items), m_isCreation, m_evmVersion) <=
GasMeter::dataGas(codeSize(jumpPattern), m_isCreation, m_evmVersion)
)
return blockExit;
}
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 exitItem = shouldInline(*tag, nextItem, *inlinableBlock))
{
newItems += inlinableBlock->items | ranges::views::drop_last(1);
newItems.emplace_back(move(*exitItem));
--inlinableBlock->pushTagCount;
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;
++it;
continue;
}
}
}
newItems.emplace_back(item);
}
m_items = move(newItems);
}