-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
eof: Pass eofVersion
flag to EVMDialect
#15467
Conversation
Thank you for your contribution to the Solidity compiler! A team member will follow up shortly. If you haven't read our contributing guidelines and our review checklist before, please do it now, this makes the reviewing process and accepting your contribution smoother. If you have any questions or need our help, feel free to post them in the PR or talk to us directly on the #solidity-dev channel on Matrix. |
d2f6a89
to
60bfab0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found some problems, the biggest one being that keys for cached dialects and optimized IR do not include EOF version. I'm actually surprised that the former does not break anything in CI due to the instructions that were already marked as unavailable on EOF.
There's a few more things but overall it should all be easy to fix.
libsolidity/ast/ASTJsonImporter.cpp
Outdated
yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value()); | ||
std::optional<uint8_t> eofVersion; | ||
if (auto const it = _node.find("eofVersion"); it != _node.end()) | ||
eofVersion = it->get<uint8_t>(); | ||
astAssert(m_eofVersion == eofVersion, "Imported tree EOF version differs from configured EOF version!"); | ||
|
||
yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value(), eofVersion); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds support for reading the field, but it's useless if we never write it. You should add it to AST exporter as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, having an EVM version field here seemed odd to me at first, and looking at the PR where it was introduced, I actually found comments indicating that it shouldn't be there in the first place (#7153 (comment), #7153 (comment), #7153 (review)). It was kept though. I suspect that the motivation might have been that Yul parsing depends on the EVM version to some extent, so if you parse with some version, export and import with a different version, you may get wrong AST structure or bypass some checks for that version.
It's weird that this is done for each assembly block though rather than the AST as a whole. If we add some new version-dependent feature in the parser, there will be no such check and no one will remember to add it. Looks like it actually already happened with the experimental
pragma. We should consider changing that (it's an unrelated issue though, just FYI).
evmasm::Assembly assembly{solidity::test::CommonOptions::get().evmVersion(), false, std::nullopt, {}}; | ||
EthAssemblyAdapter adapter(assembly); | ||
EVMObjectCompiler::compile( | ||
*stack.parserResult(), | ||
adapter, | ||
EVMDialect::strictAssemblyForEVMObjects(EVMVersion{}), | ||
// TODO: Make sure that why we cannot pass here solidity::test::CommonOptions::get().evmVersion() and assembly.eofVersion() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably can and should. Unless it's not prepared to handle some versions, but I thought it was actually the optimized transform that had that limitation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But anyway, every test which is hard-coded to use the default EVM version is also fine to leave hardcoded for non-EOF for now.
It would be best to change it to the version from CommonOptions
where possible, but I'd consider that a separate enhancement (still worth doing, but optional - we're at least not making the test worse than it was). I'd just make sure that we properly update those that were already respecting versions from the CLI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, note that currently in CI we're always running tests without setting EOF version via command line. We should add that eventually in soltest_all.sh
, next to where we loop over EVM versions. But not sure if the implementation is ready to handle that already. May be running into some asserts on incomplete stuff.
test/libyul/ObjectParser.cpp
Outdated
@@ -112,7 +112,8 @@ std::tuple<std::optional<SourceNameMap>, ErrorList> tryGetSourceLocationMapping( | |||
|
|||
ErrorList errors; | |||
ErrorReporter reporter(errors); | |||
Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(EVMVersion::berlin()); | |||
// TODO: Add EOF support | |||
Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(EVMVersion::berlin(), std::nullopt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why this one is hard-coded to berlin
, but it should at least be the default version. I'd change it to that if it won't test fail:
Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(EVMVersion::berlin(), std::nullopt); | |
Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(EVMVersion{}, std::nullopt); |
Ideally we should later change it to properly use the version from CommonOptions
.
BTW: I'd put any changes related to EVM version at least in a separate commit, since they're really unrelated to introduction of EOF version.
// NOTE: This function uses the default EVM version instead of the currently selected one. | ||
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulName(_instructionIdentifier)); | ||
// TODO: Add EOF support | ||
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}, std::nullopt).builtin(YulName(_instructionIdentifier)); | ||
if (builtin && builtin->instruction.has_value()) | ||
return validateInstructions(builtin->instruction.value(), _location); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason we use hard-coded EVM version here is that historically this was always the one that had all possible builtins. With EOF this changes so I guess we'll have to use a different strategy for checking if the thing is a builtin - we should check both EOF and non-EOF dialect here and call validateInstruction()
for whichever has the instruction (or maybe even for both).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know if you want this to be added in this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would probably be best to still do it here. I wouldn't be against doing it later in another PR in principle, but we have so many of these TODOs now that I'm a bit afraid that things like this will just get forgotten and lost, only to surface as bugs much later :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. But we need test for this. Current SyntaxTest
does not support compileToEOF
flag. I would add it to EVMVersionRestrictedTestCase
constructor to have it available in all derived TaseCase classes. WDYT? If so here on additional PR? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has to be done in this PR because CI requires to cover all possible error codes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way sounds fine, but it's related so having it here is probably a good idea. And yeah, the error code - we have a whitelist for unused codes in scripts/error_codes.py
so you could temporarily add it there if necessary, but that's always a last resort :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though one thing to consider here - we have two different approaches to these settings, and you need to choose one:
EVMVersion
setting: you set a supported range of versions in the test, but a single isoltest run always executes only for one specific version (selected via--evm-version
). If that matches the range, the test runs. Otherwise it's skipped. To get full coverage we run soltest with all relevant EVM versions in CI (viasoltest_all.sh
).compileViaYul
setting in semantic tests: you select compilation via Yul or not or both. And each run does exactly that, running it twice if necessary. There's no CLI option.
The downside of (1) is that you'll have to run tests twice locally to test both EOF and legacy. It also adds another dimension to our test matrix. Currently we have two dimensions: optimizer on/off and EVM version. EOF version will be the third one, so you'll basically have to multiply the number of runs by two (though fortunately we can exclude older EVM versions from that, which lowers that number significantly).
Also, with (1) we end up unnecessarily running tests that are not affected by the flag multiple times, so it's only good for things that affect almost every part of the compiler.
The upside is that you get it supported almost for free in all test cases just by inheriting from EVMVersionRestrictedTestCase
, while compiling things multiple times for (2) requires adding special support for it in each case. This matters especially for the smaller test cases (i.e. the ones where you used the flag from CommonOptions
), where we definitely won't be going out of our way to make them use EOF, but it would be nice to have for more comprehensive coverage.
Since we already have that --eof-version
flag in soltest and the option is closely related to EVM version, I think it still makes the most sense to go with (1). And we'll need to add that additional run with EOF enabled anyway, because otherwise the tests using EOF versions from CommonOptions
now would still only be tested with legacy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, in short, my suggestion would be to extend EVMVersionRestrictedTestCase
to make it support a setting like EOFVersion
, which takes a list of comma-separated values: for now only legacy
and >=1
. The values should really be ranges, like for EVM, but since we only have one version, I'd not bother with that - I'd just make it support >=1
as a single, hard-coded value. If we ever get EOF v2, it will be easy to extend it by just changing how the value is parsed, without having to update it in all the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We agreed on addind EOF support for AsmAnalysis in a separated PR #15471
ccd9d66
to
0b4fdb9
Compare
0b4fdb9
to
dedc058
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved. Could use a few small tweaks (see comments below), but other than that it's good.
test/libsolidity/Assembly.cpp
Outdated
Parser parser( | ||
errorReporter, | ||
solidity::test::CommonOptions::get().evmVersion(), | ||
solidity::test::CommonOptions::get().eofVersion()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
solidity::test::CommonOptions::get().eofVersion()); | |
solidity::test::CommonOptions::get().eofVersion() | |
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same in all the dialect()
calls you modified in tests.
dedc058
to
8d8ac1c
Compare
case Instruction::STATICCALL: | ||
return hasStaticCall(); | ||
return !_eofVersion.has_value() && hasStaticCall(); | ||
case Instruction::SHL: | ||
case Instruction::SHR: | ||
case Instruction::SAR: | ||
return hasBitwiseShifting(); | ||
case Instruction::CREATE2: | ||
return hasCreate2(); | ||
return !_eofVersion.has_value() && hasCreate2(); | ||
case Instruction::EXTCODEHASH: | ||
return hasExtCodeHash(); | ||
return !_eofVersion.has_value() && hasExtCodeHash(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make the EOF version a parameter in hasStaticCall()
, hasCreate2()
and hasExtCodeHash()
. I wasn't sure about it back when I reviewed this PR, but now I saw this bit again in #15456 and I think it is necessary after all.
I actually just tried to do this myself as a quick tweak, but looks like it requires the EOF version to be passed into a few more places. And in some of those places it actually needs to be handled since they make assumptions based on the availability of these instructions. For example there are fallback to call
when staticcall
is not available, but on EOF they should result in extstaticcall
instead.
eofVersion
flag in theEVMDialect
class. This unfortunately introduces a lot of changes in the code base.