argotorg / argotorg/solidity

do-while results in redundant branching instructions when compiling via IR

Open
#15,442 3 comments 0 reactions 0 assignees View on GitHub
low impact medium effort nice to have optimizer
Dominant language
C++
Stars
25.7k
Forks
6.2k
Avg merge
2d 19h
Merged PRs (30d)
29

Description

## Description

When using a do-while control flow statement such as in the following code:
```solidity
contract Sum {
function sum(uint256[] calldata nums) public pure returns (uint256 total) {
if (nums.length == 0) return 0;
uint256 i = 0;
do {
assembly ("memory-safe") {
total := add(calldataload(add(nums.offset, shl(5, i))), total)
i := add(i, 1)
}
} while (i < nums.length);
}
}
```

I expect solidity to generate the relatively straight-forward & maximally efficient assembly:

```
tag_doWhileBodyEntry:


tag_doWhileBodyEntry
jumpi
```

This is the case when compiling with the legacy pipeline but using the IR pipeline gives you roughly the following structure:

```
0x01
tag_doWhileEntry:
iszero
tag_whileCondition
jumpi
tag_doWhileBody:

0x00
tag_doWhileEntry
jump
tag_whileCondition:

tag_doWhileBody
jumpi
```

This structure, while correct is not efficient. Digging deeper the origin of this structure becomes more apparent when looking at the generated IR:

```yul
function fun_sum(var_nums_offset, var_nums_length) -> var_total {
var_total := 0
if iszero(var_nums_length) {
var_total := 0
leave
}
let var_i := 0
let _1 := 1
for { } 1 { } {
if iszero(_1) {
if iszero(lt(var_i, var_nums_length)) { break }
}
_1 := 0
var_total := add(calldataload(add(var_nums_offset, shl(5, var_i))), var_total)
var_i := add(var_i, 1)
}
}

```

## Environment

- Compiler version: 0.8.27
- Target EVM version (as per compiler settings): cancun
- Framework/IDE (e.g. Truffle or Remix): foundry
- EVM execution environment / backend / blockchain client: viaIR
- Operating system: macOS

## Steps to Reproduce

Compile the following contract with viaIR

```solidity
contract Sum {
function sum(uint256[] calldata nums) public pure returns (uint256 total) {
if (nums.length == 0) return 0;
uint256 i = 0;
do {
assembly ("memory-safe") {
total := add(calldataload(add(nums.offset, shl(5, i))), total)
i := add(i, 1)
}
} while (i < nums.length);
}
}
```

Contributor guide

Open the contributing guide

Research direction

Start by compiling the provided Sum contract with viaIR and compare its generated assembly and Yul with the legacy pipeline. Trace how the do-while loop is lowered in the IR pipeline; done means preserving behavior while eliminating the redundant branching instructions shown in the report.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, solidity
Domain
compilers, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.