Skip to content

Commit 8a7d619

Browse files
committed
EVMDialect: add test that enforces builtin handle compatibility
Between current default dialect as well as latest dialect and all other dialects.
1 parent 87acbd6 commit 8a7d619

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

libyul/backends/evm/EVMDialect.h

+8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ class Object;
4545
* Yul dialect for EVM as a backend.
4646
* The main difference is that the builtin functions take an AbstractAssembly for the
4747
* code generation.
48+
*
49+
* Builtins are defined so that their handles stay compatible over different dialect flavors - be it with/without
50+
* object access, with/without EOF, different versions. It may be, of course, that these builtins are no longer defined.
51+
* The ones that _are_ defined, though, remain the same.
4852
*/
4953
class EVMDialect: public Dialect
5054
{
@@ -81,6 +85,8 @@ class EVMDialect: public Dialect
8185
AuxiliaryBuiltinHandles const& auxiliaryBuiltinHandles() const { return m_auxiliaryBuiltinHandles; }
8286

8387
static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _evmVersion, std::optional<uint8_t> _eofVersion);
88+
/// Builtins with and without object access are compatible, i.e., builtin handles without object access are not
89+
/// invalidated.
8490
static EVMDialect const& strictAssemblyForEVMObjects(langutil::EVMVersion _evmVersion, std::optional<uint8_t> _eofVersion);
8591

8692
langutil::EVMVersion evmVersion() const { return m_evmVersion; }
@@ -92,6 +98,8 @@ class EVMDialect: public Dialect
9298
static size_t constexpr verbatimMaxInputSlots = 100;
9399
static size_t constexpr verbatimMaxOutputSlots = 100;
94100

101+
std::set<std::string_view> builtinFunctionNames() const;
102+
95103
protected:
96104
static bool constexpr isVerbatimHandle(BuiltinHandle const& _handle) { return _handle.id < verbatimIDOffset; }
97105
static BuiltinFunctionForEVM createVerbatimFunctionFromHandle(BuiltinHandle const& _handle);

