Skip to content

Commit

Permalink
eof: Support CALLF, JUMPF and RETF
Browse files Browse the repository at this point in the history
  • Loading branch information
rodiazet committed Oct 30, 2024
1 parent 56b3d7b commit 8ceb5c4
Show file tree
Hide file tree
Showing 22 changed files with 442 additions and 27 deletions.
37 changes: 34 additions & 3 deletions libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,18 @@ void Assembly::assemblyStream(
f.feed(i, _debugInfoSelection);
f.flush();

// Implementing this requires introduction of CALLF, RETF and JUMPF
if (m_codeSections.size() > 1)
solUnimplemented("Add support for more code sections");
{
for (size_t i = 1; i < m_codeSections.size(); ++i)
{
_out << std::endl << _prefix << "code_section_" << i << ": assembly {\n";
Functionalizer codeSectionF(_out, _prefix + " ", _sourceCodes, *this);
for (auto const& item: m_codeSections[i].items)
codeSectionF.feed(item, _debugInfoSelection);
codeSectionF.flush();
_out << _prefix << "}" << std::endl;
}
}

if (!m_data.empty() || !m_subs.empty())
{
Expand Down Expand Up @@ -1415,7 +1424,10 @@ LinkerObject const& Assembly::assembleEOF() const
item.instruction() != Instruction::RETURNCONTRACT &&
item.instruction() != Instruction::EOFCREATE &&
item.instruction() != Instruction::RJUMP &&
item.instruction() != Instruction::RJUMPI
item.instruction() != Instruction::RJUMPI &&
item.instruction() != Instruction::CALLF &&
item.instruction() != Instruction::JUMPF &&
item.instruction() != Instruction::RETF
);
solAssert(!(item.instruction() >= Instruction::PUSH0 && item.instruction() <= Instruction::PUSH32));
ret.bytecode += assembleOperation(item);
Expand Down Expand Up @@ -1470,6 +1482,25 @@ LinkerObject const& Assembly::assembleEOF() const
appendBigEndianUint16(ret.bytecode, item.data());
break;
}
case CallF:
{
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::CALLF));
solAssert(item.data() <= std::numeric_limits<uint16_t>::max(), "Invalid callf index value.");
appendBigEndianUint16(ret.bytecode, item.data());
break;
}
case JumpF:
{
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::JUMPF));
solAssert(item.data() <= std::numeric_limits<uint16_t>::max(), "Invalid jumpf index value.");
appendBigEndianUint16(ret.bytecode, item.data());
break;
}
case RetF:
{
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::RETF));
break;
}
default:
solThrow(InvalidOpcode, "Unexpected opcode while assembling.");
}
Expand Down
51 changes: 51 additions & 0 deletions libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,50 @@ class Assembly
}

std::optional<uint8_t> eofVersion() const { return m_eofVersion; }
bool supportsFunctions() const { return m_eofVersion.has_value(); }
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++); }
AssemblyItem newFunctionCall(uint16_t _functionID)
{
solAssert(_functionID < m_codeSections.size(), "Call to undeclared function.");
solAssert(_functionID > 0, "Cannot call call section 0");
auto const& section = m_codeSections.at(_functionID);
if (section.outputs != 0x80)
return AssemblyItem::functionCall(_functionID, section.inputs, section.outputs);
else
return AssemblyItem::jumpF(_functionID, section.inputs);
}

AssemblyItem newFunctionReturn()
{
return AssemblyItem::functionReturn(m_codeSections.at(m_currentCodeSection).outputs);
}

uint16_t createFunction(uint8_t _args, uint8_t _rets)
{
size_t functionID = m_codeSections.size();
solAssert(functionID < 1024, "Too many functions.");
solAssert(m_currentCodeSection == 0, "Functions need to be declared from the main block.");
solAssert(_rets <= 0x80, "Too many function returns.");
m_codeSections.emplace_back(CodeSection{_args, _rets, {}});
return static_cast<uint16_t>(functionID);
}

void beginFunction(uint16_t _functionID)
{
solAssert(m_currentCodeSection == 0, "Atempted to begin a function before ending the last one.");
solAssert(_functionID < m_codeSections.size(), "Attempt to begin an undeclared function.");
auto& section = m_codeSections.at(_functionID);
solAssert(section.items.empty(), "Function already defined.");
m_currentCodeSection = _functionID;
}
void endFunction()
{
solAssert(m_currentCodeSection != 0, "End function without begin function.");
m_currentCodeSection = 0;
}

/// Returns a tag identified by the given name. Creates it if it does not yet exist.
AssemblyItem namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional<uint64_t> _sourceID);
AssemblyItem newData(bytes const& _data) { util::h256 h(util::keccak256(util::asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); }
Expand Down Expand Up @@ -111,6 +152,16 @@ class Assembly
return append(AssemblyItem::returnContract(_containerId));
}

AssemblyItem appendFunctionCall(uint16_t _functionID)
{
return append(newFunctionCall(_functionID));
}

AssemblyItem appendFunctionReturn()
{
return append(newFunctionReturn());
}

AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }
AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; }
Expand Down
36 changes: 35 additions & 1 deletion libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ std::pair<std::string, std::string> AssemblyItem::nameAndData(langutil::EVMVersi
case ReturnContract:
case RelativeJump:
case ConditionalRelativeJump:
case CallF:
case JumpF:
case RetF:
return {instructionInfo(instruction(), _evmVersion).name, m_data != nullptr ? toStringInHex(*m_data) : ""};
case Push:
return {"PUSH", toStringInHex(data())};
Expand Down Expand Up @@ -130,6 +133,7 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _
{
case Operation:
case Tag: // 1 byte for the JUMPDEST
case RetF:
return 1;
case Push:
return
Expand Down Expand Up @@ -172,6 +176,8 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _
case RelativeJump:
case ConditionalRelativeJump:
case AuxDataLoadN:
case JumpF:
case CallF:
return 1 + 2;
case EOFCreate:
return 2;
Expand All @@ -186,14 +192,21 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _

size_t AssemblyItem::arguments() const
{
if (hasInstruction())
if (hasInstruction() && type() != CallF && type() != JumpF && type() != RetF)
// 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()).args);
else if (type() == VerbatimBytecode)
return std::get<0>(*m_verbatimBytecode);
else if (type() == AssignImmutable)
return 2;
else if (type() == CallF || type() == JumpF)
{
solAssert(m_functionSignature.has_value());
return (*m_functionSignature).argsNum;
}
else if (type() == RetF)
return static_cast<size_t>(data());
else
return 0;
}
Expand Down Expand Up @@ -226,6 +239,12 @@ size_t AssemblyItem::returnValues() const
return std::get<1>(*m_verbatimBytecode);
case AuxDataLoadN:
return 1;
case CallF:
solAssert(m_functionSignature.has_value());
return (*m_functionSignature).retsNum;
case JumpF:
case RetF:
return 0;
case AssignImmutable:
case UndefinedItem:
break;
Expand All @@ -244,6 +263,9 @@ bool AssemblyItem::canBeFunctional() const
case ReturnContract:
case RelativeJump:
case ConditionalRelativeJump:
case CallF:
case JumpF:
case RetF:
return !isDupInstruction(instruction()) && !isSwapInstruction(instruction());
case Push:
case PushTag:
Expand Down Expand Up @@ -374,6 +396,15 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
case ConditionalRelativeJump:
text = "rjumpi{" + std::string("tag_") + std::to_string(static_cast<size_t>(data())) + "}";
break;
case CallF:
text = "callf{" + std::to_string(static_cast<size_t>(data())) + "}";
break;
case JumpF:
text = "jumpf{" + std::to_string(static_cast<size_t>(data())) + "}";
break;
case RetF:
text = "retf";
break;
}
if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction)
{
Expand All @@ -396,6 +427,9 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons
case ReturnContract:
case RelativeJump:
case ConditionalRelativeJump:
case CallF:
case JumpF:
case RetF:
_out << " " << instructionInfo(_item.instruction(), EVMVersion()).name;
if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI)
_out << "\t" << _item.getJumpTypeAsString();
Expand Down
34 changes: 33 additions & 1 deletion libevmasm/AssemblyItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ enum AssemblyItemType
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
CallF, ///< Calls a function under function ID. Function is returning.
JumpF, ///< Calls a function under function ID. Function is non-returning.
RetF, ///< Returns from returning function to the caller.
VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification.
};

Expand Down Expand Up @@ -105,6 +108,25 @@ class AssemblyItem
m_debugData{langutil::DebugData::create()}
{}

static AssemblyItem functionCall(uint16_t _functionID, uint8_t _args, uint8_t _rets, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
// TODO: Make this constructor this way that it's impossible to create it without setting functions signature.
// It can be done by template constructor with Instruction as template parameter i.e. Same for JumpF below.
AssemblyItem result(CallF, Instruction::CALLF, _functionID, _debugData);
result.m_functionSignature = {_args, _rets};
return result;
}
static AssemblyItem jumpF(uint16_t _functionID, uint8_t _args, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
AssemblyItem result(JumpF, Instruction::JUMPF, _functionID, _debugData);
result.m_functionSignature = {_args, 0};
return result;
}
static AssemblyItem functionReturn(uint8_t _rets, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
return AssemblyItem(RetF, Instruction::RETF, _rets, _debugData);
}

static AssemblyItem eofCreate(ContainerID _containerID, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
return AssemblyItem(EOFCreate, Instruction::EOFCREATE, _containerID, std::move(_debugData));
Expand Down Expand Up @@ -162,7 +184,10 @@ class AssemblyItem
m_type == EOFCreate ||
m_type == ReturnContract ||
m_type == RelativeJump ||
m_type == ConditionalRelativeJump;
m_type == ConditionalRelativeJump ||
m_type == CallF ||
m_type == JumpF ||
m_type == RetF;
}
/// @returns the instruction of this item (only valid if hasInstruction returns true)
Instruction instruction() const
Expand Down Expand Up @@ -267,6 +292,13 @@ class AssemblyItem
AssemblyItemType m_type;
Instruction m_instruction; ///< Only valid if m_type == Operation
std::shared_ptr<u256> m_data; ///< Only valid if m_type != Operation

