IR-based compilation increase contract size when an internal function is called many times

Description

When calling an internal function many times using viaIR=true, the contract size increase drastically compared to using viaIR=false

With viaIR=true

 ·····················|··············|·················
 |  Test              ·      14.445  ·  
 ·····················|··············|·················

With viaIR=false

  ·····················|··············|·················
 |  Test              ·       3.788  ·                │
 ·····················|··············|·················

Environment

  • Compiler version: 0.8.17
  • Target EVM version (as per compiler settings): default
  • Framework/IDE (e.g. Truffle or Remix): hardhat
  • Operating system: macOS

Steps to Reproduce

Here is an example and a repository to reproduce it
Repo

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Test {

    ERC20 internal token1;

    function _function1(address address_) internal {
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
        token1.transfer(address_, 1000 ether);
    }   

    function function1() public {
        _function1(msg.sender);
    }

    function function1v2(address address_) public {
        _function1(address_);
    }

    function function1v10() public {
        _function1(address(10));
    }
    function function1v11() public {
        _function1(address(11));
    }
    function function1v12() public {
        _function1(address(12));
    }
    function function1v13() public {
        _function1(address(13));
    }
    function function1v14() public {
        _function1(address(14));
    }
    function function1v15() public {
        _function1(address(15));
    }
    function function1v16() public {
        _function1(address(16));
    }
    function function1v17() public {
        _function1(address(17));
    }
    function function1v18() public {
        _function1(address(18));
    }
    function function1v19() public {
        _function1(address(19));
    }
}

I can confirm this - I haven’t verified it yet, but my first guess would be that this is due to Inlining heuristics for `FunctionSpecializer` · Issue #13787 · ethereum/solidity · GitHub causing excessive function duplication.

1 Like