Skip to content
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

eof: Add support for SWAPN/DUPN #15845

Merged
merged 1 commit into from
Feb 17, 2025
Merged
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
22 changes: 20 additions & 2 deletions libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,11 +767,21 @@ AssemblyItem Assembly::newImmutableAssignment(std::string const& _identifier)
return AssemblyItem{AssignImmutable, h};
}

AssemblyItem Assembly::newAuxDataLoadN(size_t _offset)
AssemblyItem Assembly::newAuxDataLoadN(size_t _offset) const
{
return AssemblyItem{AuxDataLoadN, _offset};
}

AssemblyItem Assembly::newSwapN(size_t _depth) const
{
return AssemblyItem::swapN(_depth);
}

AssemblyItem Assembly::newDupN(size_t _depth) const
{
return AssemblyItem::dupN(_depth);
}

Assembly& Assembly::optimise(OptimiserSettings const& _settings)
{
optimiseInternal(_settings, {});
Expand Down Expand Up @@ -1558,7 +1568,9 @@ LinkerObject const& Assembly::assembleEOF() const
item.instruction() != Instruction::RJUMPI &&
item.instruction() != Instruction::CALLF &&
item.instruction() != Instruction::JUMPF &&
item.instruction() != Instruction::RETF
item.instruction() != Instruction::RETF &&
item.instruction() != Instruction::DUPN &&
item.instruction() != Instruction::SWAPN
);
solAssert(!(item.instruction() >= Instruction::PUSH0 && item.instruction() <= Instruction::PUSH32));
ret.bytecode += assembleOperation(item);
Expand Down Expand Up @@ -1636,6 +1648,12 @@ LinkerObject const& Assembly::assembleEOF() const
case RetF:
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::RETF));
break;
case SwapN:
case DupN:
ret.bytecode.push_back(static_cast<uint8_t>(item.instruction()));
solAssert(item.data() >= 1 && item.data() <= 256);
ret.bytecode.push_back(static_cast<uint8_t>(item.data() - 1));
break;
default:
solAssert(false, "Unexpected opcode while assembling.");
}
Expand Down
6 changes: 5 additions & 1 deletion libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ class Assembly
AssemblyItem newPushLibraryAddress(std::string const& _identifier);
AssemblyItem newPushImmutable(std::string const& _identifier);
AssemblyItem newImmutableAssignment(std::string const& _identifier);
AssemblyItem newAuxDataLoadN(size_t offset);
AssemblyItem newAuxDataLoadN(size_t offset) const;
AssemblyItem newSwapN(size_t _depth) const;
AssemblyItem newDupN(size_t _depth) const;

AssemblyItem const& append(AssemblyItem _i);
AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
Expand All @@ -102,6 +104,8 @@ class Assembly
void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
void appendAuxDataLoadN(uint16_t _offset) { append(newAuxDataLoadN(_offset));}
void appendSwapN(size_t _depth) { append(newSwapN(_depth)); }
void appendDupN(size_t _depth) { append(newDupN(_depth)); }

void appendVerbatim(bytes _data, size_t _arguments, size_t _returnVariables)
{
Expand Down
27 changes: 26 additions & 1 deletion libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <libevmasm/AssemblyItem.h>

#include <libevmasm/Assembly.h>
#include <libevmasm/SemanticInformation.h>

#include <libsolutil/CommonData.h>
#include <libsolutil/CommonIO.h>
Expand Down Expand Up @@ -90,6 +91,9 @@ std::pair<std::string, std::string> AssemblyItem::nameAndData(langutil::EVMVersi
case JumpF:
case RetF:
return {instructionInfo(instruction(), _evmVersion).name, ""};
case SwapN:
case DupN:
return {instructionInfo(instruction(), _evmVersion).name, util::toString(static_cast<size_t>(data())) };
case Push:
return {"PUSH", toStringInHex(data())};
case PushTag:
Expand Down Expand Up @@ -192,6 +196,10 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _
return 2;
case ReturnContract:
return 2;
case SwapN:
return 2;
case DupN:
return 2;
case UndefinedItem:
solAssert(false);
}
Expand All @@ -203,6 +211,10 @@ size_t AssemblyItem::arguments() const
{
if (type() == CallF || type() == JumpF)
return functionSignature().argsNum;
else if (type() == SwapN)
return static_cast<size_t>(data()) + 1;
else if (type() == DupN)
return static_cast<size_t>(data());
else if (hasInstruction())
{
solAssert(instruction() != Instruction::CALLF && instruction() != Instruction::JUMPF);
Expand Down Expand Up @@ -231,6 +243,9 @@ size_t AssemblyItem::returnValues() const
// The latest EVMVersion is used here, since the InstructionInfo is assumed to be
// the same across all EVM versions except for the instruction name.
return static_cast<size_t>(instructionInfo(instruction(), EVMVersion()).ret);
case SwapN:
case DupN:
return static_cast<size_t>(data()) + 1;
case Push:
case PushTag:
case PushData:
Expand Down Expand Up @@ -270,8 +285,10 @@ bool AssemblyItem::canBeFunctional() const
case ConditionalRelativeJump:
case CallF:
case JumpF:
case SwapN:
case DupN:
case RetF:
return !isDupInstruction(instruction()) && !isSwapInstruction(instruction());
return !SemanticInformation::isDupInstruction(*this) && !SemanticInformation::isSwapInstruction(*this);
case Push:
case PushTag:
case PushData:
Expand Down Expand Up @@ -410,6 +427,12 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
case RetF:
text = "retf";
break;
case SwapN:
text = "swapn{" + std::to_string(static_cast<size_t>(data())) + "}";
break;
case DupN:
text = "dupn{" + std::to_string(static_cast<size_t>(data())) + "}";
break;
}
if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction)
{
Expand All @@ -435,6 +458,8 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons
case CallF:
case JumpF:
case RetF:
case SwapN:
case DupN:
_out << " " << instructionInfo(_item.instruction(), EVMVersion()).name;
if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI)
_out << "\t" << _item.getJumpTypeAsString();
Expand Down
18 changes: 16 additions & 2 deletions libevmasm/AssemblyItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ enum AssemblyItemType
CallF, ///< Jumps to a returning EOF function, adding a new frame to the return stack.
JumpF, ///< Jumps to a returning or non-returning EOF function without changing the return stack.
RetF, ///< Returns from an EOF function, removing a frame from the return stack.
VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification.
VerbatimBytecode, ///< Contains data that is inserted into the bytecode code section without modification.
SwapN, ///< EOF SWAPN with immediate argument.
DupN, ///< EOF DUPN with immediate argument.
};

enum class Precision { Precise , Approximate };
Expand Down Expand Up @@ -147,6 +149,16 @@ class AssemblyItem
solAssert(_tag.type() == Tag);
return AssemblyItem(ConditionalRelativeJump, Instruction::RJUMPI, _tag.data(), _debugData);
}
static AssemblyItem swapN(size_t _depth, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
solAssert(_depth >= 1 && _depth <= 256);
return AssemblyItem(SwapN, Instruction::SWAPN, _depth, _debugData);
}
static AssemblyItem dupN(size_t _depth, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
solAssert(_depth >= 1 && _depth <= 256);
return AssemblyItem(DupN, Instruction::DUPN, _depth, _debugData);
}

AssemblyItem(AssemblyItem const&) = default;
AssemblyItem(AssemblyItem&&) = default;
Expand Down Expand Up @@ -191,7 +203,9 @@ class AssemblyItem
m_type == ConditionalRelativeJump ||
m_type == CallF ||
m_type == JumpF ||
m_type == RetF;
m_type == RetF ||
m_type == SwapN ||
m_type == DupN;
}
/// @returns the instruction of this item (only valid if hasInstruction returns true)
Instruction instruction() const
Expand Down
4 changes: 4 additions & 0 deletions libevmasm/Instruction.cpp
ekpyron marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ std::map<std::string, Instruction> const solidity::evmasm::c_instructions =
{ "CALLF", Instruction::CALLF },
{ "RETF", Instruction::RETF },
{ "JUMPF", Instruction::JUMPF },
{ "DUPN", Instruction::DUPN },
{ "SWAPN", Instruction::SWAPN },
{ "RJUMP", Instruction::RJUMP },
{ "RJUMPI", Instruction::RJUMPI },
{ "EOFCREATE", Instruction::EOFCREATE },
Expand Down Expand Up @@ -339,6 +341,8 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{Instruction::RETF, {"RETF", 0, 0, 0, true, Tier::RetF}},
{Instruction::CALLF, {"CALLF", 2, 0, 0, true, Tier::CallF}},
{Instruction::JUMPF, {"JUMPF", 2, 0, 0, true, Tier::JumpF}},
{Instruction::SWAPN, {"SWAPN", 1, 0, 0, false, Tier::VeryLow}},
{Instruction::DUPN, {"DUPN", 1, 0, 0, false, Tier::VeryLow}},
cameel marked this conversation as resolved.
Show resolved Hide resolved
{Instruction::EOFCREATE, {"EOFCREATE", 1, 4, 1, true, Tier::Special}},
{Instruction::RETURNCONTRACT, {"RETURNCONTRACT", 1, 2, 0, true, Tier::Special}},
{Instruction::CREATE, {"CREATE", 0, 3, 1, true, Tier::Special}},
Expand Down
26 changes: 2 additions & 24 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ enum class Instruction: uint8_t
CALLF = 0xe3, ///< call function in a EOF code section
RETF = 0xe4, ///< return to caller from the code section of EOF container
JUMPF = 0xe5, ///< jump to a code section of EOF container without adding a new return stack frame.
DUPN = 0xe6, ///< copies a value at the stack depth given as immediate argument to the top of the stack
SWAPN = 0xe7, ///< swaps the highest value with a value at a stack depth given as immediate argument
EOFCREATE = 0xec, ///< create a new account with associated container code.
RETURNCONTRACT = 0xee, ///< return container to be deployed with axiliary data filled in.
CREATE = 0xf0, ///< create a new account with associated code
Expand Down Expand Up @@ -232,18 +234,6 @@ inline bool isPushInstruction(Instruction _inst)
return Instruction::PUSH0 <= _inst && _inst <= Instruction::PUSH32;
}