struct FunctionSignature
{
uint8_t argsNum;
uint8_t retsNum;
};
std::optional<FunctionSignature> m_functionSignature; ///< Only valid if m_type == CallF or JumpF
/// If m_type == VerbatimBytecode, this holds number of arguments, number of
/// return variables and verbatim bytecode.
std::optional<std::tuple<size_t, size_t, bytes>> m_verbatimBytecode;
Expand Down
6 changes: 6 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ std::map<std::string, Instruction> const solidity::evmasm::c_instructions =
{ "LOG3", Instruction::LOG3 },
{ "LOG4", Instruction::LOG4 },
{ "DATALOADN", Instruction::DATALOADN },
{ "CALLF", Instruction::CALLF },
{ "RETF", Instruction::RETF },
{ "JUMPF", Instruction::JUMPF },
{ "RJUMP", Instruction::RJUMP },
{ "RJUMPI", Instruction::RJUMPI },
{ "EOFCREATE", Instruction::EOFCREATE },
Expand Down Expand Up @@ -330,6 +333,9 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{Instruction::LOG2, {"LOG2", 0, 4, 0, true, Tier::Special}},
{Instruction::LOG3, {"LOG3", 0, 5, 0, true, Tier::Special}},
{Instruction::LOG4, {"LOG4", 0, 6, 0, true, Tier::Special}},
{Instruction::RETF, {"RETF", 0, 0, 0, true, Tier::Special}},
{Instruction::CALLF, {"CALLF", 2, 0, 0, true, Tier::Special}},
{Instruction::JUMPF, {"JUMPF", 2, 0, 0, true, Tier::Special}},
{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
3 changes: 3 additions & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ enum class Instruction: uint8_t

RJUMP = 0xe0, ///< relative jump
RJUMPI = 0xe1, ///< conditional relative jump
CALLF = 0xe3, ///< call function in a EOF code section
RETF = 0xe4, ///< return to caller from the code section of EOF continer
JUMPF = 0xe5, ///< jump to a code section of EOF contaner. No stack cleaning.
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
7 changes: 7 additions & 0 deletions libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
case PushDeployTimeAddress:
case AssignImmutable:
case VerbatimBytecode:
case CallF:
case JumpF:
case RetF:
return true;
case Push:
case PushTag:
Expand Down Expand Up @@ -331,6 +334,9 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item)
case Instruction::INVALID:
case Instruction::REVERT:
case Instruction::RETURNCONTRACT:
case Instruction::CALLF:
case Instruction::JUMPF:
case Instruction::RETF:
return true;
default:
return false;
Expand Down Expand Up @@ -397,6 +403,7 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
case Instruction::RETURNDATACOPY: // depends on previous calls
case Instruction::RETURNDATASIZE:
case Instruction::EOFCREATE:
case Instruction::CALLF:
return false;
default:
return true;
Expand Down
3 changes: 3 additions & 0 deletions liblangutil/EVMVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional<uint8_t> _eofVersi
case Instruction::DATALOADN:
case Instruction::RJUMP:
case Instruction::RJUMPI:
case Instruction::CALLF:
case Instruction::JUMPF:
case Instruction::RETF:
return _eofVersion.has_value();
default:
return true;
Expand Down
14 changes: 14 additions & 0 deletions libyul/backends/evm/AbstractAssembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class AbstractAssembly
using LabelID = size_t;
using SubID = size_t;
using ContainerID = uint8_t;
using FunctionID = uint16_t;
enum class JumpType { Ordinary, IntoFunction, OutOfFunction };

virtual ~AbstractAssembly() = default;
Expand Down Expand Up @@ -101,6 +102,19 @@ class AbstractAssembly
virtual void appendAssemblySize() = 0;
/// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset.
virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(bool _creation, std::string _name = "") = 0;

/// Creates new function with given signature and returns newly created function ID
virtual FunctionID createFunction(uint8_t _args, uint8_t _rets) = 0;
/// Starts filling function body under given function ID
virtual void beginFunction(FunctionID _functionID) = 0;
/// Ends currently being filled function
virtual void endFunction() = 0;

/// Appends function call to a function under given ID
virtual void appendFunctionCall(FunctionID _functionID) = 0;
/// Appends function return from currently being filled function.
virtual void appendFunctionReturn() = 0;

/// Appends the offset of the given sub-assembly or data.
virtual void appendDataOffset(std::vector<SubID> const& _subPath) = 0;
/// Appends the size of the given sub-assembly or data.
Expand Down
Loading

0 comments on commit 8ceb5c4

Please sign in to comment.