Skip to content

eof: Enable constant optimizer for EOF #15985

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

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
27 changes: 21 additions & 6 deletions libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -940,13 +940,13 @@ 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,
m_evmVersion,
*this
*this,
m_eofVersion
);

m_tagReplacements = std::move(tagReplacements);
Expand Down Expand Up @@ -1601,7 +1601,8 @@ LinkerObject const& Assembly::assembleEOF() const
ret.bytecode = headerBytecode;

m_tagPositionsInBytecode = std::vector<size_t>(m_usedTags, std::numeric_limits<size_t>::max());
std::map<size_t, uint16_t> dataSectionRef;
std::map<size_t, uint16_t> staticAuxDataSectionRef;
std::map<h256, std::vector<size_t>> predeployDataSectionRef;
std::map<size_t, size_t> tagRef;

for (auto&& [codeSectionIndex, codeSection]: m_codeSections | ranges::views::enumerate)
Expand Down Expand Up @@ -1677,12 +1678,19 @@ LinkerObject const& Assembly::assembleEOF() const
case Tag:
ret.bytecode += assembleTag(item, ret.bytecode.size(), false);
break;
case DataLoadN:
{
ret.bytecode += assembleOperation(item);
predeployDataSectionRef[item.data()].push_back(ret.bytecode.size());
appendBigEndianUint16(ret.bytecode, 0u);
break;
}
case AuxDataLoadN:
{
// In findMaxAuxDataLoadNOffset we already verified that unsigned data value fits 2 bytes
solAssert(item.data() <= std::numeric_limits<uint16_t>::max(), "Invalid auxdataloadn position.");
ret.bytecode.push_back(uint8_t(Instruction::DATALOADN));
dataSectionRef[ret.bytecode.size()] = static_cast<uint16_t>(item.data());
staticAuxDataSectionRef[ret.bytecode.size()] = static_cast<uint16_t>(item.data());
appendBigEndianUint16(ret.bytecode, item.data());
break;
}
Expand Down Expand Up @@ -1765,7 +1773,14 @@ LinkerObject const& Assembly::assembleEOF() const
auto const dataStart = ret.bytecode.size();

for (auto const& dataItem: m_data)
{
for (auto const pos: predeployDataSectionRef[dataItem.first])
{
solAssert(dataItem.second.size() == 32);
setBigEndianUint16(ret.bytecode, pos, ret.bytecode.size() - dataStart);
}
ret.bytecode += dataItem.second;
}

ret.bytecode += m_auxiliaryData;

Expand All @@ -1784,7 +1799,7 @@ LinkerObject const& Assembly::assembleEOF() const

