Skip to content

[DO NOT MERGE; EOF] Draft of some experiments with libevmasm optimizer for EOF #15921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ std::map<u256, u256> const& Assembly::optimiseInternal(
}.optimise();
}
// TODO: verify this for EOF.
if (_settings.runJumpdestRemover && !m_eofVersion.has_value())
if (_settings.runJumpdestRemover)
{
for (auto& codeSection: m_codeSections)
{
Expand All @@ -844,7 +844,7 @@ std::map<u256, u256> const& Assembly::optimiseInternal(
}

// TODO: verify this for EOF.
if (_settings.runPeephole && !m_eofVersion.has_value())
if (_settings.runPeephole)
{
for (auto& codeSection: m_codeSections)
{
Expand All @@ -859,7 +859,7 @@ std::map<u256, u256> const& Assembly::optimiseInternal(

// This only modifies PushTags, we have to run again to actually remove code.
// TODO: implement for EOF.
if (_settings.runDeduplicate && !m_eofVersion.has_value())
if (_settings.runDeduplicate)
for (auto& section: m_codeSections)
{
BlockDeduplicator deduplicator{section.items};
Expand Down Expand Up @@ -941,7 +941,7 @@ std::map<u256, u256> const& Assembly::optimiseInternal(
}

// TODO: investigate for EOF
if (_settings.runConstantOptimiser && !m_eofVersion.has_value())
if (_settings.runConstantOptimiser)
ConstantOptimisationMethod::optimiseConstants(
isCreation(),
isCreation() ? 1 : _settings.expectedExecutionsPerDeployment,
Expand Down Expand Up @@ -1698,7 +1698,6 @@ LinkerObject const& Assembly::assembleEOF() const
solAssert(m_codeSections[index].inputs == item.functionSignature().argsNum);
solAssert(m_codeSections[index].outputs == item.functionSignature().retsNum);
// If CallF the function cannot be non-returning.
solAssert(item.type() == JumpF || !m_codeSections[index].nonReturning);
appendBigEndianUint16(ret.bytecode, item.data());
break;
}
Expand Down
48 changes: 47 additions & 1 deletion libevmasm/BlockDeduplicator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <libevmasm/AssemblyItem.h>
#include <libevmasm/SemanticInformation.h>

#include <boost/mpl/tag.hpp>
#include <functional>
#include <set>

Expand All @@ -36,6 +37,11 @@ using namespace solidity::evmasm;

bool BlockDeduplicator::deduplicate()
{
// For all tags determine their relative position within the assembly items.
std::map<u256, size_t> tagOffsets;
for (size_t i = 0; i < m_items.size(); ++i)
if (m_items[i].type() == Tag)
tagOffsets[m_items.at(i).data()] = i;
// Compares indices based on the suffix that starts there, ignoring tags and stopping at
// opcodes that stop the control flow.

Expand Down Expand Up @@ -76,6 +82,10 @@ bool BlockDeduplicator::deduplicate()
return std::lexicographical_compare(first, end, second, end);
};

// Use the first block with the same sequence as representative for all blocks of that sequence.
std::map<u256, u256> tagToRepresentative;
// Store all blocks that are equal to a representative.
std::map<u256, std::vector<u256>> replacementCandidates;
size_t iterations = 0;
for (; ; ++iterations)
{
Expand All @@ -85,11 +95,47 @@ bool BlockDeduplicator::deduplicate()
{
if (m_items.at(i).type() != Tag)
continue;
// Prevent considering any blocks that are not reverting.
// Actually not sure why this breaks more easily otherwise, but a lot of cases are of this simpler form,
// so it's a good start.
bool keep = false;
for (size_t j = i + 1; j < m_items.size(); ++j)
if (SemanticInformation::altersControlFlow(m_items[j]))
{
keep = SemanticInformation::terminatesControlFlow(m_items[j]);
break;
}
if (!keep)
continue;
auto it = blocksSeen.find(i);
if (it == blocksSeen.end())
blocksSeen.insert(i);
else
m_replacedTags[m_items.at(i).data()] = m_items.at(*it).data();
{
// It we find a match, map it to the representative.
tagToRepresentative[m_items.at(i).data()] = m_items.at(*it).data();
// And add it to the candidate list.
replacementCandidates[m_items.at(*it).data()].push_back(m_items.at(i).data());

}
}

// Sort all representative lists with respect to their order in the item sequence.
// Note: could make it a set with this as comparison.
for (auto& [representative, replacementCandidates]: replacementCandidates)
std::sort(replacementCandidates.begin(), replacementCandidates.end(), [&](u256 const& _lhs, u256 const& _rhs)
{
return tagOffsets[_lhs] < tagOffsets[_rhs];
});

// For all mappings from tags to representatives, map it to the last candidate.
for (auto&& [tag, representative]: tagToRepresentative)
{
u256 replacement = replacementCandidates.at(representative).back();
if (tag != replacement) // Is this really necessary?
{
m_replacedTags[tag] = replacement;
}
}

if (!applyTagReplacement(m_items, m_replacedTags))
Expand Down
3 changes: 2 additions & 1 deletion libevmasm/ConstantOptimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ unsigned ConstantOptimisationMethod::optimiseConstants(
ComputeMethod compute(params, item.data());
bigint computeGas = compute.gasNeeded();
AssemblyItems replacement;
if (copyGas < literalGas && copyGas < computeGas)
// TODO: Prevents choosing codecopy for EOF. Could be brought back using data section.
if (!_assembly.eofVersion().has_value() && copyGas < literalGas && copyGas < computeGas)
{
replacement = copy.execute(_assembly);
optimisations++;
Expand Down
66 changes: 56 additions & 10 deletions libevmasm/JumpdestRemover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
* Removes unused JUMPDESTs.
*/

#include "SemanticInformation.h"


#include <libevmasm/JumpdestRemover.h>

#include <libevmasm/AssemblyItem.h>
Expand All @@ -37,20 +40,63 @@ bool JumpdestRemover::optimise(std::set<size_t> const& _tagsReferencedFromOutsid

size_t initialSize = m_items.size();
/// Remove tags which are never referenced.
auto pend = remove_if(
m_items.begin(),
m_items.end(),
[&](AssemblyItem const& _item)
AssemblyItems newItems;
for (auto it = m_items.begin(); it != m_items.end();)
{
// If we're not a tag: copy.
if (it->type() != Tag)
{
newItems.emplace_back(*it);
++it;
}
else
{
if (_item.type() != Tag)
return false;
auto asmIdAndTag = _item.splitForeignPushTag();
// We're a tag.
auto asmIdAndTag = it->splitForeignPushTag();
assertThrow(asmIdAndTag.first == std::numeric_limits<size_t>::max(), OptimizerException, "Sub-assembly tag used as label.");
size_t tag = asmIdAndTag.second;
return !references.count(tag);
if (references.count(tag))
{
// We're explicitly referenced; copy.
newItems.emplace_back(*it);
++it;
}
else
{
// We look like an unreferenced tag.
if (it == m_items.begin() || (it - 1)->type() == ConditionalRelativeJump)
{
// We're a tag after a conditional jump. This means we're still reachable.
// We can remove the tag, but need to continue copying afterwards.
++it;
}
else
{
// We're a fully unreachable tag.
// We remove the tag and everything up to the next tag or change of control flow.
// TODO: Why do we need to also stop at control flow alterations? Shouldn't anything that can be
// reachable again first be a tag? Apparently not.
++it;
while (it != m_items.end())
{
if (it->type() == Tag)
break;
if (SemanticInformation::altersControlFlow(*it))
{
++it;
break;
}
++it;
}
if (it == m_items.end())
break;
}
}

}
);
m_items.erase(pend, m_items.end());
}
m_items = std::move(newItems);

return m_items.size() != initialSize;
}

Expand Down
4 changes: 2 additions & 2 deletions test/libsolidity/SemanticTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ SemanticTest::SemanticTest(
langutil::EVMVersion _evmVersion,
std::optional<uint8_t> _eofVersion,
std::vector<boost::filesystem::path> const& _vmPaths,
bool _enforceGasCost,
bool,
u256 _enforceGasCostMinValue
):
SolidityExecutionFramework(_evmVersion, _eofVersion, _vmPaths, false),
Expand All @@ -70,7 +70,7 @@ SemanticTest::SemanticTest(
m_lineOffset(m_reader.lineNumber()),
m_builtins(makeBuiltins()),
m_sideEffectHooks(makeSideEffectHooks()),
m_enforceGasCost(_enforceGasCost),
m_enforceGasCost(true),
m_enforceGasCostMinValue(std::move(_enforceGasCostMinValue))
{
static std::set<std::string> const compileViaYulAllowedValues{"also", "true", "false"};
Expand Down
6 changes: 3 additions & 3 deletions test/libsolidity/SemanticTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
_options.evmVersion,
_options.eofVersion,
_options.vmPaths,
_options.enforceGasCost,
true,
_options.enforceGasCostMinValue
);
}
Expand All @@ -72,7 +72,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
langutil::EVMVersion _evmVersion,
std::optional<uint8_t> _eofVersion,
std::vector<boost::filesystem::path> const& _vmPaths,
bool _enforceGasCost = false,
bool _enforceGasCost = true,
u256 _enforceGasCostMinValue = 100000
);

Expand Down Expand Up @@ -122,7 +122,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
bool m_runWithABIEncoderV1Only = false;
bool m_allowNonExistingFunctions = false;
bool m_gasCostFailure = false;
bool m_enforceGasCost = false;
bool m_enforceGasCost = true;
RequiresYulOptimizer m_requiresYulOptimizer{};
u256 m_enforceGasCostMinValue;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ contract C {
}
// ----
// f(bytes): 0x20, 0x80, 0x21, 0x40, 0x7, "abcdefg" -> 0x21, 0x40, 0x7, "abcdefg"
// gas irOptimized: 135499
// gas irOptimized: 134878
// gas legacy: 137095
// gas legacyOptimized: 135823
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ contract C {
}
// ----
// f() -> 0x20, 0x8, 0x40, 0x3, 0x9, 0xa, 0xb
// gas irOptimized: 203167
// gas irOptimized: 202152
// gas legacy: 206263
// gas legacyOptimized: 203172
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ contract C {
// EVMVersion: >homestead
// ----
// test_bytes() ->
// gas irOptimized: 314884
// gas irOptimized: 210960
// gas legacy: 305816
// gas legacyOptimized: 253573
// test_uint256() ->
// gas irOptimized: 448346
// gas irOptimized: 294648
// gas legacy: 421304
// gas legacyOptimized: 351544
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ contract C {
// ----
// library: L
// f() -> 8, 7, 1, 2, 7, 12
// gas irOptimized: 166761
// gas irOptimized: 166491
// gas legacy: 169273
// gas legacyOptimized: 167243
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ contract C {
// EVMVersion: >homestead
// ----
// test_bytes() ->
// gas irOptimized: 314884
// gas irOptimized: 210960
// gas legacy: 305816
// gas legacyOptimized: 253573
// test_uint256() ->
// gas irOptimized: 448346
// gas irOptimized: 294648
// gas legacy: 421304
// gas legacyOptimized: 351544
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ contract C {
// f2() -> 0x20, 0xa0, 0x1, 0x60, 0x2, 0x3, "abc"
// f3() -> 0x20, 0xa0, 0x1, 0x60, 0x2, 0x3, "abc"
// f4() -> 0x20, 0x160, 0x1, 0x80, 0xc0, 0x2, 0x3, "abc", 0x7, 0x40, 0x2, 0x2, 0x3
// gas irOptimized: 111816
// gas irOptimized: 111603
// gas legacy: 113890
// gas legacyOptimized: 111658
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ contract C is B {
}
// ----
// test() -> 77
// gas irOptimized: 55117
// gas irOptimized code: 56800
// gas irOptimized: 54627
// gas irOptimized code: 63400
// gas legacy: 57266
// gas legacy code: 94600
// gas legacyOptimized: 55195
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract C {
// f_which(uint256[],uint256[2],uint256): 0x40, 1, 2, 1, 5, 6 -> 0x20, 0x40, 5, 2
// f_which(uint256[],uint256[2],uint256): 0x40, 1, 2, 1 -> FAILURE
// f_storage(uint256[],uint256[2]): 0x20, 1, 2 -> 0x20, 0x60, 0x20, 1, 2
// gas irOptimized: 111409
// gas irOptimized: 111041
// gas legacy: 112707
// gas legacyOptimized: 111845
// f_storage(uint256[],uint256[2]): 0x40, 1, 2, 5, 6 -> 0x20, 0x80, 0x20, 2, 5, 6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ contract C {
// EVMVersion: >homestead
// ----
// h(uint256[2][]): 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324
// gas irOptimized: 180080
// gas irOptimized: 179430
// gas legacy: 184233
// gas legacyOptimized: 180856
// i(uint256[2][2]): 123, 124, 223, 224 -> 32, 128, 123, 124, 223, 224
// gas irOptimized: 112031
// gas irOptimized: 111736
// gas legacy: 115091
// gas legacyOptimized: 112657
Loading