diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index b10427974ac5..fa6ad014f9ba 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -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()) { @@ -696,6 +705,46 @@ AssemblyItem Assembly::namedTag(std::string const& _name, size_t _params, size_t return AssemblyItem{Tag, m_namedTags.at(_name).id}; } +AssemblyItem Assembly::newFunctionCall(uint16_t _functionID) const +{ + 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 Assembly::newFunctionReturn() const +{ + return AssemblyItem::functionReturn(m_codeSections.at(m_currentCodeSection).outputs); +} + +uint16_t Assembly::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(functionID); +} + +void Assembly::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 Assembly::endFunction() +{ + solAssert(m_currentCodeSection != 0, "End function without begin function."); + m_currentCodeSection = 0; +} + AssemblyItem Assembly::newPushLibraryAddress(std::string const& _identifier) { h256 h(util::keccak256(_identifier)); @@ -1415,7 +1464,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); @@ -1468,6 +1520,25 @@ LinkerObject const& Assembly::assembleEOF() const appendBigEndianUint16(ret.bytecode, item.data()); break; } + case CallF: + case JumpF: + { + ret.bytecode.push_back(static_cast(item.instruction())); + solAssert(item.data() <= std::numeric_limits::max(), "Invalid callf/jumpf index value."); + size_t const index = static_cast(item.data()); + solAssert(index < m_codeSections.size()); + solAssert(m_codeSections[index].inputs == item.functionSignature().argsNum); + solAssert(m_codeSections[index].outputs == item.functionSignature().retsNum); + solAssert(item.type() != JumpF || item.functionSignature().retsNum == 0x80); + appendBigEndianUint16(ret.bytecode, item.data()); + break; + } + case RetF: + { + solAssert(item.data() <= 0x80, "Invalid number of outputs in RetF."); + ret.bytecode.push_back(static_cast(Instruction::RETF)); + break; + } default: solThrow(InvalidOpcode, "Unexpected opcode while assembling."); } diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 4b52ca3d7970..a80e74ede22e 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -65,9 +65,17 @@ class Assembly } std::optional 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) const; + AssemblyItem newFunctionReturn() const; + uint16_t createFunction(uint8_t _args, uint8_t _rets); + void beginFunction(uint16_t _functionID); + void endFunction(); + /// 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); } @@ -111,6 +119,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; } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 0b4305ac5f1c..a035e3231162 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -86,6 +86,9 @@ std::pair 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())}; @@ -139,6 +142,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 @@ -181,6 +185,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; @@ -195,7 +201,7 @@ 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(instructionInfo(instruction(), EVMVersion()).args); @@ -203,6 +209,10 @@ size_t AssemblyItem::arguments() const return std::get<0>(*m_verbatimBytecode); else if (type() == AssignImmutable) return 2; + else if (type() == CallF || type() == JumpF) + return functionSignature().argsNum; + else if (type() == RetF) + return static_cast(data()); else return 0; } @@ -235,6 +245,11 @@ size_t AssemblyItem::returnValues() const return std::get<1>(*m_verbatimBytecode); case AuxDataLoadN: return 1; + case CallF: + return functionSignature().retsNum; + case JumpF: + case RetF: + return 0; case AssignImmutable: case UndefinedItem: break; @@ -253,6 +268,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: @@ -383,6 +401,15 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const case ConditionalRelativeJump: text = "rjumpi{" + std::string("tag_") + std::to_string(realativeJumpOffset()) + "}"; break; + case CallF: + text = "callf{" + std::to_string(static_cast(data())) + "}"; + break; + case JumpF: + text = "jumpf{" + std::to_string(static_cast(data())) + "}"; + break; + case RetF: + text = "retf"; + break; } if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction) { @@ -405,6 +432,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(); diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 0b2a1c81057f..4b3278f01cd8 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -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. }; @@ -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)); @@ -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 @@ -262,12 +287,26 @@ class AssemblyItem void setImmutableOccurrences(size_t _n) const { m_immutableOccurrences = _n; } size_t realativeJumpOffset() const; + struct FunctionSignature + { + uint8_t argsNum; + uint8_t retsNum; + }; + + FunctionSignature const& functionSignature() const + { + solAssert(m_type == CallF || m_type == JumpF); + solAssert(m_functionSignature.has_value()); + return *m_functionSignature; + } + private: size_t opcodeCount() const noexcept; 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; ///< 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> m_verbatimBytecode; diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 3ee3a39cbdc4..1eff35fb2d66 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -275,7 +275,10 @@ unsigned GasMeter::runGas(Instruction _instruction, langutil::EVMVersion _evmVer case Tier::RJump: return GasCosts::tier1Gas; case Tier::RJumpI: return GasCosts::rjumpiGas; case Tier::VeryLow: return GasCosts::tier2Gas; + case Tier::RetF: return GasCosts::tier2Gas; case Tier::Low: return GasCosts::tier3Gas; + case Tier::CallF: return GasCosts::tier3Gas; + case Tier::JumpF: return GasCosts::tier3Gas; case Tier::Mid: return GasCosts::tier4Gas; case Tier::High: return GasCosts::tier5Gas; case Tier::BlockHash: return GasCosts::tier6Gas; diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 60ec75f9284e..82ed73cc994e 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -169,6 +169,9 @@ std::map 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 }, @@ -330,6 +333,9 @@ static std::map 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::RetF}}, + {Instruction::CALLF, {"CALLF", 2, 0, 0, true, Tier::CallF}}, + {Instruction::JUMPF, {"JUMPF", 2, 0, 0, true, Tier::JumpF}}, {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}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 76998c538d5f..2b66e7fc720a 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -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 @@ -303,8 +306,11 @@ enum class Tier Base, // 2, Quick RJump, // 2, RJump VeryLow, // 3, Fastest + RetF, // 3, RJumpI, // 4, Low, // 5, Fast + CallF, // 5, + JumpF, // 5, Mid, // 8, Mid High, // 10, Slow BlockHash, // 20 diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 668013b8199a..f45d017f03d9 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -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: @@ -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; @@ -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; diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp index f469f337d632..26364f2af2a2 100644 --- a/liblangutil/EVMVersion.cpp +++ b/liblangutil/EVMVersion.cpp @@ -82,6 +82,9 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional _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; diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 7c44dfa78dc1..1e1b87680b57 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -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; @@ -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, 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 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 980787c52297..e14586a759fb 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,8 @@ struct CFG bool canContinue = true; }; + /// True if control flow graph uses functions. False otherwise. + 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 fe6e0ec8ee1d..cebcd27496e5 100644 --- a/libyul/backends/evm/ControlFlowGraphBuilder.cpp +++ b/libyul/backends/evm/ControlFlowGraphBuilder.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -51,12 +52,27 @@ namespace /// Removes edges to blocks that are not reachable. void cleanUnreachable(CFG& _cfg) { + // If operation is a function call it adds the callee entry as child + auto const addFunctionsEntries = [&_cfg](CFG::BasicBlock* _node, auto&& _addChild) + { + for (auto const& o: _node->operations) + { + if (auto const* p = std::get_if(&o.operation); p != nullptr) + { + auto const fInfo = _cfg.functionInfo.at(&(p->function.get())); + _addChild(fInfo.entry); + } + } + }; + // Determine which blocks are reachable from the entry. util::BreadthFirstSearch reachabilityCheck{{_cfg.entry}}; for (auto const& functionInfo: _cfg.functionInfo | ranges::views::values) reachabilityCheck.verticesToTraverse.emplace_back(functionInfo.entry); reachabilityCheck.run([&](CFG::BasicBlock* _node, auto&& _addChild) { + if (_cfg.useFunctions) + addFunctionsEntries(_node, _addChild); visit(util::GenericVisitor{ [&](CFG::BasicBlock::Jump const& _jump) { _addChild(_jump.target); @@ -76,6 +92,19 @@ void cleanUnreachable(CFG& _cfg) cxx20::erase_if(node->entries, [&](CFG::BasicBlock* entry) -> bool { return !reachabilityCheck.visited.count(entry); }); + + if (_cfg.useFunctions) + { + // Remove functions which are never referenced. + _cfg.functions.erase(std::remove_if(_cfg.functions.begin(), _cfg.functions.end(), [&](auto const& item) { + return !reachabilityCheck.visited.count(_cfg.functionInfo.at(item).entry); + }), _cfg.functions.end()); + + // Remove functionInfos which are never referenced. + cxx20::erase_if(_cfg.functionInfo, [&](auto const& entry) -> bool { + return !reachabilityCheck.visited.count(entry.second.entry); + }); + } } /// Sets the ``recursive`` member to ``true`` for all recursive function calls. @@ -211,7 +240,11 @@ std::unique_ptr ControlFlowGraphBuilder::build( Block const& _block ) { - auto result = std::make_unique(); + std::optional eofVersion; + if (yul::EVMDialect const* evmDialect = dynamic_cast(&_dialect)) + eofVersion = evmDialect->eofVersion(); + + auto result = std::make_unique(eofVersion.has_value()); result->entry = &result->makeBlock(debugDataOf(_block)); ControlFlowSideEffectsCollector sideEffects(_dialect, _block); @@ -542,7 +575,8 @@ 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) + // For EOF (useFunctions == true) we do not have to put return label on stack. + 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/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 1443b44811fa..4109b0371d41 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include @@ -44,6 +45,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/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index cbaed2e6806a..50040e1074de 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.cpp +++ b/libyul/backends/evm/EthAssemblyAdapter.cpp @@ -145,6 +145,31 @@ std::pair, AbstractAssembly::SubID> EthAssembl return {std::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::appendEOFCreate(ContainerID _containerID) { m_assembly.appendEOFCreate(_containerID); diff --git a/libyul/backends/evm/EthAssemblyAdapter.h b/libyul/backends/evm/EthAssemblyAdapter.h index d0bd3e14aff5..202d6927e66c 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.h +++ b/libyul/backends/evm/EthAssemblyAdapter.h @@ -56,6 +56,11 @@ class EthAssemblyAdapter: public AbstractAssembly void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override; void appendAssemblySize() override; std::pair, SubID> createSubAssembly(bool _creation, 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 appendEOFCreate(ContainerID _containerID) override; void appendReturnContract(ContainerID _containerID) override; void appendDataOffset(std::vector const& _subPath) override; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index 0497b11b63fb..74f43cfc7881 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -120,6 +120,31 @@ std::pair, AbstractAssembly::SubID> NoOutputAs 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 5af1bd418026..b61186df378f 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,7 +52,7 @@ namespace solidity::yul class NoOutputAssembly: public AbstractAssembly { public: - explicit NoOutputAssembly(langutil::EVMVersion _evmVersion): m_evmVersion(_evmVersion) { } + explicit NoOutputAssembly(langutil::EVMVersion _evmVersion): m_context(std::make_shared()), m_evmVersion(_evmVersion) { } ~NoOutputAssembly() override = default; void setSourceLocation(langutil::SourceLocation const&) override {} @@ -66,6 +73,11 @@ class NoOutputAssembly: public AbstractAssembly void appendAssemblySize() override; std::pair, SubID> createSubAssembly(bool _creation, 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; @@ -84,6 +96,7 @@ class NoOutputAssembly: public AbstractAssembly langutil::EVMVersion evmVersion() const override { return m_evmVersion; } private: + std::shared_ptr m_context; int m_stackHeight = 0; langutil::EVMVersion m_evmVersion; }; diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp index 8ef01f0b6a86..52462b378823 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp @@ -49,7 +49,25 @@ std::vector OptimizedEVMCodeTransform::run( ) { std::unique_ptr dfg = ControlFlowGraphBuilder::build(_analysisInfo, _dialect, _block); + yulAssert(_dialect.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() <= std::numeric_limits::max()); + yulAssert(info.returnVariables.size() <= std::numeric_limits::max()); + // According to EOF spec function output num equals 0x80 means non-returning function + auto functionID = _assembly.createFunction( + static_cast(info.parameters.size()), + static_cast(info.canContinue ? info.returnVariables.size() : 0x80) + ); + _builtinContext.functionIDs[function] = functionID; + } + } + OptimizedEVMCodeTransform optimizedCodeTransform( _assembly, _builtinContext, @@ -67,10 +85,11 @@ std::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().numArguments + (_call.canContinue ? 1 : 0), ""); + yulAssert(m_stack.size() >= _call.function.get().numArguments + (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, @@ -78,7 +97,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 = std::get_if( &m_stack.at(m_stack.size() - _call.functionCall.get().arguments.size() - 1) @@ -90,21 +109,26 @@ 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().numReturns) - static_cast(_call.function.get().numArguments) - (_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().numReturns) - static_cast(_call.function.get().numArguments) - (_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().numArguments + (_call.canContinue ? 1 : 0); ++i) + for (size_t i = 0; i < _call.function.get().numArguments + (useReturnLabel ? 1 : 0); ++i) m_stack.pop_back(); // Push return values to m_stack. + if (m_dfg.useFunctions) + yulAssert(_call.function.get().numReturns < 0x80, "Num of function output >= 128"); for (size_t index: ranges::views::iota(0u, _call.function.get().numReturns)) m_stack.emplace_back(TemporarySlot{_call.functionCall, index}); yulAssert(m_assembly.stackHeight() == static_cast(m_stack.size()), ""); @@ -183,7 +207,7 @@ OptimizedEVMCodeTransform::OptimizedEVMCodeTransform( m_builtinContext(_builtinContext), m_dfg(_dfg), m_stackLayout(_stackLayout), - m_functionLabels([&](){ + m_functionLabels(_dfg.useFunctions ? decltype(m_functionLabels)() : [&](){ std::map functionLabels; std::set assignedFunctionNames; for (Scope::Function const* function: m_dfg.functions) @@ -216,6 +240,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)); } @@ -493,11 +518,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&) { @@ -518,25 +547,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 7e648ca964bb..81665f1f787a 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.h +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.h @@ -104,6 +104,7 @@ class OptimizedEVMCodeTransform Stack m_stack; std::map m_returnLabels; std::map m_blockLabels; + /// Non-empty only if m_dfg.useFunctions == false std::map const m_functionLabels; /// Set of blocks already generated. If any of the contained blocks is ever jumped to, m_blockLabels should /// contain a jump label for it. diff --git a/libyul/backends/evm/StackLayoutGenerator.cpp b/libyul/backends/evm/StackLayoutGenerator.cpp index 1a30842aecc8..a503c034e0b8 100644 --- a/libyul/backends/evm/StackLayoutGenerator.cpp +++ b/libyul/backends/evm/StackLayoutGenerator.cpp @@ -49,7 +49,7 @@ using namespace solidity::yul; StackLayout StackLayoutGenerator::run(CFG const& _cfg) { - StackLayout stackLayout; + StackLayout stackLayout{_cfg.useFunctions, {}, {}}; StackLayoutGenerator{stackLayout, nullptr}.processEntryPoint(*_cfg.entry); for (auto& functionInfo: _cfg.functionInfo | ranges::views::values) @@ -70,7 +70,7 @@ std::map> StackLayoutGe std::vector StackLayoutGenerator::reportStackTooDeep(CFG const& _cfg, YulName _functionName) { - StackLayout stackLayout; + StackLayout stackLayout{_cfg.useFunctions, {}, {}}; CFG::FunctionInfo const* functionInfo = nullptr; if (!_functionName.empty()) { @@ -464,7 +464,9 @@ std::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 @@ -735,7 +737,7 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block, CFG::Functi _addChild(_conditionalJump.zero); _addChild(_conditionalJump.nonZero); }, - [&](CFG::BasicBlock::FunctionReturn const&) { yulAssert(false); }, + [&](CFG::BasicBlock::FunctionReturn const&) { yulAssert(m_layout.useFunctions); }, [&](CFG::BasicBlock::Terminated const&) {}, }, _block->exit); }); diff --git a/libyul/backends/evm/StackLayoutGenerator.h b/libyul/backends/evm/StackLayoutGenerator.h index fc846a9deced..cbd1d2b2527a 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/test/libyul/ControlFlowGraphTest.cpp b/test/libyul/ControlFlowGraphTest.cpp index b7264bc0e96d..eb1aa0184801 100644 --- a/test/libyul/ControlFlowGraphTest.cpp +++ b/test/libyul/ControlFlowGraphTest.cpp @@ -41,7 +41,7 @@ using namespace solidity::frontend; using namespace solidity::frontend::test; ControlFlowGraphTest::ControlFlowGraphTest(std::string const& _filename): - TestCase(_filename) + frontend::test::EVMVersionRestrictedTestCase(_filename) { m_source = m_reader.source(); auto dialectName = m_reader.stringSetting("dialect", "evm"); diff --git a/test/libyul/ControlFlowGraphTest.h b/test/libyul/ControlFlowGraphTest.h index 44cea06d3f7c..e9de50a610c3 100644 --- a/test/libyul/ControlFlowGraphTest.h +++ b/test/libyul/ControlFlowGraphTest.h @@ -27,7 +27,7 @@ struct Dialect; namespace test { -class ControlFlowGraphTest: public solidity::frontend::test::TestCase +class ControlFlowGraphTest: public frontend::test::EVMVersionRestrictedTestCase { public: static std::unique_ptr create(Config const& _config) diff --git a/test/libyul/StackLayoutGeneratorTest.cpp b/test/libyul/StackLayoutGeneratorTest.cpp index f50d2d23b028..a8949b658e6b 100644 --- a/test/libyul/StackLayoutGeneratorTest.cpp +++ b/test/libyul/StackLayoutGeneratorTest.cpp @@ -45,7 +45,7 @@ using namespace solidity::frontend; using namespace solidity::frontend::test; StackLayoutGeneratorTest::StackLayoutGeneratorTest(std::string const& _filename): - TestCase(_filename) + frontend::test::EVMVersionRestrictedTestCase(_filename) { m_source = m_reader.source(); auto dialectName = m_reader.stringSetting("dialect", "evm"); diff --git a/test/libyul/StackLayoutGeneratorTest.h b/test/libyul/StackLayoutGeneratorTest.h index 9f570045f78e..6116b1df7347 100644 --- a/test/libyul/StackLayoutGeneratorTest.h +++ b/test/libyul/StackLayoutGeneratorTest.h @@ -27,7 +27,7 @@ struct Dialect; namespace test { -class StackLayoutGeneratorTest: public solidity::frontend::test::TestCase +class StackLayoutGeneratorTest: public frontend::test::EVMVersionRestrictedTestCase { public: static std::unique_ptr create(Config const& _config) diff --git a/test/libyul/objectCompiler/eof/functions.yul b/test/libyul/objectCompiler/eof/functions.yul new file mode 100644 index 000000000000..e81cf774298f --- /dev/null +++ b/test/libyul/objectCompiler/eof/functions.yul @@ -0,0 +1,86 @@ +object "a" { + code { + + mstore(0, fun1(calldataload(0))) + + if calldataload(32) { + non_ret_fun() + } + + return(0, 32) + + function fun1(i) -> r { + if i { + r := 5 + leave + } + r := 99 + } + + function non_ret_fun() { + revert(0, 0) + } + } +} + +// ==== +// EVMVersion: >=prague +// bytecodeFormat: >=EOFv1 +// ---- +// Assembly: +// /* "source":74:75 */ +// 0x00 +// /* "source":61:76 */ +// calldataload +// /* "source":56:77 */ +// callf{1} +// /* "source":53:54 */ +// 0x00 +// /* "source":46:78 */ +// mstore +// /* "source":107:109 */ +// 0x20 +// /* "source":94:110 */ +// calldataload +// /* "source":91:128 */ +// rjumpi{tag_1} +// /* "source":22:386 */ +// tag_2: +// /* "source":151:153 */ +// 0x20 +// /* "source":148:149 */ +// 0x00 +// /* "source":141:154 */ +// return +// /* "source":111:128 */ +// tag_1: +// /* "source":113:126 */ +// jumpf{2} +// +// code_section_1: assembly { +// /* "source":217:294 */ +// rjumpi{tag_3} +// /* "source":203:324 */ +// tag_4: +// /* "source":312:314 */ +// 0x63 +// /* "source":173:324 */ +// retf +// /* "source":234:294 */ +// tag_3: +// /* "source":257:258 */ +// 0x05 +// /* "source":275:280 */ +// retf +// } +// +// code_section_2: assembly { +// /* "source":376:377 */ +// 0x00 +// /* "source":366:378 */ +// dup1 +// revert +// } +// Bytecode: ef000101000c020003001400090003040000000080ffff0101ffff0080ffff5f35e300015f52602035e1000460205ff3e50002e100036063e46005e45f80fd +// Opcodes: 0xEF STOP ADD ADD STOP 0xC MUL STOP SUB STOP EQ STOP MULMOD STOP SUB DIV STOP STOP STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT ADD ADD SELFDESTRUCT SELFDESTRUCT STOP DUP1 SELFDESTRUCT SELFDESTRUCT PUSH0 CALLDATALOAD CALLF 0x1 PUSH0 MSTORE PUSH1 0x20 CALLDATALOAD RJUMPI 0x4 PUSH1 0x20 PUSH0 RETURN JUMPF 0x2 RJUMPI 0x3 PUSH1 0x63 RETF PUSH1 0x5 RETF PUSH0 DUP1 REVERT +// SourceMappings: 74:1:0:-:0;61:15;56:21;53:1;46:32;107:2;94:16;91:37;22:364;151:2;148:1;141:13;111:17;113:13217:77:0:-:0;203:121;312:2;173:151;234:60;257:1;275:5376:1:0:-:0;366:12; diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 044fff8ce6f4..92ac56d6919f 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -488,6 +488,9 @@ u256 EVMInstructionInterpreter::eval( case Instruction::SWAP16: yulAssert(false, "Impossible in strict assembly."); case Instruction::DATALOADN: + case Instruction::CALLF: + case Instruction::RETF: + case Instruction::JUMPF: case Instruction::EOFCREATE: case Instruction::RETURNCONTRACT: case Instruction::RJUMP: