Skip to content
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
823 changes: 597 additions & 226 deletions libevmasm/Assembly.cpp

Large diffs are not rendered by default.

106 changes: 98 additions & 8 deletions libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,60 @@ using AssemblyPointer = std::shared_ptr<Assembly>;
class Assembly
{
public:
Assembly(langutil::EVMVersion _evmVersion, bool _creation, std::string _name): m_evmVersion(_evmVersion), m_creation(_creation), m_name(std::move(_name)) { }
Assembly(langutil::EVMVersion _evmVersion, bool _creation, std::optional<uint8_t> _eofVersion, std::string _name):
m_evmVersion(_evmVersion),
m_creation(_creation),
m_eofVersion(_eofVersion),
m_name(std::move(_name))
{
// Code section number 0 has to be non-returning.
m_codeSections.emplace_back(CodeSection{0, 0x80, {}});
}

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)
{
assertThrow(_functionID < m_codeSections.size(), AssemblyException, "Call to undeclared function.");
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();
assertThrow(functionID < 1024, AssemblyException, "Too many functions.");
assertThrow(m_currentCodeSection == 0, AssemblyException, "Functions need to be declared from the main block.");
assertThrow(_rets <= 0x80, AssemblyException, "Too many function returns.");
m_codeSections.emplace_back(CodeSection{_args, _rets, {}});
return static_cast<uint16_t>(functionID);
}

void beginFunction(uint16_t _functionID)
{
assertThrow(m_currentCodeSection == 0, AssemblyException, "Atempted to begin a function before ending the last one.");
assertThrow(_functionID < m_codeSections.size(), AssemblyException, "Attempt to begin an undeclared function.");
auto& section = m_codeSections.at(_functionID);
assertThrow(section.items.empty(), AssemblyException, "Function already defined.");
m_currentCodeSection = _functionID;
}
void endFunction()
{
assertThrow(m_currentCodeSection != 0, AssemblyException, "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 All @@ -64,6 +114,7 @@ class Assembly
AssemblyItem newPushLibraryAddress(std::string const& _identifier);
AssemblyItem newPushImmutable(std::string const& _identifier);
AssemblyItem newImmutableAssignment(std::string const& _identifier);
AssemblyItem newDataLoadN(size_t offset);

AssemblyItem const& append(AssemblyItem _i);
AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
Expand All @@ -76,12 +127,37 @@ class Assembly
void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); }
void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
void appendDataLoadN(size_t offset) { append(newDataLoadN(offset));}

void appendVerbatim(bytes _data, size_t _arguments, size_t _returnVariables)
{
append(AssemblyItem(std::move(_data), _arguments, _returnVariables));
}

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

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

AssemblyItem appendEOFCreate(uint16_t _containerId)
{
assertThrow(_containerId < m_subs.size(), AssemblyException, "EOF Create of undefined container");

return append(AssemblyItem::eofCreate(_containerId));
}

AssemblyItem appendReturnContract(uint16_t _containerId)
{
assertThrow(_containerId < m_subs.size(), AssemblyException, "Return undefined container id");

return append(AssemblyItem::returnContract(_containerId));
}

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 All @@ -97,12 +173,6 @@ class Assembly
/// Appends @a _data literally to the very end of the bytecode.
void appendToAuxiliaryData(bytes const& _data) { m_auxiliaryData += _data; }

/// Returns the assembly items.
AssemblyItems const& items() const { return m_items; }

/// Returns the mutable assembly items. Use with care!
AssemblyItems& items() { return m_items; }

int deposit() const { return m_deposit; }
void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assertThrow(m_deposit >= 0, InvalidDeposit, ""); }
void setDeposit(int _deposit) { m_deposit = _deposit; assertThrow(m_deposit >= 0, InvalidDeposit, ""); }
Expand Down Expand Up @@ -175,12 +245,30 @@ class Assembly

bool isCreation() const { return m_creation; }

struct CodeSection
{
uint8_t inputs = 0;
uint8_t outputs = 0;
AssemblyItems items{};
};

std::vector<CodeSection>& codeSections()
{
return m_codeSections;
}

std::vector<CodeSection> const& codeSections() const
{
return m_codeSections;
}

protected:
/// Does the same operations as @a optimise, but should only be applied to a sub and
/// returns the replaced tags. Also takes an argument containing the tags of this assembly
/// that are referenced in a super-assembly.
std::map<u256, u256> const& optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> _tagsReferencedFromOutside);

/// For EOF and legacy it calculates approximate size of "pure" code without data.
unsigned codeSize(unsigned subTagSize) const;

/// Add all assembly items from given JSON array. This function imports the items by iterating through
Expand Down Expand Up @@ -217,11 +305,12 @@ class Assembly
};