test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ set(libyul_sources
149149
libyul/ControlFlowSideEffectsTest.h
150150
libyul/EVMCodeTransformTest.cpp
151151
libyul/EVMCodeTransformTest.h
152+
libyul/EVMDialectCompatibility.cpp
152153
libyul/FunctionSideEffects.cpp
153154
libyul/FunctionSideEffects.h
154155
libyul/Inliner.cpp
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#include <libyul/backends/evm/EVMDialect.h>
20+
21+
#include <libsolidity/util/SoltestErrors.h>
22+
23+
#include <boost/test/data/test_case.hpp>
24+
#include <boost/test/data/monomorphic.hpp>
25+
#include <boost/test/unit_test.hpp>
26+
27+
#include <range/v3/view/zip.hpp>
28+
#include <range/v3/range_concepts.hpp>
29+
30+
#include <fmt/format.h>
31+
32+
#include <cstdint>
33+
#include <optional>
34+
#include <vector>
35+
36+
namespace bdata = boost::unit_test::data;
37+
38+
using namespace solidity;
39+
using namespace solidity::yul;
40+
41+
namespace
42+
{
43+
44+
struct EVMDialectConfigurationToTest
45+
{
46+
EVMDialect const& dialect() const
47+
{
48+
return objectAccess ? EVMDialect::strictAssemblyForEVMObjects(evmVersion, eofVersion) : EVMDialect::strictAssemblyForEVM(evmVersion, eofVersion);
49+
}
50+
51+
friend std::ostream& operator<<(std::ostream& _out, EVMDialectConfigurationToTest const& _config)
52+
{
53+
_out << fmt::format(
54+
"EVMConfigurationToTest[{}, eof={}, objectAccess={}]",
55+
_config.evmVersion.name(),
56+
_config.eofVersion.has_value() ? std::to_string(*_config.eofVersion) : "null",
57+
_config.objectAccess
58+
);
59+
return _out;
60+
}
61+
62+
langutil::EVMVersion evmVersion;
63+
std::optional<uint8_t> eofVersion;
64+
bool objectAccess;
65+
};
66+
67+
template<ranges::range EVMVersionCollection>
68+
std::vector<EVMDialectConfigurationToTest> generateConfigs(EVMVersionCollection const& _evmVersions, std::vector<bool> const& _objectAccess = {false, true})
69+
{
70+
std::vector<EVMDialectConfigurationToTest> configs;
71+
for (bool const objectAccess: _objectAccess)
72+
for (auto const& eofVersion: langutil::EVMVersion::allEOFVersions())
73+
for (auto const& evmVersion: _evmVersions)
74+
if (!eofVersion || evmVersion.supportsEOF())
75+
configs.push_back(EVMDialectConfigurationToTest{evmVersion, eofVersion, objectAccess});
76+
77+
return configs;
78+
}
79+
}
80+
81+
BOOST_AUTO_TEST_SUITE(EVMDialectCompatibility)
82+
83+
/// Test for both current and latest (source) EVM dialect that for all other (target) dialects and all builtins in the
84+
/// source dialect, if the builtin exists for both source and target, they have the same handle.
85+
/// Note: The comparison is packed into a single BOOST_REQUIRE to avoid massive amounts of output on cout.
86+
BOOST_DATA_TEST_CASE(
87+
builtin_function_handle_compatibility,
88+
bdata::monomorphic::grid(
89+
bdata::make(generateConfigs(std::array{langutil::EVMVersion::current(), langutil::EVMVersion::allVersions().back()})),
90+
bdata::make(generateConfigs(langutil::EVMVersion::allVersions()))
91+
),
92+
sourceDialectConfiguration,
93+
evmDialectConfigurationToTest
94+
)
95+
{
96+
auto const& sourceDialect = sourceDialectConfiguration.dialect();
97+
auto const& dialectToTestAgainst = evmDialectConfigurationToTest.dialect();
98+
99+
std::set<std::string_view> const builtinNames = sourceDialect.builtinFunctionNames();
100+
std::vector<BuiltinHandle> sourceHandles;
101+
sourceHandles.reserve(builtinNames.size());
102+
std::vector<std::optional<BuiltinHandle>> testHandles;
103+
testHandles.reserve(builtinNames.size());
104+
105+
for (auto const& builtinFunctionName: builtinNames)
106+
{
107+
std::optional<BuiltinHandle> sourceHandle = sourceDialect.findBuiltin(builtinFunctionName);
108+
soltestAssert(sourceHandle.has_value());
109+
sourceHandles.push_back(*sourceHandle);
110+
testHandles.push_back(dialectToTestAgainst.findBuiltin(builtinFunctionName));
111+
}
112+
113+
BOOST_REQUIRE([&]() -> boost::test_tools::predicate_result
114+
{
115+
boost::test_tools::predicate_result result{true};
116+
for (auto const& [name, sourceBuiltin, testBuiltin]: ranges::views::zip(builtinNames, sourceHandles, testHandles))
117+
if (testBuiltin && sourceBuiltin != *testBuiltin)
118+
{
119+
result = false;
120+
result.message() << fmt::format("Builtin \"{}\" had a mismatch of builtin handles: {} =/= {}.", name, sourceBuiltin.id, testBuiltin->id);
121+
}
122+
return result;
123+
}());
124+
}
125+
126+
/// Test that for all inline-dialects the corresponding object dialect contains all inline-dialect builtins and they
127+
/// have the same handle.
128+
BOOST_DATA_TEST_CASE(
129+
builtin_inline_to_object_compatibility,
130+
bdata::make(generateConfigs(langutil::EVMVersion::allVersions(), {false})),
131+
configToTest
132+
)
133+
{
134+
auto const& dialect = EVMDialect::strictAssemblyForEVM(configToTest.evmVersion, configToTest.eofVersion);
135+
auto const& dialectForObjects = EVMDialect::strictAssemblyForEVMObjects(configToTest.evmVersion, configToTest.eofVersion);
136+
137+
std::set<std::string_view> const inlineBuiltinNames = dialect.builtinFunctionNames();
138+
139+
std::vector<BuiltinHandle> inlineHandles;
140+
inlineHandles.reserve(inlineBuiltinNames.size());
141+
std::vector<std::optional<BuiltinHandle>> objectHandles;
142+
objectHandles.reserve(inlineBuiltinNames.size());
143+
144+
for (auto const& builtinFunctionName: inlineBuiltinNames)
145+
{
146+
std::optional<BuiltinHandle> handle = dialect.findBuiltin(builtinFunctionName);
147+
soltestAssert(handle.has_value());
148+
inlineHandles.push_back(*handle);
149+
objectHandles.push_back(dialectForObjects.findBuiltin(builtinFunctionName));
150+
}
151+
152+
BOOST_REQUIRE([&]() -> boost::test_tools::predicate_result
153+
{
154+
boost::test_tools::predicate_result result{true};
155+
for (auto const& [name, inlineHandle, objectHandle]: ranges::views::zip(inlineBuiltinNames, inlineHandles, objectHandles))
156+
if (!objectHandle || inlineHandle != *objectHandle)
157+
{
158+
result = false;
159+
result.message()
160+
<< fmt::format("Builtin \"{}\" had a mismatch of builtin handles: {} != ", name, inlineHandle.id)
161+
<< (objectHandle.has_value() ? std::to_string(objectHandle->id) : "null");
162+
}
163+
return result;
164+
}());
165+
}
166+
167+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)