Open Philogy opened 1 week ago
Yes, I can confirm that this is how it works, but unfortunately I don't think we can do much about it, at least in the short term. This a case where Yul not having jumps makes it impossible to implement it more efficiently in a general case.
Since Yul has no native do
...while
loop, we simulate it with a for
loop. Naively one could do it like this:
for {} true {} {
<body>
if iszero(<condition>) { break }
}
This should give you the assembly you want, however, it will break if you put continue
in the body. The codegen translates it to Yul's continue
, which will not jump to the right condition here. The legacy codegen does not have this problem, since it can use arbitrary jumps and just inserts a tag before the right condition, making continue
jump there.
To deal with this problem, we generate this instead, which is the reason for the less efficient assembly you get via IR:
let firstRun := 1
for {} true {} {
if iszero(firstRun) {
if iszero(<condition>) { break }
}
firstRun := 0
<body>
}
Since this affects only code that contains continue
, one option would be to detect its presence and generate the simpler code when it's not there. But I'm not convinced that the extra special-casing is worth it as it goes hard against the general philosophy of keeping the IR codegen simple. It is technically an option, as we did it allow one such case in the past (Optimizer > Unchecked Loop Increment), but this one does not seem to be on the same level in terms of how common it is and what impact it has.
We'd rather do things like this in the optimizer, and we may eventually do it, but we have many potential optimizations in the pipeline and this one does not seem like it would be high on the priority list.
but unfortunately I don't think we can do much about it
Why couldn't Yul be expanded/improved?
If the goal of Yul is to be simple to compile then forcing the addition of such specialized passes in the backend seems like it'd be going against that motto? Maybe wrong venue but I'd love to learn more about how the solc team thinks about language & compiler design.
Description
When using a do-while control flow statement such as in the following code:
I expect solidity to generate the relatively straight-forward & maximally efficient assembly:
This is the case when compiling with the legacy pipeline but using the IR pipeline gives you roughly the following structure:
This structure, while correct is not efficient. Digging deeper the origin of this structure becomes more apparent when looking at the generated IR:
Environment
Steps to Reproduce
Compile the following contract with viaIR