-
Notifications
You must be signed in to change notification settings - Fork 6k
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
Redundant store eliminator limited #12672
Conversation
6d5a0c5
to
2338228
Compare
Hope this new PR will also help with this issue: #12460 (comment) |
In combination with #12206 it does address the issue, but it might also need some more careful tuning of the inlining parameters to make this always work. |
e8a3876
to
768d57d
Compare
d6282f6
to
d0d4bca
Compare
4a3ca5d
to
c7a82d2
Compare
if (!funCall) | ||
return; |
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.
if (!funCall) | |
return; | |
yulAssert(funCall); |
?
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 reverted again now - but well, it also doesn't really matter too much.
c7a82d2
to
a25cd94
Compare
00b3f80
to
3441409
Compare
if (_op1.start && _op2.start) | ||
return knowledge.knownToBeDifferent(*_op1.start, *_op2.start); |
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.
Since we did set the lengths to constant 1 for these, maybe we should assert that they're there and that they are one here.
Rebased. |
25a3df9
to
90fa9dd
Compare
Squashed. |
90fa9dd
to
8acd2cc
Compare
// Due to wrapping arithmetic, the below code can have "false positives". | ||
// In the situation that the subtraction for computing "diff" wraps, | ||
// we can return "unrelated" although the writes are actually related. | ||
// In this case, 2.start and 1.start will both be >= largeNumber | ||
// (see test/formal/redundant_store_unrelated.py). | ||
// This means, both operations revert. | ||
// Because of these false positives, this optimizer routine removes write operations | ||
// where all subsequent read operations are unrelated or will revert due to high memory | ||
// access. Since this second class of read operations will never return a value, | ||
// it is irrelevant what value they return, so the write can be safely removed. |
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 wonder whether we should document this somewhere more prominently...
Also: did this have a significant impact beyond the literal-constant-only version? If not, I tend to play it safe and revert to that one... but if it has some effect, it's probably fine to go for it...
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 was able to optimize this out: https://github.com/ethereum/solidity/pull/12672/files#diff-454ca1e7741b290c37ead677fb52b645bd9c18325aa1aa5d023df0b926b2e1b7R16
I'm not sure what the impact in practice was. Of course, "known unrelated" is always difficult because we need the code to also terminate in the same function in order to say something about memory.
Still, if we want to improve this with the linear solver, we need exactly the same reasoning here.
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 just checked, that is the only change for isoltest.
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 would have hoped that the linear solver could actually tell us stuff like if _op1.start <= _op2.start
and then the problem just vanishes...
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 it might not be able to do that accurately either if either of them could ever have overflowed...
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 still seems a bit weird to me to have the optimizer operate under conflicting assumptions - we say both "large memory reads don't return values", but we also allow them to be load-resolved, so we also say "we can assume any memory read to return a value"...
I don't see any issue due to this in practice (yet?), but it will never be formally correct like this at least :-)...
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.
And I actually wouldn't be surprised there was a case, in which this actually happens:
- a store to high memory location A
- a store to high memory location B
- a read from high memory location A
in which B is spuriously unrelated to A... then the write to B would be removed and we may resolve the read from A, after which we can potentially further remove the write to A...
Even if that doesn't happen the way things are set up right now for any concrete values of A and B, we'd have the constant danger of this happening, once we extend the optimizer anywhere...
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.
Ok, the interaction with the load resolver is actually a good point.
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.
To do this stuff properly, maybe there is no way around proving that the free memory pointer and arithmetic on it never overflows... but that's probably always going to be tricky (especially across functions calls and such)...
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.
On the other hand: any code we generate in which the free memory pointer does overflow is ill-defined anyways (since we may then remove all kinds of memory writes and reads preventing actual out-of-gas, but have a write after the memory pointer overflow mess with the scratch space or the free memory pointer itself)...
5fc9b74
to
5e0f3f1
Compare
The termination conditions are still wrong... |
20872eb
to
f316b77
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.
LGTM apart from remaining minor coding style comments.
e6890fe
to
4f02be1
Compare
if ( | ||
(length1 && *length1 <= 32) && | ||
(length2 && *length2 <= 32) && | ||
knowledge.knownToBeDifferentByAtLeast32(*_op1.start, *_op2.start) |
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.
Hm... this could be
knownToBeDifferentByAtLeast(*_op.start, *_op2.start, max(*length1, *length2))
thinking of two mstore8
s...
And there's nothing really that restricts this case to only <= 32
, is there?
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.
Probably <= 2^255
is the only boundary, if any?
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.
That is true, but I'm not sure it is worth it delaying the PR :)
The fuzzer reported the following trace divergence (mstore optimized out and hence the keccak256 call returns a different value)
|
@bshastry Actually, the
after which the and it's actually correct - we know the value stored in memory at So it may be a false positive, but I'm not sure where it's coming from... maybe the interpreter (that's what you use as reference, right?) calculates the keccak hash incorrectly? |
Ah, the yul interpreter doesn't interpret reads and writes from high memory addresses, does it? And because of that |
Not sure if there's good reason to restrict the interpreter to only low memory accesses... maybe it should be a setting... |
No description provided.