// If some data was already added to data section we need to update data section refs accordingly
if (preDeployDataSectionSize > 0)
for (auto [refPosition, staticAuxDataOffset] : dataSectionRef)
for (auto [refPosition, staticAuxDataOffset] : staticAuxDataSectionRef)
{
// staticAuxDataOffset + preDeployDataSectionSize value is already verified to fit 2 bytes because
// staticAuxDataOffset < staticAuxDataSize
Expand Down
1 change: 1 addition & 0 deletions libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class Assembly
AssemblyItem newPushLibraryAddress(std::string const& _identifier);
AssemblyItem newPushImmutable(std::string const& _identifier);
AssemblyItem newImmutableAssignment(std::string const& _identifier);
AssemblyItem newDataLoadN(bytes const& _data) { util::h256 h(util::keccak256(util::asString(_data))); m_data[h] = _data; return AssemblyItem(DataLoadN, Instruction::DATALOADN, h); }
AssemblyItem newAuxDataLoadN(size_t offset) const;
AssemblyItem newSwapN(size_t _depth) const;
AssemblyItem newDupN(size_t _depth) const;
Expand Down
11 changes: 11 additions & 0 deletions libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ std::pair<std::string, std::string> AssemblyItem::nameAndData(langutil::EVMVersi
return {"PUSH data", toStringInHex(data())};
case VerbatimBytecode:
return {"VERBATIM", util::toHex(verbatimData())};
case DataLoadN:
return {"DATALOADN", util::toString(data())};
case AuxDataLoadN:
return {"AUXDATALOADN", util::toString(data())};
case UndefinedItem:
Expand Down Expand Up @@ -188,6 +190,7 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _
return std::get<2>(*m_verbatimBytecode).size();
case RelativeJump:
case ConditionalRelativeJump:
case DataLoadN:
case AuxDataLoadN:
case JumpF:
case CallF:
Expand Down Expand Up @@ -260,6 +263,7 @@ size_t AssemblyItem::returnValues() const
return 0;
case VerbatimBytecode:
return std::get<1>(*m_verbatimBytecode);
case DataLoadN:
case AuxDataLoadN:
return 1;
case JumpF:
Expand Down Expand Up @@ -298,6 +302,7 @@ bool AssemblyItem::canBeFunctional() const
case PushLibraryAddress:
case PushDeployTimeAddress:
case PushImmutable:
case DataLoadN:
case AuxDataLoadN:
return true;
case Tag:
Expand Down Expand Up @@ -406,6 +411,9 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
assertThrow(data() <= std::numeric_limits<size_t>::max(), AssemblyException, "Invalid auxdataloadn argument.");
text = "auxdataloadn{" + std::to_string(static_cast<size_t>(data())) + "}";
break;
case DataLoadN:
text = "dataloadn{" + util::toHex(toCompactBigEndian(data(), 1)) + "}";
break;
case EOFCreate:
text = "eofcreate{" + std::to_string(static_cast<size_t>(data())) + "}";
break;
Expand Down Expand Up @@ -509,6 +517,9 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons
case VerbatimBytecode:
_out << " Verbatim " << util::toHex(_item.verbatimData());
break;
case DataLoadN:
_out << " DataLoadN " << util::toString(_item.data());
break;
case AuxDataLoadN:
_out << " AuxDataLoadN " << util::toString(_item.data());
break;
Expand Down
4 changes: 4 additions & 0 deletions libevmasm/AssemblyItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ enum AssemblyItemType
PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor.
AssignImmutable, ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.

/// Loads 32 bytes from static data of EOF data section.
/// More details here: https://github.com/ipsilon/eof/blob/main/spec/eof.md#data-section-lifecycle
DataLoadN,
/// Loads 32 bytes from static auxiliary data of EOF data section. The offset does *not* have to be always from the beginning
/// of the data EOF section. More details here: https://github.com/ipsilon/eof/blob/main/spec/eof.md#data-section-lifecycle
AuxDataLoadN,
Expand Down Expand Up @@ -200,6 +203,7 @@ class AssemblyItem
m_type == ReturnContract ||
m_type == RelativeJump ||
m_type == ConditionalRelativeJump ||
m_type == DataLoadN ||
m_type == CallF ||
m_type == JumpF ||
m_type == RetF ||
Expand Down
137 changes: 79 additions & 58 deletions libevmasm/ConstantOptimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ unsigned ConstantOptimisationMethod::optimiseConstants(
bool _isCreation,
size_t _runs,
langutil::EVMVersion _evmVersion,
Assembly& _assembly
Assembly& _assembly,
std::optional<uint8_t> _eofVersion
)
{
// TODO: design the optimiser in a way this is not needed
Expand All @@ -55,6 +56,7 @@ unsigned ConstantOptimisationMethod::optimiseConstants(
params.isCreation = _isCreation;
params.runs = _runs;
params.evmVersion = _evmVersion;
params.eofVersion = _eofVersion;
LiteralMethod lit(params, item.data());
bigint literalGas = lit.gasNeeded();
CodeCopyMethod copy(params, item.data());
Expand Down Expand Up @@ -161,73 +163,92 @@ AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
{
bytes data = toBigEndian(m_value);
assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding.");
AssemblyItem newPushData = _assembly.newData(data);
return copyRoutine(&newPushData);

if (m_params.eofVersion.has_value())
return AssemblyItems {_assembly.newDataLoadN(data)};
else
{
AssemblyItem newPushData = _assembly.newData(data);
return copyRoutine(&newPushData);
}
}

AssemblyItems CodeCopyMethod::copyRoutine(AssemblyItem* _pushData) const
AssemblyItems CodeCopyMethod::copyRoutine(AssemblyItem* _dataItem) const
{
if (_pushData)
assertThrow(_pushData->type() == PushData, OptimizerException, "Invalid Assembly Item.");

AssemblyItem dataUsed = _pushData ? *_pushData : AssemblyItem(PushData, u256(1) << 16);

// PUSH0 is cheaper than PUSHn/DUP/SWAP.
if (m_params.evmVersion.hasPush0())
if (m_params.eofVersion.has_value())
{
// This costs ~29 gas.
// This case is used only for gas calculation.
solAssert(_dataItem == nullptr);
AssemblyItems copyRoutine{
// back up memory
// mload(0)
u256(0),
Instruction::MLOAD,

// codecopy(0, <offset>, 32)
u256(32),
dataUsed,
u256(0),
Instruction::CODECOPY,

// mload(0)
u256(0),
Instruction::MLOAD,

// restore original memory
// mstore(0, x)
Instruction::SWAP1,
u256(0),
Instruction::MSTORE
AssemblyItem(DataLoadN, Instruction::DATALOADN, u256(1) << 16)
};
return copyRoutine;
}
else
{
// This costs ~33 gas.
AssemblyItems copyRoutine{
// constant to be reused 3+ times
u256(0),

// back up memory
// mload(0)
Instruction::DUP1,
Instruction::MLOAD,

// codecopy(0, <offset>, 32)
u256(32),
dataUsed,
Instruction::DUP4,
Instruction::CODECOPY,

// mload(0)
Instruction::DUP2,
Instruction::MLOAD,

// restore original memory
// mstore(0, x)
Instruction::SWAP2,
Instruction::MSTORE
};
return copyRoutine;
AssemblyItem* _pushData = _dataItem;
if (_pushData)
assertThrow(_pushData->type() == PushData, OptimizerException, "Invalid Assembly Item.");

AssemblyItem dataUsed = _pushData ? *_pushData : AssemblyItem(PushData, u256(1) << 16);

// PUSH0 is cheaper than PUSHn/DUP/SWAP.
if (m_params.evmVersion.hasPush0())
{
// This costs ~29 gas.
AssemblyItems copyRoutine{
// back up memory
// mload(0)
u256(0),
Instruction::MLOAD,

// codecopy(0, <offset>, 32)
u256(32),
dataUsed,
u256(0),
Instruction::CODECOPY,

// mload(0)
u256(0),
Instruction::MLOAD,

// restore original memory
// mstore(0, x)
Instruction::SWAP1,
u256(0),
Instruction::MSTORE
};
return copyRoutine;
}
else
{
// This costs ~33 gas.
AssemblyItems copyRoutine{
// constant to be reused 3+ times
u256(0),

// back up memory
// mload(0)
Instruction::DUP1,
Instruction::MLOAD,

// codecopy(0, <offset>, 32)
u256(32),
dataUsed,
Instruction::DUP4,
Instruction::CODECOPY,

// mload(0)
Instruction::DUP2,
Instruction::MLOAD,

// restore original memory
// mstore(0, x)
Instruction::SWAP2,
Instruction::MSTORE
};
return copyRoutine;
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion libevmasm/ConstantOptimiser.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class ConstantOptimisationMethod
bool _isCreation,
size_t _runs,
langutil::EVMVersion _evmVersion,
Assembly& _assembly
Assembly& _assembly,
std::optional<uint8_t> _eofVersion
);

protected:
Expand All @@ -63,6 +64,7 @@ class ConstantOptimisationMethod
size_t runs; ///< Estimated number of calls per opcode oven the lifetime of the contract.
size_t multiplicity; ///< Number of times the constant appears in the code.
langutil::EVMVersion evmVersion; ///< Version of the EVM
std::optional<uint8_t> eofVersion; ///< Version of EOF. Legacy EVM if not set.
};

explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value):
Expand Down

Large diffs are not rendered by default.

Loading