/// @returns true if the instruction is a DUP
inline bool isDupInstruction(Instruction _inst)
{
return Instruction::DUP1 <= _inst && _inst <= Instruction::DUP16;
}

/// @returns true if the instruction is a SWAP
inline bool isSwapInstruction(Instruction _inst)
{
return Instruction::SWAP1 <= _inst && _inst <= Instruction::SWAP16;
}

/// @returns true if the instruction is a LOG
inline bool isLogInstruction(Instruction _inst)
{
Expand All @@ -256,18 +246,6 @@ inline unsigned getPushNumber(Instruction _inst)
return static_cast<uint8_t>(_inst) - unsigned(Instruction::PUSH0);
}

/// @returns the number of DUP Instruction _inst
inline unsigned getDupNumber(Instruction _inst)
{
return static_cast<uint8_t>(_inst) - unsigned(Instruction::DUP1) + 1;
}

/// @returns the number of SWAP Instruction _inst
inline unsigned getSwapNumber(Instruction _inst)
{
return static_cast<uint8_t>(_inst) - unsigned(Instruction::SWAP1) + 1;
}

/// @returns the number of LOG Instruction _inst
inline unsigned getLogNumber(Instruction _inst)
{
Expand Down
4 changes: 2 additions & 2 deletions libevmasm/KnownState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,14 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
setStackElement(
m_stackHeight + 1,
stackElement(
m_stackHeight - static_cast<int>(instruction) + static_cast<int>(Instruction::DUP1),
m_stackHeight - (static_cast<int>(SemanticInformation::getDupNumber(_item)) - 1),
_item.debugData()
)
);
else if (SemanticInformation::isSwapInstruction(_item))
swapStackElements(
m_stackHeight,
m_stackHeight - 1 - static_cast<int>(instruction) + static_cast<int>(Instruction::SWAP1),
m_stackHeight - 1 - (static_cast<int>(SemanticInformation::getSwapNumber(_item)) - 1),
_item.debugData()
);
else if (instruction != Instruction::POP)
Expand Down
2 changes: 1 addition & 1 deletion libevmasm/PeepholeOptimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap>
if (
SemanticInformation::isDupInstruction(_dupN) &&
SemanticInformation::isSwapInstruction(_swapN) &&
getDupNumber(_dupN.instruction()) == getSwapNumber(_swapN.instruction())
SemanticInformation::getDupNumber(_dupN) == SemanticInformation::getSwapNumber(_swapN)
)
{
*_out = _dupN;
Expand Down
28 changes: 26 additions & 2 deletions libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,16 +312,22 @@ bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item)

bool SemanticInformation::isDupInstruction(AssemblyItem const& _item)
{
if (_item.type() == evmasm::DupN)
return true;
if (_item.type() != evmasm::Operation)
return false;
return evmasm::isDupInstruction(_item.instruction());
auto inst = _item.instruction();
return Instruction::DUP1 <= inst && inst <= Instruction::DUP16;
}

bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item)
{
if (_item.type() == evmasm::SwapN)
return true;
if (_item.type() != evmasm::Operation)
return false;
return evmasm::isSwapInstruction(_item.instruction());
auto inst = _item.instruction();
return Instruction::SWAP1 <= inst && inst <= Instruction::SWAP16;
}

bool SemanticInformation::altersControlFlow(AssemblyItem const& _item)
Expand Down Expand Up @@ -387,6 +393,24 @@ bool SemanticInformation::reverts(Instruction _instruction)
}
}

size_t SemanticInformation::getDupNumber(AssemblyItem const& _item)
{
assertThrow(isDupInstruction(_item), OptimizerException, "Not a DUP instruction.");
if (_item.type() == evmasm::DupN)
return static_cast<size_t>(_item.data());
auto inst = _item.instruction();
return static_cast<uint8_t>(inst) - static_cast<size_t>(Instruction::DUP1) + 1;
}

size_t SemanticInformation::getSwapNumber(AssemblyItem const& _item)
{
assertThrow(isSwapInstruction(_item), OptimizerException, "Not a swap instruction.");
if (_item.type() == evmasm::SwapN)
return static_cast<size_t>(_item.data());
auto inst = _item.instruction();
return static_cast<uint8_t>(inst) - static_cast<size_t>(Instruction::SWAP1) + 1;
}

bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
{
assertThrow(_item.type() != VerbatimBytecode, AssemblyException, "");
Expand Down
4 changes: 4 additions & 0 deletions libevmasm/SemanticInformation.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ struct SemanticInformation
static bool terminatesControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(Instruction _instruction);
static bool reverts(Instruction _instruction);
/// @returns the 1-based DUP depth a DUP AssembleItem @a _item
static size_t getDupNumber(AssemblyItem const& _item);
/// @returns the 1-based SWAP depth a DUP AssembleItem @a _item
static size_t getSwapNumber(AssemblyItem const& _item);
/// @returns false if the value put on the stack by _item depends on anything else than
/// the information in the current block header, memory, storage, transient storage or stack.
static bool isDeterministic(AssemblyItem const& _item);
Expand Down
2 changes: 2 additions & 0 deletions liblangutil/EVMVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional<uint8_t> _eofVersi
case Instruction::RJUMPI:
case Instruction::CALLF:
case Instruction::JUMPF:
case Instruction::DUPN:
case Instruction::SWAPN:
case Instruction::RETF:
case Instruction::EXTCALL:
case Instruction::EXTSTATICCALL:
Expand Down
4 changes: 3 additions & 1 deletion libyul/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,9 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio
_instr != evmasm::Instruction::RJUMPI &&
_instr != evmasm::Instruction::CALLF &&
_instr != evmasm::Instruction::JUMPF &&
_instr != evmasm::Instruction::RETF
_instr != evmasm::Instruction::RETF &&
_instr != evmasm::Instruction::DUPN &&
_instr != evmasm::Instruction::SWAPN
);

auto errorForVM = [&](ErrorId _errorId, std::string const& vmKindMessage) {
Expand Down
7 changes: 7 additions & 0 deletions libyul/backends/evm/AbstractAssembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ class AbstractAssembly
/// EOF auxiliary data in data section and the auxiliary data are different things.
virtual void appendToAuxiliaryData(bytes const& _data) = 0;

/// Appends a SWAPN with 1-based indexing for @arg _depth. I.e. a depth of 1 would be equivalent to emitting SWAP1.
/// @arg _depth ranges from 1 to 256.
virtual void appendSwapN(size_t _depth) = 0;
/// Appends a DUPN with 1-based indexing for @arg _depth. I.e. a depth of 1 would be equivalent to emitting DUP1.
/// @arg _depth ranges from 1 to 256.
virtual void appendDupN(size_t _depth) = 0;

/// Mark this assembly as invalid. Any attempt to request bytecode from it should throw.
virtual void markAsInvalid() = 0;

Expand Down
6 changes: 4 additions & 2 deletions libyul/backends/evm/EVMDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ std::vector<std::optional<BuiltinFunctionForEVM>> createBuiltins(langutil::EVMVe
auto const opcode = instr.second;

if (
!evmasm::isDupInstruction(opcode) &&
!evmasm::isSwapInstruction(opcode) &&
!(opcode >= evmasm::Instruction::DUP1 && opcode <= evmasm::Instruction::DUP16) &&
!(opcode >= evmasm::Instruction::SWAP1 && opcode <= evmasm::Instruction::SWAP16) &&
!evmasm::isPushInstruction(opcode) &&
opcode != evmasm::Instruction::JUMP &&
opcode != evmasm::Instruction::JUMPI &&
Expand All @@ -224,6 +224,8 @@ std::vector<std::optional<BuiltinFunctionForEVM>> createBuiltins(langutil::EVMVe
opcode != evmasm::Instruction::RJUMPI &&
opcode != evmasm::Instruction::CALLF &&
opcode != evmasm::Instruction::JUMPF &&
opcode != evmasm::Instruction::DUPN &&
opcode != evmasm::Instruction::SWAPN &&
opcode != evmasm::Instruction::RETF &&
_evmVersion.hasOpcode(opcode, _eofVersion) &&
!prevRandaoException(name)
Expand Down
Loading