Dead code remains in an unreachable code branch that contains a loop (legacy pipeline)
- Dominant language
- C++
- Stars
- 25.7k
- Forks
- 6.2k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 21
Description
## Description
Not a bug, but rather an issue. In the following example, it is known at compile time that the `else` branch is unreachable. If the loop is commented out, the legacy pipeline optimizer successfully eliminates the dead code; however, with the loop in place, the optimizer fails to do so.
```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;
contract Test {
function foo() external payable returns (uint256) {
if (true) {
return 0xbeef;
} else {
for (uint256 i; i < 1; ++i) {}
return 0xdead;
}
}
}
```
Assembly listing
Output of the command: `solc --optimize --optimize-runs=250 --no-cbor-metadata --asm Test.sol`:
```assembly
EVM assembly:
/* "Test.sol":756:979 contract Test {... */
mstore(0x40, 0x80)
callvalue
dup1
iszero
tag_1
jumpi
revert(0x00, 0x00)
tag_1:
pop
dataSize(sub_0)
dup1
dataOffset(sub_0)
0x00
codecopy
0x00
return
stop
sub_0: assembly {
/* "Test.sol":756:979 contract Test {... */
mstore(0x40, 0x80)
jumpi(tag_1, lt(calldatasize, 0x04))
shr(0xe0, calldataload(0x00))
dup1
0xc2985578
eq
tag_2
jumpi
tag_1:
revert(0x00, 0x00)
/* "Test.sol":776:977 function foo() external payable returns (uint256) {... */
tag_2:
/* "Test.sol":867:873 0xbeef */
0xbeef
/* "Test.sol":776:977 function foo() external payable returns (uint256) {... */
mload(0x40)
/* "#utility.yul":160:185 */
swap1
dup2
mstore
/* "#utility.yul":148:150 */
0x20
/* "#utility.yul":133:151 */
add
/* "Test.sol":776:977 function foo() external payable returns (uint256) {... */
mload(0x40)
dup1
swap2
sub
swap1
return
/* "Test.sol":904:934 for (uint256 i; i < 1; ++i) {} */
tag_10:
/* "Test.sol":924:925 1 */
0x01
/* "Test.sol":920:921 i */
dup2
/* "Test.sol":920:925 i < 1 */
lt
/* "Test.sol":904:934 for (uint256 i; i < 1; ++i) {} */
iszero
tag_11
jumpi
/* "Test.sol":927:930 ++i */
0x01
add
/* "Test.sol":904:934 for (uint256 i; i < 1; ++i) {} */
jump(tag_10)
tag_11:
pop
/* "Test.sol":954:960 0xdead */
0xdead
/* "Test.sol":947:960 return 0xdead */
swap1
pop
/* "Test.sol":776:977 function foo() external payable returns (uint256) {... */
swap1
jump // out
}
```
In the listing above the tags `tag_10` and `tag_11` are unreachable. However, `tag_10` (the loop) references both itself and `tag_11`, which likely prevents both tags from being removed by the legacy pipeline.
## Environment
- Compiler version: 0.8.24-0.8.29
- Compilation pipeline (legacy, IR, EOF): legacy
- Target EVM version (as per compiler settings): default
- Framework/IDE (e.g. Foundry, Hardhat, Remix): N/A
- EVM execution environment / backend / blockchain client: N/A
- Operating system: Linux
## Steps to Reproduce
Compile the example given at the beginning of the issue with following command:
```shell
solc --optimize --optimize-runs=250 --no-cbor-metadata --asm Test.sol
```
Contributor guide
Research direction
Reproduce the issue with Test.sol using the provided solc --optimize --optimize-runs=250 --no-cbor-metadata --asm command. Start by tracing the legacy pipeline's unreachable-code and dead-code elimination handling for the loop, and consider the work complete when the unreachable loop and return branch no longer appear in the assembly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, solidity
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100