Skip to content
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

Merged
merged 1 commit into from
Mar 10, 2022
Merged

Conversation

chriseth
Copy link
Contributor

No description provided.

@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch 2 times, most recently from 6d5a0c5 to 2338228 Compare February 15, 2022 13:07
@k06a
Copy link

k06a commented Feb 15, 2022

Hope this new PR will also help with this issue: #12460 (comment)

@chriseth
Copy link
Contributor Author

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.

@chriseth chriseth mentioned this pull request Feb 17, 2022
@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch from e8a3876 to 768d57d Compare February 17, 2022 10:46
@chriseth chriseth changed the base branch from develop to extendSideEffects February 17, 2022 10:46
@chriseth chriseth added the has dependencies The PR depends on other PRs that must be merged first label Feb 17, 2022
@chriseth chriseth force-pushed the extendSideEffects branch 2 times, most recently from d6282f6 to d0d4bca Compare March 1, 2022 15:06
Base automatically changed from extendSideEffects to develop March 1, 2022 22:48
@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch 2 times, most recently from 4a3ca5d to c7a82d2 Compare March 2, 2022 08:58
@ekpyron ekpyron removed the has dependencies The PR depends on other PRs that must be merged first label Mar 2, 2022
Comment on lines 130 to 131
if (!funCall)
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!funCall)
return;
yulAssert(funCall);

?

Copy link
Member

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.

@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch from c7a82d2 to a25cd94 Compare March 2, 2022 09:56
@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch from 00b3f80 to 3441409 Compare March 2, 2022 10:32
Comment on lines 270 to 255
if (_op1.start && _op2.start)
return knowledge.knownToBeDifferent(*_op1.start, *_op2.start);
Copy link
Member

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.

@chriseth
Copy link
Contributor Author

chriseth commented Mar 7, 2022

Rebased.

@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch from 25a3df9 to 90fa9dd Compare March 7, 2022 18:37
@chriseth
Copy link
Contributor Author

chriseth commented Mar 9, 2022

Squashed.

@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch from 90fa9dd to 8acd2cc Compare March 9, 2022 16:07
Comment on lines 268 to 277
// 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.
Copy link
Member

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...

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

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...

Copy link
Member

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...

Copy link
Member

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 :-)...

Copy link
Member

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...

Copy link
Contributor Author

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.

Copy link
Member

@ekpyron ekpyron Mar 10, 2022

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)...

Copy link
Member

@ekpyron ekpyron Mar 10, 2022

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)...

@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch 3 times, most recently from 5fc9b74 to 5e0f3f1 Compare March 10, 2022 10:01
@chriseth
Copy link
Contributor Author

The termination conditions are still wrong...

@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch 2 times, most recently from 20872eb to f316b77 Compare March 10, 2022 16:41
Copy link
Member

@ekpyron ekpyron left a 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.

@chriseth chriseth force-pushed the redundantStoreEliminatorLimited branch from e6890fe to 4f02be1 Compare March 10, 2022 17:25
if (
(length1 && *length1 <= 32) &&
(length2 && *length2 <= 32) &&
knowledge.knownToBeDifferentByAtLeast32(*_op1.start, *_op2.start)
Copy link
Member

@ekpyron ekpyron Mar 10, 2022

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 mstore8s...

And there's nothing really that restricts this case to only <= 32, is there?

Copy link
Member

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?

Copy link
Contributor Author

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 :)

@chriseth chriseth merged commit 4263b89 into develop Mar 10, 2022
@chriseth chriseth deleted the redundantStoreEliminatorLimited branch March 10, 2022 18:45
@bshastry
Copy link
Contributor

The fuzzer reported the following trace divergence (mstore optimized out and hence the keccak256 call returns a different value)

{
  mstore(add("z",iszero(0x9f)), 0xa0)
  sstore(0xa1, 0xff)
  sstore(0x100, 0x101)
  sstore(keccak256("z",0x10), 0x100)
}

@ekpyron
Copy link
Member

ekpyron commented Mar 11, 2022

@bshastry Actually, the keccak256 seems to be evaluated even before this PR... so this resolves to

{
            mstore("z", 0xa0)
            sstore(0xa1, 0xff)
            let _1 := 0x100
            sstore(_1, 0x101)
            sstore(110620294328144418057589324861608220015688365608948720310623173341503153578932, _1)
}

after which the mstore is really redundant and can be removed...

and it's actually correct - we know the value stored in memory at "z" is 0x00...00a0, so the first 0x10 bytes are all zero and the big constant should be the keccak256 hash of 16 zeroes...

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?

@ekpyron
Copy link
Member

ekpyron commented Mar 11, 2022

Ah, the yul interpreter doesn't interpret reads and writes from high memory addresses, does it?
EVMInstructionInterpreter::accessMemory looks like it only allows access up to numeric_limits<size_t>::max() - 0xffff...

And because of that EVMInstructionInterpreter::eval returns the bogus return u256("0x1234cafe1234cafe1234cafe") + arg[0]; in L185 - and won't get the keccak value right... I think that's it...

@ekpyron
Copy link
Member

ekpyron commented Mar 11, 2022

Not sure if there's good reason to restrict the interpreter to only low memory accesses... maybe it should be a setting...

Changelog.md Outdated Show resolved Hide resolved
libevmasm/SemanticInformation.cpp Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants