Skip to content

Dead code remains in an unreachable code branch that contains a loop (legacy pipeline) #16007

Open
@madlabman

Description

@madlabman

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.

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

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:

solc --optimize --optimize-runs=250 --no-cbor-metadata --asm Test.sol

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions