Open Subway2023 opened 2 weeks ago
The evaluation order in Yul is right-to-left (inherited from the argument order on stack on the EVM), so in f(g(), mload(0))
, mload(0)
is evaluated before g()
. Hence also, as seen in the storage trace, the value of r
in both cases is the same.
The Yul optimizer inlines both f
and g
, after which it can prove that memory offset 0
is never read after the mstore(0,4)
. Hence it can validly remove it - memory is local to the call frame, so if it is not read again, it's semantically unobservable and thus redundant. So yes, the mstore(0,4)
is optimized away here, but that's valid.
The evaluation order in Yul is right-to-left (inherited from the argument order on stack on the EVM), so in
f(g(), mload(0))
,mload(0)
is evaluated beforeg()
. Hence also, as seen in the storage trace, the value ofr
in both cases is the same.The Yul optimizer inlines both
f
andg
, after which it can prove that memory offset0
is never read after themstore(0,4)
. Hence it can validly remove it - memory is local to the call frame, so if it is not read again, it's semantically unobservable and thus redundant. So yes, themstore(0,4)
is optimized away here, but that's valid.
I don't think the bug is in f(g(), mload(0)) or mload(0), because I replaced it with let a := g(), and the inconsistency still persists.
{
function g() -> y {
y := mload(0)
mstore(0, 4)
}
let i := 0
for {} lt(i, 2) { i := add(i, 1) }
{
let x := 1337
if lt(i, 1) {
x := 42
continue
}
mstore(0, x)
}
let a:=g()
sstore(0, mload(0))
}
The evaluation order in Yul is right-to-left (inherited from the argument order on stack on the EVM), so in
f(g(), mload(0))
,mload(0)
is evaluated beforeg()
. Hence also, as seen in the storage trace, the value ofr
in both cases is the same. The Yul optimizer inlines bothf
andg
, after which it can prove that memory offset0
is never read after themstore(0,4)
. Hence it can validly remove it - memory is local to the call frame, so if it is not read again, it's semantically unobservable and thus redundant. So yes, themstore(0,4)
is optimized away here, but that's valid.I don't think the bug is in f(g(), mload(0)) or mload(0), because I replaced it with let a := g(), and the inconsistency still persists.
{ function g() -> y { y := mload(0) mstore(0, 4) } let i := 0 for {} lt(i, 2) { i := add(i, 1) } { let x := 1337 if lt(i, 1) { x := 42 continue } mstore(0, x) } let a:=g() sstore(0, mload(0)) }
In this new snippet, the optimizer will resolve the mload(0)
to the value 4
, since it knows that's necessarily what's stored at memory offset zero - with that the mload(0)
within the sstore
vanishes - which leaves an mstore(0, 4)
that is no longer read from and thereby also redundant and can subsequently be removed.
So it's still valid optimizations. The yul optimizer generally doesn't make any guarantees about the state of memory at the end of the call frame (since memory doesn't persist) - so memory writes are only preserved if they can possibly be read from by any subsequent instruction/code path.
If, for some reason like debugging, you don't want these memory writes to be removed, you can provide a custom Yul optimizer sequence not involving the UnusedStoreEliminator (in the sequence denoted as S
). (For example https://soliditylang.org/blog/2024/05/21/solidity-0.8.26-release-announcement contains some explanation of the default optimizer sequence and how to change it.) But the cases so far appear to be valid optimizations and don't seem to constitute optimizer bugs.
Environment
Steps to Reproduce
Get Bin
normal
optimize
Run in EVM
normal
optimize
Execution result analysis
normal
optimize
The data at position 0 is different. The suspected cause is that mstore(0, 4) was incorrectly optimized away.