std::map<std::string, NamedTagInfo> m_namedTags;
AssemblyItems m_items;
std::map<util::h256, bytes> m_data;
/// Data that is appended to the very end of the contract.
bytes m_auxiliaryData;
std::vector<std::shared_ptr<Assembly>> m_subs;
std::vector<CodeSection> m_codeSections;
uint16_t m_currentCodeSection = 0;
std::map<util::h256, std::string> m_strings;
std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables.
Expand All @@ -242,6 +331,7 @@ class Assembly
int m_deposit = 0;
/// True, if the assembly contains contract creation code.
bool const m_creation = false;
std::optional<uint8_t> m_eofVersion;
/// Internal name of the assembly object, only used with the Yul backend
/// currently
std::string m_name;
Expand Down
108 changes: 106 additions & 2 deletions libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ 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, "");
assertThrow(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump, util::Exception, "");
u256 combined = u256(data());
size_t subId = static_cast<size_t>((combined >> 64) - 1);
size_t tag = static_cast<size_t>(combined & 0xffffffffffffffffULL);
Expand Down Expand Up @@ -109,13 +109,27 @@ 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, "");
assertThrow(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump , util::Exception, "");
u256 data = _tag;
if (_subId != std::numeric_limits<size_t>::max())
data |= (u256(_subId) + 1) << 64;
setData(data);
}

size_t AssemblyItem::maxStackHeightDelta() const
{
if (m_type == AssignImmutable)
{
assertThrow(m_immutableOccurrences.has_value(), util::Exception, "");
if (*m_immutableOccurrences == 0)
return 0;
else
return (*m_immutableOccurrences - 1) * 2 + 1;
}
else
return deposit();
}

size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _evmVersion, Precision _precision) const
{
switch (m_type)
Expand Down Expand Up @@ -161,6 +175,21 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _
}
case VerbatimBytecode:
return std::get<2>(*m_verbatimBytecode).size();
case JumpF:
case CallF:
return 3;
case RetF:
return 1;
case RelativeJump:
return 3;
case ConditionalRelativeJump:
return 3;
case EofCreate:
return 2;
case ReturnContract:
return 2;
case DataLoadN:
return 2;
default:
break;
}
Expand All @@ -177,6 +206,19 @@ size_t AssemblyItem::arguments() const
return std::get<0>(*m_verbatimBytecode);
else if (type() == AssignImmutable)
return 2;
else if (type() == CallF || type() == JumpF)
{
assertThrow(m_functionSignature.has_value(), AssemblyException, "");
return std::get<0>(*m_functionSignature);
}
else if (type() == RetF)
return static_cast<size_t>(data());
else if (type() == ConditionalRelativeJump)
return 1;
else if (type() == EofCreate)
return 4;
else if (type() == ReturnContract)
return 2;
else
return 0;
}
Expand All @@ -203,6 +245,17 @@ size_t AssemblyItem::returnValues() const
return 0;
case VerbatimBytecode:
return std::get<1>(*m_verbatimBytecode);
case CallF:
assertThrow(m_functionSignature.has_value(), AssemblyException, "");
return std::get<1>(*m_functionSignature);
case JumpF:
return 0;
case RetF:
case ReturnContract:
return 0;
case DataLoadN:
case EofCreate:
return 1;
default:
break;
}
Expand Down Expand Up @@ -327,6 +380,30 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
case VerbatimBytecode:
text = std::string("verbatimbytecode_") + util::toHex(std::get<2>(*m_verbatimBytecode));
break;
case RelativeJump:
text = "rjump(" + std::string("tag_") + std::to_string(static_cast<size_t>(data())) + ")";
break;
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;
case EofCreate:
text = "eofcreate(" + std::to_string(static_cast<size_t>(data())) + ")";
break;
case ReturnContract:
text = "returcontract(" + std::to_string(static_cast<size_t>(data())) + ")";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo in assembly text of RETURNCONTRACT

Suggested change
text = "returcontract(" + std::to_string(static_cast<size_t>(data())) + ")";
text = "returncontract(" + std::to_string(static_cast<size_t>(data())) + ")";

break;
case DataLoadN:
text = "dataloadn(" + std::to_string(static_cast<size_t>(data())) + ")";
break;
default:
assertThrow(false, InvalidOpcode, "");
}
Expand Down Expand Up @@ -363,6 +440,24 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons
_out << " PushTag " << subId << ":" << _item.splitForeignPushTag().second;
break;
}
case RelativeJump:
{
size_t subId = _item.splitForeignPushTag().first;
if (subId == std::numeric_limits<size_t>::max())
_out << " RelativeJump " << _item.splitForeignPushTag().second;
else
_out << " RelativeJump " << subId << ":" << _item.splitForeignPushTag().second;
break;
}
case ConditionalRelativeJump:
{
size_t subId = _item.splitForeignPushTag().first;
if (subId == std::numeric_limits<size_t>::max())
_out << " ConditionalRelativeJump " << _item.splitForeignPushTag().second;
else
_out << " ConditionalRelativeJump " << subId << ":" << _item.splitForeignPushTag().second;
break;
}
case Tag:
_out << " Tag " << _item.data();
break;
Expand Down Expand Up @@ -396,6 +491,15 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons
case VerbatimBytecode:
_out << " Verbatim " << util::toHex(_item.verbatimData());
break;
case CallF:
_out << " CALLF " << std::dec << _item.data();
break;
case JumpF:
_out << " JUMPF " << std::dec << _item.data();
break;
case RetF:
_out << " RETF";
break;
case UndefinedItem:
_out << " ???";
break;
Expand Down
Loading