From 41c9ff8985a8af8fdf4995645f0d291b8dfb0bc3 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 14 Dec 2022 12:06:27 +0100 Subject: [PATCH] Initial still broken version. --- libevmasm/Assembly.cpp | 113 +++++++++++++----- libevmasm/Assembly.h | 62 +++++++++- libevmasm/AssemblyItem.cpp | 30 +++++ libevmasm/AssemblyItem.h | 15 ++- libevmasm/Instruction.h | 4 + libevmasm/SemanticInformation.cpp | 2 + libsolidity/codegen/CompilerContext.cpp | 1 + libyul/CompilabilityChecker.cpp | 3 +- libyul/CompilabilityChecker.h | 7 +- libyul/YulStack.cpp | 1 + libyul/backends/evm/AbstractAssembly.h | 10 ++ libyul/backends/evm/ControlFlowGraph.h | 3 +- .../backends/evm/ControlFlowGraphBuilder.cpp | 5 +- libyul/backends/evm/ControlFlowGraphBuilder.h | 8 +- libyul/backends/evm/EVMDialect.h | 3 + libyul/backends/evm/EVMObjectCompiler.cpp | 1 + libyul/backends/evm/EthAssemblyAdapter.cpp | 30 +++++ libyul/backends/evm/EthAssemblyAdapter.h | 6 + libyul/backends/evm/NoOutputAssembly.cpp | 27 +++++ libyul/backends/evm/NoOutputAssembly.h | 20 +++- .../evm/OptimizedEVMCodeTransform.cpp | 61 +++++++--- .../backends/evm/OptimizedEVMCodeTransform.h | 1 + libyul/backends/evm/StackLayoutGenerator.cpp | 8 +- libyul/backends/evm/StackLayoutGenerator.h | 1 + libyul/backends/wasm/EVMToEwasmTranslator.cpp | 1 + libyul/optimiser/OptimiserStep.h | 1 + libyul/optimiser/StackCompressor.cpp | 5 +- libyul/optimiser/StackCompressor.h | 1 + libyul/optimiser/StackLimitEvader.cpp | 3 +- libyul/optimiser/Suite.cpp | 5 +- libyul/optimiser/Suite.h | 1 + test/libyul/CompilabilityChecker.cpp | 7 +- test/libyul/ControlFlowGraphTest.cpp | 2 +- test/libyul/KnowledgeBaseTest.cpp | 2 +- test/libyul/StackLayoutGeneratorTest.cpp | 2 +- test/libyul/YulOptimizerTestCommon.cpp | 5 +- .../EVMInstructionInterpreter.cpp | 3 + test/tools/yulopti.cpp | 3 +- tools/yulPhaser/Program.cpp | 1 + 39 files changed, 398 insertions(+), 66 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index a9fda6c10..460881581 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -52,11 +52,11 @@ AssemblyItem const& Assembly::append(AssemblyItem _i) { assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); m_deposit += static_cast(_i.deposit()); - m_items.emplace_back(std::move(_i)); - if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid()) - m_items.back().setLocation(m_currentSourceLocation); - m_items.back().m_modifierDepth = m_currentModifierDepth; - return m_items.back(); + items().emplace_back(std::move(_i)); + if (!items().back().location().isValid() && m_currentSourceLocation.isValid()) + items().back().setLocation(m_currentSourceLocation); + items().back().m_modifierDepth = m_currentModifierDepth; + return items().back(); } unsigned Assembly::codeSize(unsigned subTagSize) const @@ -67,7 +67,7 @@ unsigned Assembly::codeSize(unsigned subTagSize) const for (auto const& i: m_data) ret += i.second.size(); - for (AssemblyItem const& i: m_items) + for (AssemblyItem const& i: items()) ret += i.bytesRequired(tagSize, Precision::Approximate); if (numberEncodingSize(ret) <= tagSize) return static_cast(ret); @@ -189,7 +189,7 @@ void Assembly::assemblyStream( { Functionalizer f(_out, _prefix, _sourceCodes, *this); - for (auto const& i: m_items) + for (auto const& i: items()) f.feed(i, _debugInfoSelection); f.flush(); @@ -227,7 +227,7 @@ Json::Value Assembly::assemblyJSON(map const& _sourceIndices, Json::Value root; root[".code"] = Json::arrayValue; Json::Value& code = root[".code"]; - for (AssemblyItem const& item: m_items) + for (AssemblyItem const& item: items()) { int sourceIndex = -1; if (item.location().sourceName) @@ -344,6 +344,10 @@ map const& Assembly::optimiseInternal( std::set _tagsReferencedFromOutside ) { + if (m_eofVersion.has_value()) + // TODO + return *m_tagReplacements; + if (m_tagReplacements) return *m_tagReplacements; @@ -354,10 +358,10 @@ map const& Assembly::optimiseInternal( Assembly& sub = *m_subs[subId]; map const& subTagReplacements = sub.optimiseInternal( settings, - JumpdestRemover::referencedTags(m_items, subId) + JumpdestRemover::referencedTags(items(), subId) ); // Apply the replacements (can be empty). - BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); + BlockDeduplicator::applyTagReplacement(items(), subTagReplacements, subId); } map tagReplacements; @@ -368,7 +372,7 @@ map const& Assembly::optimiseInternal( if (_settings.runInliner) Inliner{ - m_items, + items(), _tagsReferencedFromOutside, _settings.expectedExecutionsPerDeployment, isCreation(), @@ -377,14 +381,14 @@ map const& Assembly::optimiseInternal( if (_settings.runJumpdestRemover) { - JumpdestRemover jumpdestOpt{m_items}; + JumpdestRemover jumpdestOpt{items()}; if (jumpdestOpt.optimise(_tagsReferencedFromOutside)) count++; } if (_settings.runPeephole) { - PeepholeOptimiser peepOpt{m_items}; + PeepholeOptimiser peepOpt{items()}; while (peepOpt.optimise()) { count++; @@ -395,7 +399,7 @@ map const& Assembly::optimiseInternal( // This only modifies PushTags, we have to run again to actually remove code. if (_settings.runDeduplicate) { - BlockDeduplicator deduplicator{m_items}; + BlockDeduplicator deduplicator{items()}; if (deduplicator.deduplicate()) { for (auto const& replacement: deduplicator.replacedTags()) @@ -425,17 +429,17 @@ map const& Assembly::optimiseInternal( // function types that can be stored in storage. AssemblyItems optimisedItems; - bool usesMSize = ranges::any_of(m_items, [](AssemblyItem const& _i) { + bool usesMSize = ranges::any_of(items(), [](AssemblyItem const& _i) { return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode; }); - auto iter = m_items.begin(); - while (iter != m_items.end()) + auto iter = items().begin(); + while (iter != items().end()) { KnownState emptyState; CommonSubexpressionEliminator eliminator{emptyState}; auto orig = iter; - iter = eliminator.feedItems(iter, m_items.end(), usesMSize); + iter = eliminator.feedItems(iter, items().end(), usesMSize); bool shouldReplace = false; AssemblyItems optimisedChunk; try @@ -462,9 +466,9 @@ map const& Assembly::optimiseInternal( else copy(orig, iter, back_inserter(optimisedItems)); } - if (optimisedItems.size() < m_items.size()) + if (optimisedItems.size() < items().size()) { - m_items = std::move(optimisedItems); + items() = std::move(optimisedItems); count++; } } @@ -493,6 +497,8 @@ LinkerObject const& Assembly::assemble() const LinkerObject& ret = m_assembledObject; + bool const needsEOFContainer = m_eofVersion.has_value(); + size_t subTagSize = 1; map>> immutableReferencesBySub; for (auto const& sub: m_subs) @@ -515,7 +521,7 @@ LinkerObject const& Assembly::assemble() const bool setsImmutables = false; bool pushesImmutables = false; - for (auto const& i: m_items) + for (auto const& i: items()) if (i.type() == AssignImmutable) { i.setImmutableOccurrences(immutableReferencesBySub[i.data()].second.size()); @@ -539,6 +545,7 @@ LinkerObject const& Assembly::assemble() const unsigned bytesPerTag = numberEncodingSize(bytesRequiredForCode); uint8_t tagPush = static_cast(pushInstruction(bytesPerTag)); + // TODO: all of this is a bit off unsigned bytesRequiredIncludingData = (m_eofVersion.has_value() ? 10 : 0) + bytesRequiredForCode + 1 + static_cast(m_auxiliaryData.size()); for (auto const& sub: m_subs) bytesRequiredIncludingData += static_cast(sub->assemble().bytecode.size()); @@ -549,27 +556,55 @@ LinkerObject const& Assembly::assemble() const // Insert EOF1 header. bytesRef eofCodeLength(&ret.bytecode.back(), 0); + vector eofFunctionLengths; bytesRef eofDataLength(&ret.bytecode.back(), 0); - if (m_eofVersion.has_value()) + if (needsEOFContainer) { // TODO: empty data is disallowed ret.bytecode.push_back(0xef); ret.bytecode.push_back(0x00); ret.bytecode.push_back(0x01); // version 1 + if (!m_functions.empty()) + { + ret.bytecode.push_back(0x03); // kind=type + ret.bytecode.push_back(0x00); // length of type section + ret.bytecode.push_back(0x00); + bytesRef length(&ret.bytecode.back() + 1 - 2, 2); + toBigEndian((m_functions.size() + 1) * 2, length); + } ret.bytecode.push_back(0x01); // kind=code ret.bytecode.push_back(0x00); // length of code ret.bytecode.push_back(0x00); eofCodeLength = bytesRef(&ret.bytecode.back() + 1 - 2, 2); + for (size_t i = 0; i < m_functions.size(); ++i) + { + ret.bytecode.push_back(0x01); // kind=code + ret.bytecode.push_back(0x00); // length of code + ret.bytecode.push_back(0x00); + eofFunctionLengths.emplace_back(&ret.bytecode.back() + 1 - 2, 2); + } ret.bytecode.push_back(0x02); // kind=data ret.bytecode.push_back(0x00); // length of data ret.bytecode.push_back(0x00); eofDataLength = bytesRef(&ret.bytecode.back() + 1 - 2, 2); ret.bytecode.push_back(0x00); // terminator + if (!m_functions.empty()) + { + ret.bytecode.push_back(0); + ret.bytecode.push_back(0); + for (auto const& [args, rets, functionAssembly]: m_functions) + { + (void)functionAssembly; + ret.bytecode.push_back(args); + ret.bytecode.push_back(rets); + } + } } auto const codeStart = ret.bytecode.size(); - for (AssemblyItem const& i: m_items) + auto assembleItems = [&](AssemblyItems const& _items) { + for (AssemblyItem const& i: _items) { // store position of the invalid jump destination if (i.type() != Tag && m_tagPositionsInBytecode[0] == numeric_limits::max()) @@ -684,10 +719,25 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.push_back(static_cast(Instruction::JUMPDEST)); break; } + case CallF: + { + ret.bytecode.push_back(static_cast(Instruction::CALLF)); + ret.bytecode.resize(ret.bytecode.size() + 2); + bytesRef byr(&ret.bytecode.back() + 1 - 2, 2); + toBigEndian(i.data(), byr); + break; + } + case RetF: + { + ret.bytecode.push_back(static_cast(Instruction::RETF)); + break; + } default: assertThrow(false, InvalidOpcode, "Unexpected opcode while assembling."); } } + }; + assembleItems(items()); if (!immutableReferencesBySub.empty()) throw @@ -697,17 +747,26 @@ LinkerObject const& Assembly::assemble() const "Some immutables were read from but never assigned, possibly because of optimization." ); - if (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty()) + if (!needsEOFContainer && (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty())) // Append an INVALID here to help tests find miscompilation. ret.bytecode.push_back(static_cast(Instruction::INVALID)); auto const codeLength = ret.bytecode.size() - codeStart; - if (m_eofVersion.has_value()) + if (needsEOFContainer) { assertThrow(codeLength > 0 && codeLength <= 0xffff, AssemblyException, "Invalid code section size."); toBigEndian(codeLength, eofCodeLength); } + + for (size_t i = 0; i < m_functions.size(); ++i) + { + size_t start = ret.bytecode.size(); + assembleItems(std::get<2>(m_functions[i])); + size_t size = ret.bytecode.size() - start; + toBigEndian(size, eofFunctionLengths.at(i)); + } + auto const dataStart = ret.bytecode.size(); for (auto const& [subIdPath, bytecodeOffset]: subRef) @@ -738,7 +797,7 @@ LinkerObject const& Assembly::assemble() const { size_t position = m_tagPositionsInBytecode.at(tagInfo.id); optional tagIndex; - for (auto&& [index, item]: m_items | ranges::views::enumerate) + for (auto&& [index, item]: items() | ranges::views::enumerate) if (item.type() == Tag && static_cast(item.data()) == tagInfo.id) { tagIndex = index; @@ -775,7 +834,7 @@ LinkerObject const& Assembly::assemble() const } auto dataLength = ret.bytecode.size() - dataStart; - if (m_eofVersion.has_value()) + if (needsEOFContainer) { // Note: Temporary solution to current evmone requirement of non-empty data section. if (dataLength == 0) diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 1da4ac541..c6b205db3 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -51,13 +51,46 @@ class Assembly public: Assembly(bool _creation, std::optional _eofVersion, std::string _name): m_creation(_creation), m_eofVersion(_eofVersion), m_name(std::move(_name)) { } + std::optional eofVersion() const { return m_eofVersion; } + bool supportsFunctions() 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) + { + auto&& [args, rets, functionItems] = m_functions.at(_functionID); + (void)functionItems; + return AssemblyItem::functionCall(_functionID, args, rets); + } + AssemblyItem newFunctionReturn() + { + assertThrow(m_currentFunctionID.has_value(), AssemblyException, ""); + return AssemblyItem::functionReturn(std::get<1>(m_functions.at(*m_currentFunctionID))); + } /// 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 _sourceID); AssemblyItem newData(bytes const& _data) { util::h256 h(util::keccak256(util::asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } bytes const& data(util::h256 const& _i) const { return m_data.at(_i); } AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } + uint16_t createFunction(uint8_t _args, uint8_t _rets) + { + size_t functionID = m_functions.size(); + assertThrow(functionID <= 0xFFFF, AssemblyException, "Too many functions"); + assertThrow(!m_currentFunctionID.has_value(), AssemblyException, "Nested createFunction"); + m_functions.emplace_back(_args, _rets, AssemblyItems{}); + return static_cast(functionID); + } + void beginFunction(uint16_t _functionID) + { + auto& function = m_functions.at(_functionID); + assertThrow(!m_currentFunctionID.has_value(), AssemblyException, "Nested beginFunction"); + assertThrow(std::get<2>(function).empty(), AssemblyException, "Function already defined."); + m_currentFunctionID = _functionID; + } + void endFunction() + { + assertThrow(m_currentFunctionID.has_value(), AssemblyException, ""); + m_currentFunctionID.reset(); + } Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); } Assembly& sub(size_t _sub) { return *m_subs.at(_sub); } size_t numSubs() const { return m_subs.size(); } @@ -83,6 +116,15 @@ public: append(AssemblyItem(std::move(_data), _arguments, _returnVariables)); } + 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; } @@ -99,10 +141,22 @@ public: void appendToAuxiliaryData(bytes const& _data) { m_auxiliaryData += _data; } /// Returns the assembly items. - AssemblyItems const& items() const { return m_items; } + AssemblyItems const& items() const + { + if (m_currentFunctionID.has_value()) + return std::get<2>(m_functions.at(*m_currentFunctionID)); + else + return m_mainItems; + } /// Returns the mutable assembly items. Use with care! - AssemblyItems& items() { return m_items; } + AssemblyItems& items() + { + if (m_currentFunctionID.has_value()) + return std::get<2>(m_functions.at(*m_currentFunctionID)); + else + return m_mainItems; + } int deposit() const { return m_deposit; } void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assertThrow(m_deposit >= 0, InvalidDeposit, ""); } @@ -189,11 +243,12 @@ protected: }; std::map m_namedTags; - AssemblyItems m_items; + AssemblyItems m_mainItems; std::map m_data; /// Data that is appended to the very end of the contract. bytes m_auxiliaryData; std::vector> m_subs; + std::vector> m_functions; std::map m_strings; std::map m_libraries; ///< Identifiers of libraries to be linked. std::map m_immutables; ///< Identifiers of immutables. @@ -213,6 +268,7 @@ protected: /// True, if the assembly contains contract creation code. bool const m_creation = false; std::optional m_eofVersion; + std::optional m_currentFunctionID; /// Internal name of the assembly object, only used with the Yul backend /// currently std::string m_name; diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index a36e0ddb1..71dffc74c 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -159,6 +159,10 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, Precision _precision) } case VerbatimBytecode: return std::get<2>(*m_verbatimBytecode).size(); + case CallF: + return 3; + case RetF: + return 1; default: break; } @@ -173,6 +177,15 @@ size_t AssemblyItem::arguments() const return get<0>(*m_verbatimBytecode); else if (type() == AssignImmutable) return 2; + else if (type() == CallF) + { + assertThrow(m_functionSignature.has_value(), AssemblyException, ""); + return std::get<0>(*m_functionSignature); + } + else if (type() == RetF) + { + return static_cast(data()); + } else return 0; } @@ -197,6 +210,11 @@ size_t AssemblyItem::returnValues() const return 0; case VerbatimBytecode: return get<1>(*m_verbatimBytecode); + case CallF: + assertThrow(m_functionSignature.has_value(), AssemblyException, ""); + return std::get<1>(*m_functionSignature); + case RetF: + return 0; default: break; } @@ -309,6 +327,12 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const case VerbatimBytecode: text = string("verbatimbytecode_") + util::toHex(get<2>(*m_verbatimBytecode)); break; + case CallF: + text = "callf(" + to_string(static_cast(data())) + ")"; + break; + case RetF: + text = "retf"; + break; default: assertThrow(false, InvalidOpcode, ""); } @@ -335,6 +359,12 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item) case Push: _out << " PUSH " << hex << _item.data() << dec; break; + case CallF: + _out << " CALLF " << dec << _item.data(); + break; + case RetF: + _out << " RETF"; + break; case PushTag: { size_t subId = _item.splitForeignPushTag().first; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 4aef82d8b..a13b68566 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -50,7 +50,9 @@ enum AssemblyItemType PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer. PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor. AssignImmutable, ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code. - 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. + CallF, + RetF }; enum class Precision { Precise , Approximate }; @@ -85,6 +87,16 @@ public: m_instruction{}, m_verbatimBytecode{{_arguments, _returnVariables, std::move(_verbatimData)}} {} + static AssemblyItem functionCall(uint16_t _functionID, uint8_t _args, uint8_t _rets, langutil::SourceLocation _location = langutil::SourceLocation()) + { + AssemblyItem result(CallF, _functionID, _location); + result.m_functionSignature = std::make_tuple(_args, _rets); + return result; + } + static AssemblyItem functionReturn(uint8_t _rets, langutil::SourceLocation _location = langutil::SourceLocation()) + { + return AssemblyItem(RetF, _rets, _location); + } AssemblyItem(AssemblyItem const&) = default; AssemblyItem(AssemblyItem&&) = default; @@ -191,6 +203,7 @@ private: AssemblyItemType m_type; Instruction m_instruction; ///< Only valid if m_type == Operation std::shared_ptr m_data; ///< Only valid if m_type != Operation + std::optional> m_functionSignature; /// If m_type == VerbatimBytecode, this holds number of arguments, number of /// return variables and verbatim bytecode. std::optional> m_verbatimBytecode; diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 5d4432ca5..e1c97ff79 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -175,6 +175,10 @@ enum class Instruction: uint8_t LOG3, ///< Makes a log entry; 3 topics. LOG4, ///< Makes a log entry; 4 topics. + CALLF = 0xb0, + RETF, + JUMPF, + CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index acbcaa5c0..eeb93c182 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -160,6 +160,8 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool case PushDeployTimeAddress: case AssignImmutable: case VerbatimBytecode: + case CallF: + case RetF: return true; case Push: case PushTag: diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index bc69f82c5..dc3659dae 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -543,6 +543,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _ yul::GasMeter meter(_dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment); yul::OptimiserSuite::run( _dialect, + assembly().eofVersion(), &meter, _object, _optimiserSettings.optimizeStackAllocation, diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index 270773cbc..4a971689c 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -33,6 +33,7 @@ using namespace solidity::util; CompilabilityChecker::CompilabilityChecker( Dialect const& _dialect, + std::optional _eofVersion, Object const& _object, bool _optimizeStackAllocation ) @@ -50,7 +51,7 @@ CompilabilityChecker::CompilabilityChecker( builtinContext.subIDs[_object.name] = 1; for (auto const& subNode: _object.subObjects) builtinContext.subIDs[subNode->name] = 1; - NoOutputAssembly assembly; + NoOutputAssembly assembly(_eofVersion.has_value()); CodeTransform transform( assembly, analysisInfo, diff --git a/libyul/CompilabilityChecker.h b/libyul/CompilabilityChecker.h index 307d719e4..e9da7dd2f 100644 --- a/libyul/CompilabilityChecker.h +++ b/libyul/CompilabilityChecker.h @@ -44,7 +44,12 @@ namespace solidity::yul */ struct CompilabilityChecker { - CompilabilityChecker(Dialect const& _dialect, Object const& _object, bool _optimizeStackAllocation); + CompilabilityChecker( + Dialect const& _dialect, + std::optional _eofVersion, + Object const& _object, + bool _optimizeStackAllocation + ); std::map> unreachableVariables; std::map stackDeficit; }; diff --git a/libyul/YulStack.cpp b/libyul/YulStack.cpp index 8d3df8b9b..9b80cbf57 100644 --- a/libyul/YulStack.cpp +++ b/libyul/YulStack.cpp @@ -184,6 +184,7 @@ void YulStack::optimize(Object& _object, bool _isCreation) meter = make_unique(*evmDialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); OptimiserSuite::run( dialect, + m_eofVersion, meter.get(), _object, m_optimiserSettings.optimizeStackAllocation, diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 5e1b0272c..f6acc6b76 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -55,10 +55,12 @@ class AbstractAssembly public: using LabelID = size_t; using SubID = size_t; + using FunctionID = uint16_t; enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; virtual ~AbstractAssembly() = default; + virtual bool supportsFunctions() const = 0; /// Set a new source location valid starting from the next instruction. virtual void setSourceLocation(langutil::SourceLocation const& _location) = 0; /// Retrieve the current height of the stack. This does not have to be zero @@ -99,6 +101,14 @@ public: virtual void appendAssemblySize() = 0; /// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset. virtual std::pair, SubID> createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name = "") = 0; + + virtual FunctionID createFunction(uint8_t _args, uint8_t _rets) = 0; + virtual void beginFunction(FunctionID _functionID) = 0; + virtual void endFunction() = 0; + + virtual void appendFunctionCall(FunctionID _functionID) = 0; + virtual void appendFunctionReturn() = 0; + /// Appends the offset of the given sub-assembly or data. virtual void appendDataOffset(std::vector const& _subPath) = 0; /// Appends the size of the given sub-assembly or data. diff --git a/libyul/backends/evm/ControlFlowGraph.h b/libyul/backends/evm/ControlFlowGraph.h index 853a9e705..6426bbdae 100644 --- a/libyul/backends/evm/ControlFlowGraph.h +++ b/libyul/backends/evm/ControlFlowGraph.h @@ -123,7 +123,7 @@ inline bool canBeFreelyGenerated(StackSlot const& _slot) /// Control flow graph consisting of ``CFG::BasicBlock``s connected by control flow. struct CFG { - explicit CFG() {} + explicit CFG(bool _useFunctions): useFunctions(_useFunctions) {} CFG(CFG const&) = delete; CFG(CFG&&) = delete; CFG& operator=(CFG const&) = delete; @@ -220,6 +220,7 @@ struct CFG bool canContinue = true; }; + bool useFunctions = false; /// The main entry point, i.e. the start of the outermost Yul block. BasicBlock* entry = nullptr; /// Subgraphs for functions. diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.cpp b/libyul/backends/evm/ControlFlowGraphBuilder.cpp index d08dcf16c..f8f19e024 100644 --- a/libyul/backends/evm/ControlFlowGraphBuilder.cpp +++ b/libyul/backends/evm/ControlFlowGraphBuilder.cpp @@ -209,10 +209,11 @@ void markNeedsCleanStack(CFG& _cfg) std::unique_ptr ControlFlowGraphBuilder::build( AsmAnalysisInfo const& _analysisInfo, Dialect const& _dialect, + std::optional _eofVersion, Block const& _block ) { - auto result = std::make_unique(); + auto result = std::make_unique(_eofVersion.has_value()); result->entry = &result->makeBlock(debugDataOf(_block)); ControlFlowSideEffectsCollector sideEffects(_dialect, _block); @@ -541,7 +542,7 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal Scope::Function const& function = lookupFunction(_call.functionName.name); canContinue = m_graph.functionInfo.at(&function).canContinue; Stack inputs; - if (canContinue) + if (!m_graph.useFunctions && canContinue) inputs.emplace_back(FunctionCallReturnLabelSlot{_call}); for (auto const& arg: _call.arguments | ranges::views::reverse) inputs.emplace_back(std::visit(*this, arg)); diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.h b/libyul/backends/evm/ControlFlowGraphBuilder.h index ffe935b0d..6905ab40b 100644 --- a/libyul/backends/evm/ControlFlowGraphBuilder.h +++ b/libyul/backends/evm/ControlFlowGraphBuilder.h @@ -31,7 +31,12 @@ class ControlFlowGraphBuilder public: ControlFlowGraphBuilder(ControlFlowGraphBuilder const&) = delete; ControlFlowGraphBuilder& operator=(ControlFlowGraphBuilder const&) = delete; - static std::unique_ptr build(AsmAnalysisInfo const& _analysisInfo, Dialect const& _dialect, Block const& _block); + static std::unique_ptr build( + AsmAnalysisInfo const& _analysisInfo, + Dialect const& _dialect, + std::optional _eofVersion, + Block const& _block + ); StackSlot operator()(Expression const& _literal); StackSlot operator()(Literal const& _literal); @@ -81,6 +86,7 @@ private: AsmAnalysisInfo const& m_info; std::map const& m_functionSideEffects; Dialect const& m_dialect; + std::optional m_eofVersion; CFG::BasicBlock* m_currentBlock = nullptr; Scope* m_scope = nullptr; struct ForLoopInfo diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 28649f3e5..53369b86f 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include @@ -46,6 +47,8 @@ struct BuiltinContext Object const* currentObject = nullptr; /// Mapping from named objects to abstract assembly sub IDs. std::map subIDs; + + std::map functionIDs; }; struct BuiltinFunctionForEVM: public BuiltinFunction diff --git a/libyul/backends/evm/EVMObjectCompiler.cpp b/libyul/backends/evm/EVMObjectCompiler.cpp index bc14276af..9c98b6e78 100644 --- a/libyul/backends/evm/EVMObjectCompiler.cpp +++ b/libyul/backends/evm/EVMObjectCompiler.cpp @@ -86,6 +86,7 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize) *_object.analysisInfo, *_object.code, m_dialect, + m_eofVersion, context, OptimizedEVMCodeTransform::UseNamedLabels::ForFirstFunctionOfEachName ); diff --git a/libyul/backends/evm/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index 3d451afa2..17e06400f 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.cpp +++ b/libyul/backends/evm/EthAssemblyAdapter.cpp @@ -44,6 +44,11 @@ EthAssemblyAdapter::EthAssemblyAdapter(evmasm::Assembly& _assembly): { } +bool EthAssemblyAdapter::supportsFunctions() const +{ + return m_assembly.supportsFunctions(); +} + void EthAssemblyAdapter::setSourceLocation(SourceLocation const& _location) { m_assembly.setSourceLocation(_location); @@ -129,6 +134,31 @@ pair, AbstractAssembly::SubID> EthAssemblyAdapter:: return {make_shared(*assembly), static_cast(sub.data())}; } +AbstractAssembly::FunctionID EthAssemblyAdapter::createFunction(uint8_t _args, uint8_t _rets) +{ + return m_assembly.createFunction(_args, _rets); +} + +void EthAssemblyAdapter::beginFunction(AbstractAssembly::FunctionID _functionID) +{ + m_assembly.beginFunction(_functionID); +} + +void EthAssemblyAdapter::endFunction() +{ + m_assembly.endFunction(); +} + +void EthAssemblyAdapter::appendFunctionReturn() +{ + m_assembly.appendFunctionReturn(); +} + +void EthAssemblyAdapter::appendFunctionCall(FunctionID _functionID) +{ + m_assembly.appendFunctionCall(_functionID); +} + void EthAssemblyAdapter::appendDataOffset(vector const& _subPath) { if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end()) diff --git a/libyul/backends/evm/EthAssemblyAdapter.h b/libyul/backends/evm/EthAssemblyAdapter.h index 1a14447b4..2bb382700 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.h +++ b/libyul/backends/evm/EthAssemblyAdapter.h @@ -40,6 +40,7 @@ class EthAssemblyAdapter: public AbstractAssembly { public: explicit EthAssemblyAdapter(evmasm::Assembly& _assembly); + bool supportsFunctions() const override; void setSourceLocation(langutil::SourceLocation const& _location) override; int stackHeight() const override; void setStackHeight(int height) override; @@ -56,6 +57,11 @@ public: void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override; void appendAssemblySize() override; std::pair, SubID> createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name = {}) override; + AbstractAssembly::FunctionID createFunction(uint8_t _args, uint8_t _rets) override; + void beginFunction(AbstractAssembly::FunctionID _functionID) override; + void endFunction() override; + void appendFunctionCall(FunctionID _functionID) override; + void appendFunctionReturn() override; void appendDataOffset(std::vector const& _subPath) override; void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index d4bd24516..63e9646c0 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -104,6 +104,33 @@ pair, AbstractAssembly::SubID> NoOutputAssembly::cr return {}; } +AbstractAssembly::FunctionID NoOutputAssembly::createFunction(uint8_t _args, uint8_t _rets) +{ + yulAssert(m_context->numFunctions <= std::numeric_limits::max()); + AbstractAssembly::FunctionID id = static_cast(m_context->numFunctions++); + m_context->functionSignatures[id] = std::make_pair(_args, _rets); + return id; +} + +void NoOutputAssembly::beginFunction(FunctionID) +{ +} + +void NoOutputAssembly::endFunction() +{ +} + +void NoOutputAssembly::appendFunctionCall(FunctionID _functionID) +{ + auto [args, rets] = m_context->functionSignatures.at(_functionID); + m_stackHeight += static_cast(rets) - static_cast(args); +} + +void NoOutputAssembly::appendFunctionReturn() +{ + m_stackHeight = 0; +} + void NoOutputAssembly::appendDataOffset(std::vector const&) { appendInstruction(evmasm::Instruction::PUSH1); diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index ef5aeb24c..0c69ec135 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -37,6 +37,13 @@ struct SourceLocation; namespace solidity::yul { +class NoOutputAssembly; + +struct NoOutputAssemblyContext +{ + size_t numFunctions = 0; + std::map> functionSignatures; +}; /** * Assembly class that just ignores everything and only performs stack counting. @@ -45,9 +52,13 @@ namespace solidity::yul class NoOutputAssembly: public AbstractAssembly { public: - explicit NoOutputAssembly() { } + explicit NoOutputAssembly(bool _hasFunctions): m_hasFunctions(_hasFunctions), m_context(std::make_shared()) { } + NoOutputAssembly(bool _hasFunctions, std::shared_ptr _context): m_hasFunctions(_hasFunctions), m_context(_context) {} + ~NoOutputAssembly() override = default; + bool supportsFunctions() const override { return m_hasFunctions; } + void setSourceLocation(langutil::SourceLocation const&) override {} int stackHeight() const override { return m_stackHeight; } void setStackHeight(int height) override { m_stackHeight = height; } @@ -66,6 +77,11 @@ public: void appendAssemblySize() override; std::pair, SubID> createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name = "") override; + FunctionID createFunction(uint8_t _args, uint8_t rets) override; + void beginFunction(FunctionID) override; + void endFunction() override; + void appendFunctionCall(FunctionID _functionID) override; + void appendFunctionReturn() override; void appendDataOffset(std::vector const& _subPath) override; void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; @@ -78,6 +94,8 @@ public: void markAsInvalid() override {} private: + bool m_hasFunctions = false; + std::shared_ptr m_context; int m_stackHeight = 0; }; diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp index fd3a572e0..5658c3ebc 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp @@ -45,12 +45,28 @@ vector OptimizedEVMCodeTransform::run( AsmAnalysisInfo& _analysisInfo, Block const& _block, EVMDialect const& _dialect, + std::optional _eofVersion, BuiltinContext& _builtinContext, UseNamedLabels _useNamedLabelsForFunctions ) { - std::unique_ptr dfg = ControlFlowGraphBuilder::build(_analysisInfo, _dialect, _block); + std::unique_ptr dfg = ControlFlowGraphBuilder::build(_analysisInfo, _dialect, _eofVersion, _block); + yulAssert(_eofVersion.has_value() == dfg->useFunctions); StackLayout stackLayout = StackLayoutGenerator::run(*dfg); + + if (dfg->useFunctions) + for (Scope::Function const* function: dfg->functions) + { + auto const& info = dfg->functionInfo.at(function); + yulAssert(info.parameters.size() <= 0xFF); + yulAssert(info.returnVariables.size() <= 0xFF); + auto functionID = _assembly.createFunction( + static_cast(info.parameters.size()), + static_cast(info.returnVariables.size()) + ); + _builtinContext.functionIDs[function] = functionID; + } + OptimizedEVMCodeTransform optimizedCodeTransform( _assembly, _builtinContext, @@ -68,10 +84,11 @@ vector OptimizedEVMCodeTransform::run( void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call) { + bool useReturnLabel = !m_dfg.useFunctions && _call.canContinue; // Validate stack. { yulAssert(m_assembly.stackHeight() == static_cast(m_stack.size()), ""); - yulAssert(m_stack.size() >= _call.function.get().arguments.size() + (_call.canContinue ? 1 : 0), ""); + yulAssert(m_stack.size() >= _call.function.get().arguments.size() + (useReturnLabel ? 1 : 0), ""); // Assert that we got the correct arguments on stack for the call. for (auto&& [arg, slot]: ranges::zip_view( _call.functionCall.get().arguments | ranges::views::reverse, @@ -79,7 +96,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call) )) validateSlot(slot, arg); // Assert that we got the correct return label on stack. - if (_call.canContinue) + if (useReturnLabel) { auto const* returnLabelSlot = get_if( &m_stack.at(m_stack.size() - _call.functionCall.get().arguments.size() - 1) @@ -91,19 +108,22 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call) // Emit code. { m_assembly.setSourceLocation(originLocationOf(_call)); - m_assembly.appendJumpTo( - getFunctionLabel(_call.function), - static_cast(_call.function.get().returns.size() - _call.function.get().arguments.size()) - (_call.canContinue ? 1 : 0), - AbstractAssembly::JumpType::IntoFunction - ); - if (_call.canContinue) + if (m_dfg.useFunctions) + m_assembly.appendFunctionCall(m_builtinContext.functionIDs.at(&_call.function.get())); + else + m_assembly.appendJumpTo( + getFunctionLabel(_call.function), + static_cast(_call.function.get().returns.size() - _call.function.get().arguments.size()) - (_call.canContinue ? 1 : 0), + AbstractAssembly::JumpType::IntoFunction + ); + if (useReturnLabel) m_assembly.appendLabel(m_returnLabels.at(&_call.functionCall.get())); } // Update stack. { // Remove arguments and return label from m_stack. - for (size_t i = 0; i < _call.function.get().arguments.size() + (_call.canContinue ? 1 : 0); ++i) + for (size_t i = 0; i < _call.function.get().arguments.size() + (useReturnLabel ? 1 : 0); ++i) m_stack.pop_back(); // Push return values to m_stack. for (size_t index: ranges::views::iota(0u, _call.function.get().returns.size())) @@ -184,7 +204,7 @@ OptimizedEVMCodeTransform::OptimizedEVMCodeTransform( m_builtinContext(_builtinContext), m_dfg(_dfg), m_stackLayout(_stackLayout), - m_functionLabels([&](){ + m_functionLabels(_dfg.useFunctions ? decltype(m_functionLabels)() : [&](){ map functionLabels; set assignedFunctionNames; for (Scope::Function const* function: m_dfg.functions) @@ -217,6 +237,7 @@ void OptimizedEVMCodeTransform::assertLayoutCompatibility(Stack const& _currentS AbstractAssembly::LabelID OptimizedEVMCodeTransform::getFunctionLabel(Scope::Function const& _function) { + yulAssert(!m_dfg.useFunctions); return m_functionLabels.at(&m_dfg.functionInfo.at(&_function)); } @@ -491,11 +512,15 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block) Stack exitStack = m_currentFunctionInfo->returnVariables | ranges::views::transform([](auto const& _varSlot){ return StackSlot{_varSlot}; }) | ranges::to; - exitStack.emplace_back(FunctionReturnLabelSlot{_functionReturn.info->function}); + if (!m_dfg.useFunctions) + exitStack.emplace_back(FunctionReturnLabelSlot{_functionReturn.info->function}); // Create the function return layout and jump. createStackLayout(debugDataOf(_functionReturn), exitStack); - m_assembly.appendJump(0, AbstractAssembly::JumpType::OutOfFunction); + if (m_dfg.useFunctions) + m_assembly.appendFunctionReturn(); + else + m_assembly.appendJump(0, AbstractAssembly::JumpType::OutOfFunction); }, [&](CFG::BasicBlock::Terminated const&) { @@ -516,25 +541,31 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block) void OptimizedEVMCodeTransform::operator()(CFG::FunctionInfo const& _functionInfo) { + bool useReturnLabel = !m_dfg.useFunctions && _functionInfo.canContinue; yulAssert(!m_currentFunctionInfo, ""); ScopedSaveAndRestore currentFunctionInfoRestore(m_currentFunctionInfo, &_functionInfo); yulAssert(m_stack.empty() && m_assembly.stackHeight() == 0, ""); // Create function entry layout in m_stack. - if (_functionInfo.canContinue) + if (useReturnLabel) m_stack.emplace_back(FunctionReturnLabelSlot{_functionInfo.function}); for (auto const& param: _functionInfo.parameters | ranges::views::reverse) m_stack.emplace_back(param); + if (m_dfg.useFunctions) + m_assembly.beginFunction(m_builtinContext.functionIDs[&_functionInfo.function]); m_assembly.setStackHeight(static_cast(m_stack.size())); m_assembly.setSourceLocation(originLocationOf(_functionInfo)); - m_assembly.appendLabel(getFunctionLabel(_functionInfo.function)); + if (!m_dfg.useFunctions) + m_assembly.appendLabel(getFunctionLabel(_functionInfo.function)); // Create the entry layout of the function body block and visit. createStackLayout(debugDataOf(_functionInfo), m_stackLayout.blockInfos.at(_functionInfo.entry).entryLayout); (*this)(*_functionInfo.entry); m_stack.clear(); + if (m_dfg.useFunctions) + m_assembly.endFunction(); m_assembly.setStackHeight(0); } diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.h b/libyul/backends/evm/OptimizedEVMCodeTransform.h index ed03c1453..309b483ae 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.h +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.h @@ -52,6 +52,7 @@ public: AsmAnalysisInfo& _analysisInfo, Block const& _block, EVMDialect const& _dialect, + std::optional _eofVersion, BuiltinContext& _builtinContext, UseNamedLabels _useNamedLabelsForFunctions ); diff --git a/libyul/backends/evm/StackLayoutGenerator.cpp b/libyul/backends/evm/StackLayoutGenerator.cpp index 95c840733..440e9f50b 100644 --- a/libyul/backends/evm/StackLayoutGenerator.cpp +++ b/libyul/backends/evm/StackLayoutGenerator.cpp @@ -50,7 +50,7 @@ using namespace std; StackLayout StackLayoutGenerator::run(CFG const& _cfg) { - StackLayout stackLayout; + StackLayout stackLayout{_cfg.useFunctions, {}, {}}; StackLayoutGenerator{stackLayout}.processEntryPoint(*_cfg.entry); for (auto& functionInfo: _cfg.functionInfo | ranges::views::values) @@ -71,7 +71,7 @@ map> StackLayoutGenerator: vector StackLayoutGenerator::reportStackTooDeep(CFG const& _cfg, YulString _functionName) { - StackLayout stackLayout; + StackLayout stackLayout{_cfg.useFunctions, {}, {}}; CFG::FunctionInfo const* functionInfo = nullptr; if (!_functionName.empty()) { @@ -463,7 +463,9 @@ optional StackLayoutGenerator::getExitLayoutOrStageDependencies( Stack stack = _functionReturn.info->returnVariables | ranges::views::transform([](auto const& _varSlot){ return StackSlot{_varSlot}; }) | ranges::to; - stack.emplace_back(FunctionReturnLabelSlot{_functionReturn.info->function}); + + if (!m_layout.useFunctions) + stack.emplace_back(FunctionReturnLabelSlot{_functionReturn.info->function}); return stack; }, [&](CFG::BasicBlock::Terminated const&) -> std::optional diff --git a/libyul/backends/evm/StackLayoutGenerator.h b/libyul/backends/evm/StackLayoutGenerator.h index 40b554cd6..b2ad27465 100644 --- a/libyul/backends/evm/StackLayoutGenerator.h +++ b/libyul/backends/evm/StackLayoutGenerator.h @@ -37,6 +37,7 @@ struct StackLayout /// The resulting stack layout after executing the block. Stack exitLayout; }; + bool useFunctions = false; std::map blockInfos; /// For each operation the complete stack layout that: /// - has the slots required for the operation at the stack top. diff --git a/libyul/backends/wasm/EVMToEwasmTranslator.cpp b/libyul/backends/wasm/EVMToEwasmTranslator.cpp index d36b1cda3..cee012bc3 100644 --- a/libyul/backends/wasm/EVMToEwasmTranslator.cpp +++ b/libyul/backends/wasm/EVMToEwasmTranslator.cpp @@ -74,6 +74,7 @@ Object EVMToEwasmTranslator::run(Object const& _object) // expectedExecutionsPerDeployment is currently unused. OptimiserStepContext context{ m_dialect, + nullopt, nameDispenser, reservedIdentifiers, frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment diff --git a/libyul/optimiser/OptimiserStep.h b/libyul/optimiser/OptimiserStep.h index e3e5fe4c6..a1c3f0967 100644 --- a/libyul/optimiser/OptimiserStep.h +++ b/libyul/optimiser/OptimiserStep.h @@ -35,6 +35,7 @@ class NameDispenser; struct OptimiserStepContext { Dialect const& dialect; + std::optional eofVersion; NameDispenser& dispenser; std::set const& reservedIdentifiers; /// The value nullopt represents creation code diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index a6173996f..604b85e85 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -238,6 +238,7 @@ void eliminateVariablesOptimizedCodegen( bool StackCompressor::run( Dialect const& _dialect, + std::optional _eofVersion, Object& _object, bool _optimizeStackAllocation, size_t _maxIterations @@ -258,7 +259,7 @@ bool StackCompressor::run( if (usesOptimizedCodeGenerator) { yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object); - unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, *_object.code); + unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, _eofVersion, *_object.code); eliminateVariablesOptimizedCodegen( _dialect, *_object.code, @@ -269,7 +270,7 @@ bool StackCompressor::run( else for (size_t iterations = 0; iterations < _maxIterations; iterations++) { - map stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit; + map stackSurplus = CompilabilityChecker(_dialect, _eofVersion, _object, _optimizeStackAllocation).stackDeficit; if (stackSurplus.empty()) return true; eliminateVariables( diff --git a/libyul/optimiser/StackCompressor.h b/libyul/optimiser/StackCompressor.h index d18618667..9dd38cfcb 100644 --- a/libyul/optimiser/StackCompressor.h +++ b/libyul/optimiser/StackCompressor.h @@ -48,6 +48,7 @@ public: /// @returns true if it was successful. static bool run( Dialect const& _dialect, + std::optional _eofVersion, Object& _object, bool _optimizeStackAllocation, size_t _maxIterations diff --git a/libyul/optimiser/StackLimitEvader.cpp b/libyul/optimiser/StackLimitEvader.cpp index b4b9d5469..83ef8f0e0 100644 --- a/libyul/optimiser/StackLimitEvader.cpp +++ b/libyul/optimiser/StackLimitEvader.cpp @@ -131,12 +131,13 @@ void StackLimitEvader::run( if (evmDialect && evmDialect->evmVersion().canOverchargeGasForCall()) { yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(*evmDialect, _object); - unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, *evmDialect, *_object.code); + unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, *evmDialect, _context.eofVersion, *_object.code); run(_context, _object, StackLayoutGenerator::reportStackTooDeep(*cfg)); } else run(_context, _object, CompilabilityChecker{ _context.dialect, + _context.eofVersion, _object, true }.unreachableVariables); diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 0641328f1..18d59132e 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -135,6 +135,7 @@ void outputPerformanceMetrics(map const& _metrics) void OptimiserSuite::run( Dialect const& _dialect, + std::optional _eofVersion, GasMeter const* _meter, Object& _object, bool _optimizeStackAllocation, @@ -161,7 +162,7 @@ void OptimiserSuite::run( Block& ast = *_object.code; NameDispenser dispenser{_dialect, ast, reservedIdentifiers}; - OptimiserStepContext context{_dialect, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment}; + OptimiserStepContext context{_dialect, _eofVersion, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment}; OptimiserSuite suite(context, Debug::None); @@ -182,6 +183,7 @@ void OptimiserSuite::run( if (!usesOptimizedCodeGenerator) StackCompressor::run( _dialect, + _eofVersion, _object, _optimizeStackAllocation, stackCompressorMaxIterations @@ -202,6 +204,7 @@ void OptimiserSuite::run( { StackCompressor::run( _dialect, + _eofVersion, _object, _optimizeStackAllocation, stackCompressorMaxIterations diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index abed2720e..e747fb8d4 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -64,6 +64,7 @@ public: /// The value nullopt for `_expectedExecutionsPerDeployment` represents creation code. static void run( Dialect const& _dialect, + std::optional _eofVersion, GasMeter const* _meter, Object& _object, bool _optimizeStackAllocation, diff --git a/test/libyul/CompilabilityChecker.cpp b/test/libyul/CompilabilityChecker.cpp index b27f7ab54..15dba4e65 100644 --- a/test/libyul/CompilabilityChecker.cpp +++ b/test/libyul/CompilabilityChecker.cpp @@ -39,7 +39,12 @@ string check(string const& _input) Object obj; std::tie(obj.code, obj.analysisInfo) = yul::test::parse(_input, false); BOOST_REQUIRE(obj.code); - auto functions = CompilabilityChecker(EVMDialect::strictAssemblyForEVM(solidity::test::CommonOptions::get().evmVersion()), obj, true).stackDeficit; + auto functions = CompilabilityChecker( + EVMDialect::strictAssemblyForEVM(solidity::test::CommonOptions::get().evmVersion()), + solidity::test::CommonOptions::get().eofVersion(), + obj, + true + ).stackDeficit; string out; for (auto const& function: functions) out += function.first.str() + ": " + to_string(function.second) + " "; diff --git a/test/libyul/ControlFlowGraphTest.cpp b/test/libyul/ControlFlowGraphTest.cpp index dffc7ecbf..d910aaa07 100644 --- a/test/libyul/ControlFlowGraphTest.cpp +++ b/test/libyul/ControlFlowGraphTest.cpp @@ -208,7 +208,7 @@ TestCase::TestResult ControlFlowGraphTest::run(ostream& _stream, string const& _ std::ostringstream output; - std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, *object->code); + std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, nullopt /* TODO */, *object->code); output << "digraph CFG {\nnodesep=0.7;\nnode[shape=box];\n\n"; ControlFlowGraphPrinter printer{output}; diff --git a/test/libyul/KnowledgeBaseTest.cpp b/test/libyul/KnowledgeBaseTest.cpp index ec2f0313d..757e210eb 100644 --- a/test/libyul/KnowledgeBaseTest.cpp +++ b/test/libyul/KnowledgeBaseTest.cpp @@ -51,7 +51,7 @@ protected: NameDispenser dispenser(m_dialect, *m_object->code); std::set reserved; - OptimiserStepContext context{m_dialect, dispenser, reserved, 0}; + OptimiserStepContext context{m_dialect, nullopt /* TODO */, dispenser, reserved, 0}; CommonSubexpressionEliminator::run(context, *m_object->code); m_ssaValues(*m_object->code); diff --git a/test/libyul/StackLayoutGeneratorTest.cpp b/test/libyul/StackLayoutGeneratorTest.cpp index 70fda680e..a7979a1bd 100644 --- a/test/libyul/StackLayoutGeneratorTest.cpp +++ b/test/libyul/StackLayoutGeneratorTest.cpp @@ -225,7 +225,7 @@ TestCase::TestResult StackLayoutGeneratorTest::run(ostream& _stream, string cons std::ostringstream output; - std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, *object->code); + std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, nullopt /* TODO */, *object->code); StackLayout stackLayout = StackLayoutGenerator::run(*cfg); output << "digraph CFG {\nnodesep=0.7;\nnode[shape=box];\n\n"; diff --git a/test/libyul/YulOptimizerTestCommon.cpp b/test/libyul/YulOptimizerTestCommon.cpp index 7623efc12..418a81c6c 100644 --- a/test/libyul/YulOptimizerTestCommon.cpp +++ b/test/libyul/YulOptimizerTestCommon.cpp @@ -323,7 +323,7 @@ YulOptimizerTestCommon::YulOptimizerTestCommon( FunctionHoister::run(*m_context, *m_ast); FunctionGrouper::run(*m_context, *m_ast); size_t maxIterations = 16; - StackCompressor::run(*m_dialect, *m_object, true, maxIterations); + StackCompressor::run(*m_dialect, nullopt /* TODO */, *m_object, true, maxIterations); BlockFlattener::run(*m_context, *m_ast); }}, {"wordSizeTransform", [&]() { @@ -335,6 +335,7 @@ YulOptimizerTestCommon::YulOptimizerTestCommon( GasMeter meter(dynamic_cast(*m_dialect), false, 200); OptimiserSuite::run( *m_dialect, + nullopt, // TODO &meter, *m_object, true, @@ -347,6 +348,7 @@ YulOptimizerTestCommon::YulOptimizerTestCommon( disambiguate(); StackLimitEvader::run(*m_context, *m_object, CompilabilityChecker{ *m_dialect, + nullopt, // TODO *m_object, true }.unreachableVariables); @@ -461,6 +463,7 @@ void YulOptimizerTestCommon::updateContext() m_nameDispenser = make_unique(*m_dialect, *m_object->code, m_reservedIdentifiers); m_context = make_unique(OptimiserStepContext{ *m_dialect, + nullopt, // TODO *m_nameDispenser, m_reservedIdentifiers, frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index f0691a836..1cb6816d4 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -441,6 +441,9 @@ u256 EVMInstructionInterpreter::eval( case Instruction::SWAP14: case Instruction::SWAP15: case Instruction::SWAP16: + case Instruction::CALLF: + case Instruction::RETF: + case Instruction::JUMPF: { yulAssert(false, ""); return 0; diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index ad98ab9be..457b77460 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -222,7 +222,7 @@ public: { Object obj; obj.code = m_ast; - StackCompressor::run(m_dialect, obj, true, 16); + StackCompressor::run(m_dialect, nullopt /* TODO */, obj, true, 16); break; } default: @@ -251,6 +251,7 @@ private: NameDispenser m_nameDispenser{m_dialect, m_reservedIdentifiers}; OptimiserStepContext m_context{ m_dialect, + nullopt, // TODO m_nameDispenser, m_reservedIdentifiers, solidity::frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment diff --git a/tools/yulPhaser/Program.cpp b/tools/yulPhaser/Program.cpp index c62b8e130..35a32dc48 100644 --- a/tools/yulPhaser/Program.cpp +++ b/tools/yulPhaser/Program.cpp @@ -195,6 +195,7 @@ unique_ptr Program::applyOptimisationSteps( set const externallyUsedIdentifiers = {}; OptimiserStepContext context{ _dialect, + nullopt, // TODO _nameDispenser, externallyUsedIdentifiers, frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment