From 077ae074ebd3c9d435b6dcb965e239f0264a4fdd Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Thu, 20 Mar 2025 09:12:05 +0100 Subject: [PATCH 1/2] EVMDialect: Encapsulate adding builtins during construction into a class --- libyul/backends/evm/EVMDialect.cpp | 331 ++++++++++++++++------------- 1 file changed, 183 insertions(+), 148 deletions(-) diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index d68ef16348b1..47755f59b406 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -195,62 +195,100 @@ std::set> createReservedIdentifiers(langutil::EVMVersio return reserved; } -std::vector> createBuiltins(langutil::EVMVersion _evmVersion, std::optional _eofVersion, bool _objectAccess) +class Builtins { +public: + explicit Builtins(langutil::EVMVersion const& _evmVersion, std::optional const& _eofVersion): + m_evmVersion(_evmVersion), + m_eofVersion(_eofVersion) + {} - // Exclude prevrandao as builtin for VMs before paris and difficulty for VMs after paris. - auto prevRandaoException = [&](std::string const& _instrName) -> bool - { - return (_instrName == "prevrandao" && _evmVersion < langutil::EVMVersion::paris()) || (_instrName == "difficulty" && _evmVersion >= langutil::EVMVersion::paris()); - }; - - std::vector> builtins; - for (auto const& instr: evmasm::c_instructions) + void addInstruction(std::string const& _name, evmasm::Instruction const& _opcode) { - std::string name = toLower(instr.first); - auto const opcode = instr.second; + auto const lowerCaseName = toLower(_name); + // Exclude prevrandao as builtin for VMs before paris and difficulty for VMs after paris. + auto prevRandaoException = [&](std::string_view const _instrName) -> bool + { + return (_instrName == "prevrandao" && m_evmVersion < langutil::EVMVersion::paris()) || (_instrName == "difficulty" && m_evmVersion >= langutil::EVMVersion::paris()); + }; if ( - !(opcode >= evmasm::Instruction::DUP1 && opcode <= evmasm::Instruction::DUP16) && - !(opcode >= evmasm::Instruction::SWAP1 && opcode <= evmasm::Instruction::SWAP16) && - !evmasm::isPushInstruction(opcode) && - opcode != evmasm::Instruction::JUMP && - opcode != evmasm::Instruction::JUMPI && - opcode != evmasm::Instruction::JUMPDEST && - opcode != evmasm::Instruction::DATALOADN && - opcode != evmasm::Instruction::EOFCREATE && - opcode != evmasm::Instruction::RETURNCONTRACT && - opcode != evmasm::Instruction::RJUMP && - opcode != evmasm::Instruction::RJUMPI && - opcode != evmasm::Instruction::CALLF && - opcode != evmasm::Instruction::JUMPF && - opcode != evmasm::Instruction::DUPN && - opcode != evmasm::Instruction::SWAPN && - opcode != evmasm::Instruction::RETF && - _evmVersion.hasOpcode(opcode, _eofVersion) && - !prevRandaoException(name) + !(_opcode >= evmasm::Instruction::DUP1 && _opcode <= evmasm::Instruction::DUP16) && + !(_opcode >= evmasm::Instruction::SWAP1 && _opcode <= evmasm::Instruction::SWAP16) && + !isPushInstruction(_opcode) && + _opcode != evmasm::Instruction::JUMP && + _opcode != evmasm::Instruction::JUMPI && + _opcode != evmasm::Instruction::JUMPDEST && + _opcode != evmasm::Instruction::DATALOADN && + _opcode != evmasm::Instruction::EOFCREATE && + _opcode != evmasm::Instruction::RETURNCONTRACT && + _opcode != evmasm::Instruction::RJUMP && + _opcode != evmasm::Instruction::RJUMPI && + _opcode != evmasm::Instruction::CALLF && + _opcode != evmasm::Instruction::JUMPF && + _opcode != evmasm::Instruction::DUPN && + _opcode != evmasm::Instruction::SWAPN && + _opcode != evmasm::Instruction::RETF && + m_evmVersion.hasOpcode(_opcode, m_eofVersion) && + !prevRandaoException(lowerCaseName) ) - builtins.emplace_back(createEVMFunction(_evmVersion, name, opcode)); + m_builtinFunctions.emplace_back(createEVMFunction(m_evmVersion, lowerCaseName, _opcode)); else - builtins.emplace_back(std::nullopt); + m_builtinFunctions.emplace_back(std::nullopt); } - auto const createIfObjectAccess = [_objectAccess]( + /// When adding new builtin functions, it is imperative to not change the order between dialect versions and/or + /// object access modifiers. Blanks are added to be able to uphold this property. + /// Changing the order affects the generated `BuiltinHandle`s, which makes different dialect versions inherently + /// incompatible. + void addIfObjectAccess( + bool const _objectAccess, std::string const& _name, - size_t _params, - size_t _returns, + size_t const _params, + size_t const _returns, SideEffects _sideEffects, ControlFlowSideEffects _controlFlowSideEffects, std::vector> _literalArguments, std::function _generateCode - ) -> std::optional + ) { - if (!_objectAccess) - return std::nullopt; - return createFunction(_name, _params, _returns, _sideEffects, _controlFlowSideEffects, std::move(_literalArguments), std::move(_generateCode)); - }; + if (_objectAccess) + m_builtinFunctions.emplace_back( + createFunction( + _name, + _params, + _returns, + _sideEffects, + _controlFlowSideEffects, + std::move(_literalArguments), + std::move(_generateCode) + ) + ); + else + m_builtinFunctions.emplace_back(std::nullopt); + } + + std::vector> const& functions() const + { + return m_builtinFunctions; + } + +private: + langutil::EVMVersion m_evmVersion; + std::optional m_eofVersion; + std::vector> m_builtinFunctions; +}; + +/// Make sure to only add builtins in a way that is consistent over EVM versions. If the order depends on the +/// EVM version - which can easily happen using conditionals -, different dialects' builtin handles +/// become inherently incompatible. +Builtins createBuiltins(langutil::EVMVersion _evmVersion, std::optional _eofVersion, bool _objectAccess) +{ + Builtins builtins(_evmVersion, _eofVersion); + for (auto const& [name, opcode]: evmasm::c_instructions) + builtins.addInstruction(name, opcode); - builtins.emplace_back(createIfObjectAccess("linkersymbol", 1, 1, SideEffects{}, ControlFlowSideEffects{}, {LiteralKind::String}, []( + builtins.addIfObjectAccess(_objectAccess, "linkersymbol", 1, 1, SideEffects{}, ControlFlowSideEffects{}, {LiteralKind::String}, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& @@ -258,28 +296,22 @@ std::vector> createBuiltins(langutil::EVMVe yulAssert(_call.arguments.size() == 1, ""); Expression const& arg = _call.arguments.front(); _assembly.appendLinkerSymbol(formatLiteral(std::get(arg))); - })); - builtins.emplace_back(createIfObjectAccess( - "memoryguard", - 1, - 1, - SideEffects{}, - ControlFlowSideEffects{}, - {LiteralKind::Number}, - []( - FunctionCall const& _call, - AbstractAssembly& _assembly, - BuiltinContext& - ) { - yulAssert(_call.arguments.size() == 1, ""); - Literal const* literal = std::get_if(&_call.arguments.front()); - yulAssert(literal, ""); - _assembly.appendConstant(literal->value.value()); - }) - ); + }); + + builtins.addIfObjectAccess(_objectAccess, "memoryguard", 1, 1, SideEffects{}, ControlFlowSideEffects{}, {LiteralKind::Number}, []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& + ) { + yulAssert(_call.arguments.size() == 1, ""); + Literal const* literal = std::get_if(&_call.arguments.front()); + yulAssert(literal, ""); + _assembly.appendConstant(literal->value.value()); + }); + if (!_eofVersion.has_value()) { - builtins.emplace_back(createIfObjectAccess("datasize", 1, 1, SideEffects{}, ControlFlowSideEffects{}, {LiteralKind::String}, []( + builtins.addIfObjectAccess(_objectAccess, "datasize", 1, 1, SideEffects{}, ControlFlowSideEffects{}, {LiteralKind::String}, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context @@ -299,8 +331,8 @@ std::vector> createBuiltins(langutil::EVMVe yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">."); _assembly.appendDataSize(subIdPath); } - })); - builtins.emplace_back(createIfObjectAccess("dataoffset", 1, 1, SideEffects{}, ControlFlowSideEffects{}, {LiteralKind::String}, []( + }); + builtins.addIfObjectAccess(_objectAccess, "dataoffset", 1, 1, SideEffects{}, ControlFlowSideEffects{}, {LiteralKind::String}, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context @@ -320,8 +352,9 @@ std::vector> createBuiltins(langutil::EVMVe yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">."); _assembly.appendDataOffset(subIdPath); } - })); - builtins.emplace_back(createIfObjectAccess( + }); + builtins.addIfObjectAccess( + _objectAccess, "datacopy", 3, 0, @@ -335,8 +368,9 @@ std::vector> createBuiltins(langutil::EVMVe ) { _assembly.appendInstruction(evmasm::Instruction::CODECOPY); } - )); - builtins.emplace_back(createIfObjectAccess( + ); + builtins.addIfObjectAccess( + _objectAccess, "setimmutable", 3, 0, @@ -362,8 +396,9 @@ std::vector> createBuiltins(langutil::EVMVe auto const identifier = (formatLiteral(std::get(_call.arguments[1]))); _assembly.appendImmutableAssignment(identifier); } - )); - builtins.emplace_back(createIfObjectAccess( + ); + builtins.addIfObjectAccess( + _objectAccess, "loadimmutable", 1, 1, @@ -378,82 +413,82 @@ std::vector> createBuiltins(langutil::EVMVe yulAssert(_call.arguments.size() == 1, ""); _assembly.appendImmutable(formatLiteral(std::get(_call.arguments.front()))); } - )); + ); } else // EOF context { - if (_objectAccess) - { - builtins.emplace_back(createFunction( - "auxdataloadn", - 1, - 1, - EVMDialect::sideEffectsOfInstruction(evmasm::Instruction::DATALOADN), - ControlFlowSideEffects::fromInstruction(evmasm::Instruction::DATALOADN), - {LiteralKind::Number}, - []( - FunctionCall const& _call, - AbstractAssembly& _assembly, - BuiltinContext& - ) { - yulAssert(_call.arguments.size() == 1); - Literal const* literal = std::get_if(&_call.arguments.front()); - yulAssert(literal, ""); - yulAssert(literal->value.value() <= std::numeric_limits::max()); - _assembly.appendAuxDataLoadN(static_cast(literal->value.value())); - } - )); - - builtins.emplace_back(createFunction( - "eofcreate", - 5, - 1, - EVMDialect::sideEffectsOfInstruction(evmasm::Instruction::EOFCREATE), - ControlFlowSideEffects::fromInstruction(evmasm::Instruction::EOFCREATE), - {LiteralKind::String, std::nullopt, std::nullopt, std::nullopt, std::nullopt}, - []( - FunctionCall const& _call, - AbstractAssembly& _assembly, - BuiltinContext& context - ) { - yulAssert(_call.arguments.size() == 5); - Literal const* literal = std::get_if(&_call.arguments.front()); - auto const formattedLiteral = formatLiteral(*literal); - yulAssert(!util::contains(formattedLiteral, '.')); - auto const* containerID = valueOrNullptr(context.subIDs, formattedLiteral); - yulAssert(containerID != nullptr); - yulAssert(*containerID <= std::numeric_limits::max()); - _assembly.appendEOFCreate(static_cast(*containerID)); - } - )); - - builtins.emplace_back(createFunction( - "returncontract", - 3, - 0, - EVMDialect::sideEffectsOfInstruction(evmasm::Instruction::RETURNCONTRACT), - ControlFlowSideEffects::fromInstruction(evmasm::Instruction::RETURNCONTRACT), - {LiteralKind::String, std::nullopt, std::nullopt}, - []( - FunctionCall const& _call, - AbstractAssembly& _assembly, - BuiltinContext& context - ) { - yulAssert(_call.arguments.size() == 3); - Literal const* literal = std::get_if(&_call.arguments.front()); - yulAssert(literal); - auto const formattedLiteral = formatLiteral(*literal); - yulAssert(!util::contains(formattedLiteral, '.')); - auto const* containerID = valueOrNullptr(context.subIDs, formattedLiteral); - yulAssert(containerID != nullptr); - yulAssert(*containerID <= std::numeric_limits::max()); - _assembly.appendReturnContract(static_cast(*containerID)); - } - )); - } + builtins.addIfObjectAccess( + _objectAccess, + "auxdataloadn", + 1, + 1, + EVMDialect::sideEffectsOfInstruction(evmasm::Instruction::DATALOADN), + ControlFlowSideEffects::fromInstruction(evmasm::Instruction::DATALOADN), + {LiteralKind::Number}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& + ) { + yulAssert(_call.arguments.size() == 1); + Literal const* literal = std::get_if(&_call.arguments.front()); + yulAssert(literal, ""); + yulAssert(literal->value.value() <= std::numeric_limits::max()); + _assembly.appendAuxDataLoadN(static_cast(literal->value.value())); + } + ); + + builtins.addIfObjectAccess( + _objectAccess, + "eofcreate", + 5, + 1, + EVMDialect::sideEffectsOfInstruction(evmasm::Instruction::EOFCREATE), + ControlFlowSideEffects::fromInstruction(evmasm::Instruction::EOFCREATE), + {LiteralKind::String, std::nullopt, std::nullopt, std::nullopt, std::nullopt}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& context + ) { + yulAssert(_call.arguments.size() == 5); + Literal const* literal = std::get_if(&_call.arguments.front()); + auto const formattedLiteral = formatLiteral(*literal); + yulAssert(!util::contains(formattedLiteral, '.')); + auto const* containerID = valueOrNullptr(context.subIDs, formattedLiteral); + yulAssert(containerID != nullptr); + yulAssert(*containerID <= std::numeric_limits::max()); + _assembly.appendEOFCreate(static_cast(*containerID)); + } + ); + + builtins.addIfObjectAccess( + _objectAccess, + "returncontract", + 3, + 0, + EVMDialect::sideEffectsOfInstruction(evmasm::Instruction::RETURNCONTRACT), + ControlFlowSideEffects::fromInstruction(evmasm::Instruction::RETURNCONTRACT), + {LiteralKind::String, std::nullopt, std::nullopt}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& context + ) { + yulAssert(_call.arguments.size() == 3); + Literal const* literal = std::get_if(&_call.arguments.front()); + yulAssert(literal); + auto const formattedLiteral = formatLiteral(*literal); + yulAssert(!util::contains(formattedLiteral, '.')); + auto const* containerID = valueOrNullptr(context.subIDs, formattedLiteral); + yulAssert(containerID != nullptr); + yulAssert(*containerID <= std::numeric_limits::max()); + _assembly.appendReturnContract(static_cast(*containerID)); + } + ); } yulAssert( - ranges::all_of(builtins, [](std::optional const& _builtinFunction){ + ranges::all_of(builtins.functions(), [](std::optional const& _builtinFunction){ return !_builtinFunction || _builtinFunction->name.substr(0, "verbatim_"s.size()) != "verbatim_"; }), "Builtin functions besides verbatim should not start with the verbatim_ prefix." @@ -474,7 +509,7 @@ EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, std::optional m_objectAccess(_objectAccess), m_evmVersion(_evmVersion), m_eofVersion(_eofVersion), - m_functions(createBuiltins(_evmVersion, _eofVersion, _objectAccess)), + m_functions(createBuiltins(_evmVersion, _eofVersion, _objectAccess).functions()), m_reserved(createReservedIdentifiers(_evmVersion, _eofVersion)) { for (auto const& [index, maybeBuiltin]: m_functions | ranges::views::enumerate) @@ -482,14 +517,14 @@ EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, std::optional // ids are offset by the maximum number of verbatim functions m_builtinFunctionsByName[maybeBuiltin->name] = BuiltinHandle{index + verbatimIDOffset}; - m_discardFunction = findBuiltin("pop"); - m_equalityFunction = findBuiltin("eq"); - m_booleanNegationFunction = findBuiltin("iszero"); - m_memoryStoreFunction = findBuiltin("mstore"); - m_memoryLoadFunction = findBuiltin("mload"); - m_storageStoreFunction = findBuiltin("sstore"); - m_storageLoadFunction = findBuiltin("sload"); - m_hashFunction = findBuiltin("keccak256"); + m_discardFunction = EVMDialect::findBuiltin("pop"); + m_equalityFunction = EVMDialect::findBuiltin("eq"); + m_booleanNegationFunction = EVMDialect::findBuiltin("iszero"); + m_memoryStoreFunction = EVMDialect::findBuiltin("mstore"); + m_memoryLoadFunction = EVMDialect::findBuiltin("mload"); + m_storageStoreFunction = EVMDialect::findBuiltin("sstore"); + m_storageLoadFunction = EVMDialect::findBuiltin("sload"); + m_hashFunction = EVMDialect::findBuiltin("keccak256"); m_auxiliaryBuiltinHandles.add = EVMDialect::findBuiltin("add"); m_auxiliaryBuiltinHandles.exp = EVMDialect::findBuiltin("exp"); From 106775f784693fd829832268f47435af6024ee8d Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Thu, 20 Mar 2025 11:31:46 +0100 Subject: [PATCH 2/2] EVMDialect: add test that enforces builtin handle compatibility Between current default dialect as well as latest dialect and all other dialects. --- libyul/backends/evm/EVMDialect.cpp | 6 ++ libyul/backends/evm/EVMDialect.h | 2 + test/CMakeLists.txt | 2 + test/libyul/EVMDialectCompatibility.cpp | 82 +++++++++++++++++++++++++ test/libyul/EVMDialectCompatibility.h | 78 +++++++++++++++++++++++ 5 files changed, 170 insertions(+) create mode 100644 test/libyul/EVMDialectCompatibility.cpp create mode 100644 test/libyul/EVMDialectCompatibility.h diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 47755f59b406..9aca685d925f 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -616,6 +617,11 @@ SideEffects EVMDialect::sideEffectsOfInstruction(evmasm::Instruction _instructio }; } +std::set EVMDialect::builtinFunctionNames() const +{ + return ranges::views::keys(m_builtinFunctionsByName) | ranges::to; +} + BuiltinFunctionForEVM EVMDialect::createVerbatimFunctionFromHandle(BuiltinHandle const& _handle) { return std::apply(createVerbatimFunction, verbatimIndexToArgsAndRets(_handle.id)); diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 0440daade1ce..51d362738344 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -113,6 +113,8 @@ class EVMDialect: public Dialect static size_t constexpr verbatimMaxInputSlots = 100; static size_t constexpr verbatimMaxOutputSlots = 100; + std::set builtinFunctionNames() const; + protected: static bool constexpr isVerbatimHandle(BuiltinHandle const& _handle) { return _handle.id < verbatimIDOffset; } static BuiltinFunctionForEVM createVerbatimFunctionFromHandle(BuiltinHandle const& _handle); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index da115f0d4a5b..1b1040074dd9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -145,6 +145,8 @@ set(libyul_sources libyul/ControlFlowSideEffectsTest.h libyul/EVMCodeTransformTest.cpp libyul/EVMCodeTransformTest.h + libyul/EVMDialectCompatibility.h + libyul/EVMDialectCompatibility.cpp libyul/FunctionSideEffects.cpp libyul/FunctionSideEffects.h libyul/Inliner.cpp diff --git a/test/libyul/EVMDialectCompatibility.cpp b/test/libyul/EVMDialectCompatibility.cpp new file mode 100644 index 000000000000..d2c14ed6b352 --- /dev/null +++ b/test/libyul/EVMDialectCompatibility.cpp @@ -0,0 +1,82 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include + +namespace bdata = boost::unit_test::data; + +using namespace solidity; +using namespace solidity::yul; +using namespace solidity::yul::test; + +BOOST_AUTO_TEST_SUITE(EVMDialectCompatibility) + +BOOST_DATA_TEST_CASE( + builtin_function_handle_compatibility_non_eof, + bdata::make(generateEVMDialectConfigurationsToTest(std::nullopt)), + evmDialectConfigurationToTest +) +{ + auto const& dialectToTestAgainst = evmDialectConfigurationToTest.dialect(); + // no object access for current + { + auto const& currentDialect = EVMDialect::strictAssemblyForEVM({}, std::nullopt); + for (auto const& builtinFunctionName: currentDialect.builtinFunctionNames()) + requireBuiltinCompatibility(currentDialect, dialectToTestAgainst, builtinFunctionName); + } + // object access for current + { + auto const& currentDialect = EVMDialect::strictAssemblyForEVMObjects({}, std::nullopt); + for (auto const& builtinFunctionName: currentDialect.builtinFunctionNames()) + requireBuiltinCompatibility(currentDialect, dialectToTestAgainst, builtinFunctionName); + } +} + +BOOST_DATA_TEST_CASE( + builtin_function_handle_compatibility_eof, + bdata::monomorphic::grid( + bdata::make(generateEVMDialectConfigurationsToTest(std::nullopt)) + bdata::make(generateEVMDialectConfigurationsToTest(1)), + bdata::make({false, true}) + ), + evmDialectConfigurationToTest, + withEOF +) +{ + auto const& dialectToTestAgainst = evmDialectConfigurationToTest.dialect(); + langutil::EVMVersion latestEVMVersion = langutil::EVMVersion::allVersions().back(); + std::optional eofVersion = std::nullopt; + if (withEOF) + eofVersion = 1; + // no object access for latest + { + auto const& latestDialect = EVMDialect::strictAssemblyForEVM(latestEVMVersion, eofVersion); + for (auto const& builtinFunctionName: latestDialect.builtinFunctionNames()) + requireBuiltinCompatibility(latestDialect, dialectToTestAgainst, builtinFunctionName); + } + // object access for latest + { + auto const& latestDialect = EVMDialect::strictAssemblyForEVMObjects(latestEVMVersion, eofVersion); + for (auto const& builtinFunctionName: latestDialect.builtinFunctionNames()) + requireBuiltinCompatibility(latestDialect, dialectToTestAgainst, builtinFunctionName); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libyul/EVMDialectCompatibility.h b/test/libyul/EVMDialectCompatibility.h new file mode 100644 index 000000000000..1824821b2bb4 --- /dev/null +++ b/test/libyul/EVMDialectCompatibility.h @@ -0,0 +1,78 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include + +#include + +#include +#include +#include + + +namespace solidity::yul::test +{ + +inline void requireBuiltinCompatibility(EVMDialect const& _dialect1, EVMDialect const& _dialect2, std::string_view const _builtin) +{ + if (auto const currentHandle = _dialect1.findBuiltin(_builtin)) + if (auto const handle = _dialect2.findBuiltin(_builtin)) + BOOST_REQUIRE_MESSAGE( + *handle == *currentHandle, + fmt::format("Failed for \"{}\" with IDs {} =/= {}.", _builtin, currentHandle->id, handle->id) + ); +} + +struct EVMDialectConfigurationToTest +{ + EVMDialect const& dialect() const + { + return objectAccess ? EVMDialect::strictAssemblyForEVMObjects(evmVersion, eofVersion) : EVMDialect::strictAssemblyForEVM(evmVersion, eofVersion); + } + + friend std::ostream& operator<<(std::ostream& _out, EVMDialectConfigurationToTest const& _config) + { + _out << fmt::format( + "EVMConfigurationToTest[{}, eof={}, objectAccess={}]", + _config.evmVersion.name(), + _config.eofVersion.has_value() ? std::to_string(*_config.eofVersion) : "null", + _config.objectAccess + ); + return _out; + } + + langutil::EVMVersion evmVersion; + std::optional eofVersion; + bool objectAccess; +}; + +inline std::vector generateEVMDialectConfigurationsToTest(std::optional _eofVersion) +{ + std::vector configs; + for (bool providesObjectAccess: {false, true}) + for (auto evmVersion: langutil::EVMVersion::allVersions()) + if (!_eofVersion || evmVersion >= langutil::EVMVersion::firstWithEOF()) + configs.push_back(EVMDialectConfigurationToTest{evmVersion, _eofVersion, providesObjectAccess}); + return configs; +} + +}