Skip to content

Commit

Permalink
Merge pull request #15547 from ipsilon/eof-rjumps
Browse files Browse the repository at this point in the history
eof: Support relative jumps
  • Loading branch information
cameel authored Nov 22, 2024
2 parents 060bcea + 379b724 commit 4231717
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 18 deletions.
25 changes: 24 additions & 1 deletion libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,7 @@ LinkerObject const& Assembly::assembleEOF() const

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, size_t> tagRef;

for (auto&& [codeSectionIndex, codeSection]: m_codeSections | ranges::views::enumerate)
{
Expand All @@ -1412,7 +1413,9 @@ LinkerObject const& Assembly::assembleEOF() const
solAssert(
item.instruction() != Instruction::DATALOADN &&
item.instruction() != Instruction::RETURNCONTRACT &&
item.instruction() != Instruction::EOFCREATE
item.instruction() != Instruction::EOFCREATE &&
item.instruction() != Instruction::RJUMP &&
item.instruction() != Instruction::RJUMPI
);
solAssert(!(item.instruction() >= Instruction::PUSH0 && item.instruction() <= Instruction::PUSH32));
ret.bytecode += assembleOperation(item);
Expand All @@ -1427,6 +1430,14 @@ LinkerObject const& Assembly::assembleEOF() const
ret.linkReferences.insert(linkRef);
break;
}
case RelativeJump:
case ConditionalRelativeJump:
{
ret.bytecode.push_back(static_cast<uint8_t>(item.instruction()));
tagRef[ret.bytecode.size()] = item.relativeJumpTagID();
appendBigEndianUint16(ret.bytecode, 0u);
break;
}
case EOFCreate:
{
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::EOFCREATE));
Expand Down Expand Up @@ -1465,6 +1476,18 @@ LinkerObject const& Assembly::assembleEOF() const
setBigEndianUint16(ret.bytecode, codeSectionSizePositions[codeSectionIndex], ret.bytecode.size() - sectionStart);
}

for (auto const& [refPos, tagId]: tagRef)
{
solAssert(tagId < m_tagPositionsInBytecode.size(), "Reference to non-existing tag.");
size_t tagPos = m_tagPositionsInBytecode[tagId];
solAssert(tagPos != std::numeric_limits<size_t>::max(), "Reference to tag without position.");

ptrdiff_t const relativeJumpOffset = static_cast<ptrdiff_t>(tagPos) - (static_cast<ptrdiff_t>(refPos) + 2);
solRequire(-0x8000 <= relativeJumpOffset && relativeJumpOffset <= 0x7FFF, AssemblyException, "Relative jump too far");
solAssert(relativeJumpOffset < -2 || 0 <= relativeJumpOffset, "Relative jump offset into immediate argument.");
setBigEndianUint16(ret.bytecode, refPos, static_cast<size_t>(static_cast<uint16_t>(relativeJumpOffset)));
}

for (auto i: referencedSubIds)
ret.bytecode += m_subs[i]->assemble().bytecode;

Expand Down
1 change: 1 addition & 0 deletions libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Assembly
}

std::optional<uint8_t> eofVersion() const { return m_eofVersion; }
bool supportsRelativeJumps() const { return m_eofVersion.has_value(); }
AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); }
AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); }
/// Returns a tag identified by the given name. Creates it if it does not yet exist.
Expand Down
29 changes: 27 additions & 2 deletions libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,30 @@ AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const

std::pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const
{
assertThrow(m_type == PushTag || m_type == Tag, util::Exception, "");
solAssert(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump);
u256 combined = u256(data());
size_t subId = static_cast<size_t>((combined >> 64) - 1);
size_t tag = static_cast<size_t>(combined & 0xffffffffffffffffULL);
return std::make_pair(subId, tag);
}

size_t AssemblyItem::relativeJumpTagID() const
{
solAssert(m_type == RelativeJump || m_type == ConditionalRelativeJump);
auto const [subId, tagId] = splitForeignPushTag();
solAssert(subId == std::numeric_limits<size_t>::max(), "Relative jump to sub");
return tagId;
}

