Skip to content

Commit

Permalink
Merge pull request #15367 from ipsilon/eof-prep-3
Browse files Browse the repository at this point in the history
eof: Introduce EOF container format support in `Assembly::assemble` (EIP-3540)
  • Loading branch information
cameel authored Sep 20, 2024
2 parents 7d33151 + bc9f3e0 commit 33b67f0
Show file tree
Hide file tree
Showing 15 changed files with 504 additions and 159 deletions.
443 changes: 347 additions & 96 deletions libevmasm/Assembly.cpp

Large diffs are not rendered by default.

52 changes: 39 additions & 13 deletions libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,14 @@ class Assembly
{
public:
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))
{}
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; }
AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); }
Expand Down Expand Up @@ -103,12 +106,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 @@ -170,7 +167,8 @@ class Assembly
static std::pair<std::shared_ptr<Assembly>, std::vector<std::string>> fromJSON(
Json const& _json,
std::vector<std::string> const& _sourceList = {},
size_t _level = 0
size_t _level = 0,
std::optional<uint8_t> _eofVersion = std::nullopt
);

/// Mark this assembly as invalid. Calling ``assemble`` on it will throw.
Expand All @@ -181,12 +179,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 All @@ -210,6 +226,15 @@ class Assembly

std::shared_ptr<std::string const> sharedSourceName(std::string const& _name) const;

/// Returns EOF header bytecode | code section sizes offsets | data section size offset
std::tuple<bytes, std::vector<size_t>, size_t> createEOFHeader(std::set<uint16_t> const& _referencedSubIds) const;

LinkerObject const& assembleLegacy() const;
LinkerObject const& assembleEOF() const;

/// Returns map from m_subs to an index of subcontainer in the final EOF bytecode
std::map<uint16_t, uint16_t> findReferencedContainers() const;

protected:
/// 0 is reserved for exception
unsigned m_usedTags = 1;
Expand All @@ -223,11 +248,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 Down
73 changes: 38 additions & 35 deletions libevmasm/ConstantOptimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,46 +35,49 @@ unsigned ConstantOptimisationMethod::optimiseConstants(
)
{
// TODO: design the optimiser in a way this is not needed
AssemblyItems& _items = _assembly.items();

unsigned optimisations = 0;
std::map<AssemblyItem, size_t> pushes;
for (AssemblyItem const& item: _items)
if (item.type() == Push)
pushes[item]++;
std::map<u256, AssemblyItems> pendingReplacements;
for (auto it: pushes)
for (auto& codeSection: _assembly.codeSections())
{
AssemblyItem const& item = it.first;
if (item.data() < 0x100)
continue;
Params params;
params.multiplicity = it.second;
params.isCreation = _isCreation;
params.runs = _runs;
params.evmVersion = _evmVersion;
LiteralMethod lit(params, item.data());
bigint literalGas = lit.gasNeeded();
CodeCopyMethod copy(params, item.data());
bigint copyGas = copy.gasNeeded();
ComputeMethod compute(params, item.data());
bigint computeGas = compute.gasNeeded();
AssemblyItems replacement;
if (copyGas < literalGas && copyGas < computeGas)
{
replacement = copy.execute(_assembly);
optimisations++;
}
else if (computeGas < literalGas && computeGas <= copyGas)
AssemblyItems& _items = codeSection.items;

std::map<AssemblyItem, size_t> pushes;
for (AssemblyItem const& item: _items)
if (item.type() == Push)
pushes[item]++;
std::map<u256, AssemblyItems> pendingReplacements;
for (auto it: pushes)
{
replacement = compute.execute(_assembly);
optimisations++;
AssemblyItem const& item = it.first;
if (item.data() < 0x100)
continue;
Params params;
params.multiplicity = it.second;
params.isCreation = _isCreation;
params.runs = _runs;
params.evmVersion = _evmVersion;
LiteralMethod lit(params, item.data());
bigint literalGas = lit.gasNeeded();
CodeCopyMethod copy(params, item.data());
bigint copyGas = copy.gasNeeded();
ComputeMethod compute(params, item.data());
bigint computeGas = compute.gasNeeded();
AssemblyItems replacement;
if (copyGas < literalGas && copyGas < computeGas)
{
replacement = copy.execute(_assembly);
optimisations++;
}
else if (computeGas < literalGas && computeGas <= copyGas)
{
replacement = compute.execute(_assembly);
optimisations++;
}
if (!replacement.empty())
pendingReplacements[item.data()] = replacement;
}
if (!replacement.empty())
pendingReplacements[item.data()] = replacement;
if (!pendingReplacements.empty())
replaceConstants(_items, pendingReplacements);
}
if (!pendingReplacements.empty())
replaceConstants(_items, pendingReplacements);
return optimisations;
}

Expand Down
10 changes: 7 additions & 3 deletions libevmasm/EVMAssemblyStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void EVMAssemblyStack::analyze(std::string const& _sourceName, Json const& _asse
{
solAssert(!m_evmAssembly);
m_name = _sourceName;
std::tie(m_evmAssembly, m_sourceList) = evmasm::Assembly::fromJSON(_assemblyJson);
std::tie(m_evmAssembly, m_sourceList) = evmasm::Assembly::fromJSON(_assemblyJson, {}, 0, m_eofVersion);
solRequire(m_evmAssembly != nullptr, AssemblyImportException, "Could not create evm assembly object.");
}

Expand All @@ -56,12 +56,16 @@ void EVMAssemblyStack::assemble()
solAssert(!m_evmRuntimeAssembly);

m_object = m_evmAssembly->assemble();
m_sourceMapping = AssemblyItem::computeSourceMapping(m_evmAssembly->items(), sourceIndices());
// TODO: Check for EOF
solAssert(m_evmAssembly->codeSections().size() == 1);
m_sourceMapping = AssemblyItem::computeSourceMapping(m_evmAssembly->codeSections().front().items, sourceIndices());
if (m_evmAssembly->numSubs() > 0)
{
m_evmRuntimeAssembly = std::make_shared<evmasm::Assembly>(m_evmAssembly->sub(0));
solAssert(m_evmRuntimeAssembly && !m_evmRuntimeAssembly->isCreation());
m_runtimeSourceMapping = AssemblyItem::computeSourceMapping(m_evmRuntimeAssembly->items(), sourceIndices());
// TODO: Check for EOF
solAssert(m_evmRuntimeAssembly->codeSections().size() == 1);
m_runtimeSourceMapping = AssemblyItem::computeSourceMapping(m_evmRuntimeAssembly->codeSections().front().items, sourceIndices());
m_runtimeObject = m_evmRuntimeAssembly->assemble();
}
}
Expand Down
4 changes: 3 additions & 1 deletion libevmasm/EVMAssemblyStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ namespace solidity::evmasm
class EVMAssemblyStack: public AbstractAssemblyStack
{
public:
explicit EVMAssemblyStack(langutil::EVMVersion _evmVersion): m_evmVersion(_evmVersion) {}
explicit EVMAssemblyStack(langutil::EVMVersion _evmVersion, std::optional<uint8_t> _eofVersion):
m_evmVersion(_evmVersion), m_eofVersion(_eofVersion) {}

/// Runs parsing and analysis steps.
/// Multiple calls overwrite the previous state.
Expand Down Expand Up @@ -76,6 +77,7 @@ class EVMAssemblyStack: public AbstractAssemblyStack

private:
langutil::EVMVersion m_evmVersion;
std::optional<uint8_t> m_eofVersion;
std::string m_name;
std::shared_ptr<evmasm::Assembly> m_evmAssembly;
std::shared_ptr<evmasm::Assembly> m_evmRuntimeAssembly;
Expand Down
13 changes: 11 additions & 2 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -808,15 +808,24 @@ evmasm::AssemblyItems const* CompilerStack::assemblyItems(std::string const& _co
solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful.");

Contract const& currentContract = contract(_contractName);
return currentContract.evmAssembly ? &currentContract.evmAssembly->items() : nullptr;
if (!currentContract.evmAssembly)
return nullptr;
solUnimplementedAssert(!m_eofVersion.has_value(), "EVM assembly output not implemented for EOF yet.");
solAssert(currentContract.evmAssembly->codeSections().size() == 1, "Expected a single code section in legacy codegen.");
return &currentContract.evmAssembly->codeSections().front().items;
}

evmasm::AssemblyItems const* CompilerStack::runtimeAssemblyItems(std::string const& _contractName) const
{
solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful.");

Contract const& currentContract = contract(_contractName);
return currentContract.evmRuntimeAssembly ? &currentContract.evmRuntimeAssembly->items() : nullptr;

if (!currentContract.evmRuntimeAssembly)
return nullptr;
solUnimplementedAssert(!m_eofVersion.has_value(), "EVM assembly output not implemented for EOF yet.");
solAssert(currentContract.evmRuntimeAssembly->codeSections().size() == 1, "Expected a single code section in legacy codegen.");
return &currentContract.evmRuntimeAssembly->codeSections().front().items;
}

Json CompilerStack::generatedSources(std::string const& _contractName, bool _runtime) const
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,7 @@ Json StandardCompiler::importEVMAssembly(StandardCompiler::InputsAndSettings _in
if (!isBinaryRequested(_inputsAndSettings.outputSelection))
return Json::object();

evmasm::EVMAssemblyStack stack(_inputsAndSettings.evmVersion);
evmasm::EVMAssemblyStack stack(_inputsAndSettings.evmVersion, _inputsAndSettings.eofVersion);
std::string const& sourceName = _inputsAndSettings.jsonSources.begin()->first; // result of structured binding can only be used within lambda from C++20 on.
Json const& sourceJson = _inputsAndSettings.jsonSources.begin()->second;
try
Expand Down
10 changes: 7 additions & 3 deletions libyul/YulStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,12 @@ YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName)
creationObject.bytecode = std::make_shared<evmasm::LinkerObject>(creationAssembly->assemble());
yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables.");
creationObject.assembly = creationAssembly;
solUnimplementedAssert(!m_eofVersion.has_value(), "EVM assembly output not implemented for EOF yet.");
solAssert(creationAssembly->codeSections().size() == 1);
creationObject.sourceMappings = std::make_unique<std::string>(
// TODO: fix for EOF
evmasm::AssemblyItem::computeSourceMapping(
creationAssembly->items(),
creationAssembly->codeSections().front().items,
{{m_charStream->name(), 0}}
)
);
Expand All @@ -273,11 +276,12 @@ YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName)
{
deployedObject.bytecode = std::make_shared<evmasm::LinkerObject>(deployedAssembly->assemble());
deployedObject.assembly = deployedAssembly;
solAssert(deployedAssembly->codeSections().size() == 1);
deployedObject.sourceMappings = std::make_unique<std::string>(
evmasm::AssemblyItem::computeSourceMapping(
deployedAssembly->items(),
deployedAssembly->codeSections().front().items,
{{m_charStream->name(), 0}}
)
)
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion solc/CommandLineInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,7 @@ void CommandLineInterface::assembleFromEVMAssemblyJSON()
solAssert(m_fileReader.sourceUnits().size() == 1);
auto&& [sourceUnitName, source] = *m_fileReader.sourceUnits().begin();

auto evmAssemblyStack = std::make_unique<evmasm::EVMAssemblyStack>(m_options.output.evmVersion);
auto evmAssemblyStack = std::make_unique<evmasm::EVMAssemblyStack>(m_options.output.evmVersion, m_options.output.eofVersion);
try
{
evmAssemblyStack->parseAndAnalyze(sourceUnitName, source);
Expand Down
1 change: 1 addition & 0 deletions test/cmdlineTests/strict_asm_eof_container_prague/args
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--strict-assembly --experimental-eof-version 1 --evm-version prague --bin
36 changes: 36 additions & 0 deletions test/cmdlineTests/strict_asm_eof_container_prague/input.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
object "object" {
code {
revert(0, 0)
}

object "sub0" {
code {
mstore(0, 0)
revert(0, 32)
}
}
object "sub1" {
code {
mstore(0, 1)
revert(0, 32)
}
}
object "sub2" {
code {
mstore(0, 2)
revert(0, 32)
}
object "sub20" {
code {
mstore(0, 0x20)
revert(0, 32)
}
}
}
object "sub3" {
code {
mstore(0, 3)
revert(0, 32)
}
}
}
5 changes: 5 additions & 0 deletions test/cmdlineTests/strict_asm_eof_container_prague/output
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

======= strict_asm_eof_container_prague/input.yul (EVM) =======

Binary representation:
ef00010100040200010003030004001a001b003b001b040000000080ffff5f80fdef00010100040200010007040000000080ffff5f805260205ffdef00010100040200010008040000000080ffff60015f5260205ffdef00010100040200010008030001001b040000000080ffff60025f5260205ffdef00010100040200010008040000000080ffff60205f5260205ffdef00010100040200010008040000000080ffff60035f5260205ffd
3 changes: 2 additions & 1 deletion test/libevmasm/Assembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps)

checkCompilation(assembly);

std::string const sourceMappings = AssemblyItem::computeSourceMapping(assembly.items(), indices);
BOOST_REQUIRE(assembly.codeSections().size() == 1);
std::string const sourceMappings = AssemblyItem::computeSourceMapping(assembly.codeSections().at(0).items, indices);
auto const numberOfMappings = std::count(sourceMappings.begin(), sourceMappings.end(), ';');

LinkerObject const& obj = assembly.assemble();
Expand Down
6 changes: 4 additions & 2 deletions test/libevmasm/Optimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1381,16 +1381,18 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies)
t1.toSubAssemblyTag(subId).pushTag(),
u256(8)
};
BOOST_REQUIRE(main.codeSections().size() == 1);
BOOST_CHECK_EQUAL_COLLECTIONS(
main.items().begin(), main.items().end(),
main.codeSections().at(0).items.begin(),main.codeSections().at(0).items.end(),
expectationMain.begin(), expectationMain.end()
);

AssemblyItems expectationSub{
u256(1), t1.tag(), u256(2), Instruction::JUMP, t4.tag(), u256(7), t4.pushTag(), Instruction::JUMP
};
BOOST_REQUIRE(sub->codeSections().size() == 1);
BOOST_CHECK_EQUAL_COLLECTIONS(
sub->items().begin(), sub->items().end(),
sub->codeSections().at(0).items.begin(), sub->codeSections().at(0).items.end(),
expectationSub.begin(), expectationSub.end()
);
}
Expand Down
3 changes: 2 additions & 1 deletion test/libsolidity/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ evmasm::AssemblyItems compileContract(std::shared_ptr<CharStream> _sourceCode)
);
compiler.compileContract(*contract, std::map<ContractDefinition const*, std::shared_ptr<Compiler const>>{}, bytes());

return compiler.runtimeAssembly().items();
BOOST_REQUIRE(compiler.runtimeAssembly().codeSections().size() == 1);
return compiler.runtimeAssembly().codeSections().at(0).items;
}
BOOST_FAIL("No contract found in source.");
return AssemblyItems();
Expand Down

0 comments on commit 33b67f0

Please sign in to comment.