std::pair<std::string, std::string> AssemblyItem::nameAndData(langutil::EVMVersion _evmVersion) const
{
switch (type())
{
case Operation:
case EOFCreate:
case ReturnContract:
case RelativeJump:
case ConditionalRelativeJump:
return {instructionInfo(instruction(), _evmVersion).name, m_data != nullptr ? toStringInHex(*m_data) : ""};
case Push:
return {"PUSH", toStringInHex(data())};
Expand Down Expand Up @@ -115,7 +125,8 @@ std::pair<std::string, std::string> AssemblyItem::nameAndData(langutil::EVMVersi

void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
{
assertThrow(m_type == PushTag || m_type == Tag, util::Exception, "");
solAssert(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump);
solAssert(!(m_type == RelativeJump || m_type == ConditionalRelativeJump) || _subId == std::numeric_limits<size_t>::max());
u256 data = _tag;
if (_subId != std::numeric_limits<size_t>::max())
data |= (u256(_subId) + 1) << 64;
Expand Down Expand Up @@ -167,6 +178,8 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _
}
case VerbatimBytecode:
return std::get<2>(*m_verbatimBytecode).size();
case RelativeJump:
case ConditionalRelativeJump:
case AuxDataLoadN:
return 1 + 2;
case EOFCreate:
Expand Down Expand Up @@ -201,6 +214,8 @@ size_t AssemblyItem::returnValues() const
case Operation:
case EOFCreate:
case ReturnContract:
case RelativeJump:
case ConditionalRelativeJump:
// 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);
Expand Down Expand Up @@ -236,6 +251,8 @@ bool AssemblyItem::canBeFunctional() const
case Operation:
case EOFCreate:
case ReturnContract:
case RelativeJump:
case ConditionalRelativeJump:
return !isDupInstruction(instruction()) && !isSwapInstruction(instruction());
case Push:
case PushTag:
Expand Down Expand Up @@ -360,6 +377,12 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
case ReturnContract:
text = "returcontract{" + std::to_string(static_cast<size_t>(data())) + "}";
break;
case RelativeJump:
text = "rjump{" + std::string("tag_") + std::to_string(relativeJumpTagID()) + "}";
break;
case ConditionalRelativeJump:
text = "rjumpi{" + std::string("tag_") + std::to_string(relativeJumpTagID()) + "}";
break;
}
if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction)
{
Expand All @@ -380,6 +403,8 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons
case Operation:
case EOFCreate:
case ReturnContract:
case RelativeJump:
case ConditionalRelativeJump:
_out << " " << instructionInfo(_item.instruction(), EVMVersion()).name;
if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI)
_out << "\t" << _item.getJumpTypeAsString();
Expand Down
27 changes: 23 additions & 4 deletions libevmasm/AssemblyItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ enum AssemblyItemType
AuxDataLoadN,
EOFCreate, ///< Creates new contract using subcontainer as initcode
ReturnContract, ///< Returns new container (with auxiliary data filled in) to be deployed
RelativeJump, ///< Jumps to relative position accordingly to its argument
ConditionalRelativeJump, ///< Same as RelativeJump but takes condition from the stack
VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification.
};

Expand Down Expand Up @@ -111,20 +113,32 @@ class AssemblyItem
{
return AssemblyItem(ReturnContract, Instruction::RETURNCONTRACT, _containerID, std::move(_debugData));
}
static AssemblyItem relativeJumpTo(AssemblyItem _tag, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
solAssert(_tag.type() == Tag);
return AssemblyItem(RelativeJump, Instruction::RJUMP, _tag.data(), _debugData);
}
static AssemblyItem conditionalRelativeJumpTo(AssemblyItem _tag, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
solAssert(_tag.type() == Tag);
return AssemblyItem(ConditionalRelativeJump, Instruction::RJUMPI, _tag.data(), _debugData);
}

AssemblyItem(AssemblyItem const&) = default;
AssemblyItem(AssemblyItem&&) = default;
AssemblyItem& operator=(AssemblyItem const&) = default;
AssemblyItem& operator=(AssemblyItem&&) = default;

AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); return AssemblyItem(Tag, data()); }
AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); return AssemblyItem(PushTag, data()); }
AssemblyItem tag() const { solAssert(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump); return AssemblyItem(Tag, data()); }
AssemblyItem pushTag() const { solAssert(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump); return AssemblyItem(PushTag, data()); }
/// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies.
/// @param _subId the identifier of the subassembly the tag is taken from.
AssemblyItem toSubAssemblyTag(size_t _subId) const;
/// @returns splits the data of the push tag into sub assembly id and actual tag id.
/// The sub assembly id of non-foreign push tags is -1.
std::pair<size_t, size_t> splitForeignPushTag() const;
/// @returns relative jump target tag ID. Asserts that it is not foreign tag.
size_t relativeJumpTagID() const;
/// Sets sub-assembly part and tag for a push tag.
void setPushTagSubIdAndTag(size_t _subId, size_t _tag);

Expand All @@ -145,9 +159,14 @@ class AssemblyItem
/// @returns true if the item has m_instruction properly set.
bool hasInstruction() const
{
return m_type == Operation || m_type == EOFCreate || m_type == ReturnContract;
return
m_type == Operation ||
m_type == EOFCreate ||
m_type == ReturnContract ||
m_type == RelativeJump ||
m_type == ConditionalRelativeJump;
}
/// @returns the instruction of this item (only valid if type() == Operation || EOFCreate || ReturnContract)
/// @returns the instruction of this item (only valid if hasInstruction returns true)
Instruction instruction() const
{
solAssert(hasInstruction());
Expand Down
4 changes: 2 additions & 2 deletions libevmasm/BlockDeduplicator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ bool BlockDeduplicator::applyTagReplacement(
{
bool changed = false;
for (AssemblyItem& item: _items)
if (item.type() == PushTag)
if (item.type() == PushTag || item.type() == RelativeJump || item.type() == ConditionalRelativeJump)
{
size_t subId;
size_t tagId;
Expand All @@ -131,7 +131,7 @@ BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++()
{
if (it == end)
return *this;
if (SemanticInformation::altersControlFlow(*it) && *it != AssemblyItem{Instruction::JUMPI})
if (SemanticInformation::altersControlFlow(*it) && *it != AssemblyItem{Instruction::JUMPI} && it->type() != ConditionalRelativeJump)
it = end;
else
{
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ unsigned GasMeter::runGas(Instruction _instruction, langutil::EVMVersion _evmVer
{
case Tier::Zero: return GasCosts::tier0Gas;
case Tier::Base: return GasCosts::tier1Gas;
case Tier::RJump: return GasCosts::tier1Gas;
case Tier::RJumpI: return GasCosts::rjumpiGas;
case Tier::VeryLow: return GasCosts::tier2Gas;
case Tier::Low: return GasCosts::tier3Gas;
case Tier::Mid: return GasCosts::tier4Gas;
Expand Down
1 change: 1 addition & 0 deletions libevmasm/GasMeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ namespace GasCosts
return _evmVersion >= langutil::EVMVersion::istanbul() ? 16 : 68;
}
static unsigned const copyGas = 3;
static unsigned const rjumpiGas = 4;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ std::map<std::string, Instruction> const solidity::evmasm::c_instructions =
{ "LOG3", Instruction::LOG3 },
{ "LOG4", Instruction::LOG4 },
{ "DATALOADN", Instruction::DATALOADN },
{ "RJUMP", Instruction::RJUMP },
{ "RJUMPI", Instruction::RJUMPI },
{ "EOFCREATE", Instruction::EOFCREATE },
{ "RETURNCONTRACT", Instruction::RETURNCONTRACT },
{ "CREATE", Instruction::CREATE },
Expand Down Expand Up @@ -256,6 +258,8 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{Instruction::GAS, {"GAS", 0, 0, 1, false, Tier::Base}},
{Instruction::JUMPDEST, {"JUMPDEST", 0, 0, 0, true, Tier::Special}},
{Instruction::DATALOADN, {"DATALOADN", 2, 0, 1, true, Tier::Low}},
{Instruction::RJUMP, {"RJUMP", 2, 0, 0, true, Tier::RJump}},
{Instruction::RJUMPI, {"RJUMPI", 2, 1, 0, true, Tier::RJumpI}},
{Instruction::PUSH0, {"PUSH0", 0, 0, 1, false, Tier::Base}},
{Instruction::PUSH1, {"PUSH1", 1, 0, 1, false, Tier::VeryLow}},
{Instruction::PUSH2, {"PUSH2", 2, 0, 1, false, Tier::VeryLow}},
Expand Down
5 changes: 5 additions & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ enum class Instruction: uint8_t
LOG4, ///< Makes a log entry; 4 topics.

DATALOADN = 0xd1, ///< load data from EOF data section

RJUMP = 0xe0, ///< relative jump
RJUMPI = 0xe1, ///< conditional relative jump
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 @@ -298,7 +301,9 @@ enum class Tier
// NOTE: Tiers should be ordered by cost, since we sometimes perform comparisons between them.
Zero = 0, // 0, Zero
Base, // 2, Quick
RJump, // 2, RJump
VeryLow, // 3, Fastest
RJumpI, // 4,
Low, // 5, Fast
Mid, // 8, Mid
High, // 10, Slow
Expand Down
2 changes: 1 addition & 1 deletion libevmasm/JumpdestRemover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ std::set<size_t> JumpdestRemover::referencedTags(AssemblyItems const& _items, si
{
std::set<size_t> ret;
for (auto const& item: _items)
if (item.type() == PushTag)
if (item.type() == PushTag || item.type() == RelativeJump || item.type() == ConditionalRelativeJump)
{
auto subAndTag = item.splitForeignPushTag();
if (subAndTag.first == _subId)
Expand Down
Loading

0 comments on commit 4231717

Please sign in to comment.