argotorg / argotorg/solidity

Pathological compile time with `--via-ir --optimize-yul` (and `--via-ssa-cfg`) on long `((super).f)() | this.f() | …` diamond chain

Open
#16,746 0 comments 0 reactions 1 assignee Claimed by @r0qs View on GitHub
Dominant language
C++
Stars
25.7k
Forks
6.2k
Avg merge
1d 11h
Merged PRs (30d)
21

Description

## Summary

An ~8.9 KB / 28-line Solidity file with a four-contract diamond (`A`, `B`, `C` both `is A`, `D is B, C`) where `C::f()` returns a single 600-term `|`-chain mixing `((super).f)()`, `this.f()` and `2**100` causes `solc` to spend the entire 12 s budget compiling under `--via-ir --optimize-yul` and under `--experimental --via-ssa-cfg --optimize-yul`. Legacy codegen (with or without `--optimize`) finishes in <500 ms on the same input. Most of the time is in `yul::StackCompressor::run` → `StackLayoutGenerator::reportStackTooDeep` → `fillInJunk` / `createStackLayout`, with `Multiplicity::operator[]` as the hottest self-symbol.

Found via AFL fuzzing of `solc`.

## Environment

- Compiler: `0.8.36-develop.2026.5.18+commit.090bc8ff` (solidity submodule at `090bc8ff2`, top of `develop` after PR #16709), Release build.
- OS: `Linux 7.0.5-arch1-1`, AMD Ryzen 9 3900.

## Steps to reproduce

Reproducer (8911 bytes, 28 lines):
[`source.sol`](https://gist.github.com/msooseth/2786044ff1416522f7f1f73770ee0446).

Shape:

- `contract A` with `function f() public virtual returns (uint256)`.
- `contract B is A` overriding `f()` as `return ((super).f)() | 2;`.
- `contract C is A` overriding `f()` whose body is a single 600-term `|`-chain mixing **401** `((super).f)()`, **122** `this.f()` and **75** `2**100` operands (plus `4 | 4` at the tail).
- `contract D is B, C` with `function f() public override(B, C) returns (uint256) { return ((super).f)() | 8; }`.

```bash
# Times measured with a 12 s wall-clock cap per invocation:
solc --bin --evm-version osaka --via-ir source.sol # 1006 ms
solc --bin --evm-version osaka --via-ir --optimize source.sol # >12 s (timeout)
solc --bin --evm-version osaka source.sol # 53 ms
solc --bin --evm-version osaka --optimize source.sol # 332 ms
solc --bin --evm-version osaka --via-ir --optimize --experimental --via-ssa-cfg source.sol # >12 s (timeout)
```

So both `--via-ir --optimize` and `--via-ssa-cfg --optimize` exceed 12 s while the legacy backend (with or without `--optimize`) compiles in tens to hundreds of ms; `--via-ir` without `--optimize` also finishes in ~1 s.

## Profile

`perf` top symbols for `solc --bin --via-ir --optimize source.sol` (truncated at 12 s):

| Incl. % | Self % | Symbol |
|---|---|---|
| 99.42 | 0.00 | `frontend::CompilerStack::generateIR` |
| 98.97 | 0.00 | `yul::ObjectOptimizer::optimize` |
| 98.92 | 0.00 | `yul::OptimiserSuite::run` |
| **80.02** | 0.00 | **`yul::StackCompressor::run`** |
| 79.97 | 0.00 | `yul::StackLayoutGenerator::reportStackTooDeep` |
| 79.96 | 0.01 | `yul::StackLayoutGenerator::processEntryPoint` |
| 59.75 | 0.00 | `yul::StackLayoutGenerator::fillInJunk` |
| 58.09 | 3.34 | `yul::createStackLayout<…fillInJunk…>` |
| **37.68** | **25.33** | **`yul::Multiplicity::operator[]`** |
| 19.10 | 0.58 | `yul::StackLayoutGenerator::combineStack` |
| 18.86 | 0.00 | `yul::OptimiserSuite::runSequence` |
| 15.27 | 0.17 | `yul::ASTModifier::operator()(yul::Block&)` |
| 14.73 | 0.00 | `yul::DataFlowAnalyzer::operator()(yul::Block&)` |
| 11.95 | 0.06 | `yul::Multiplicity::~Multiplicity` |
| 10.62 | 10.48 | `yul::DataFlowAnalyzer::clearValues` |
| 6.49 | 6.49 | `yul::Multiplicity::at` |
| 5.34 | 0.01 | `yul::CommonSubexpressionEliminator::run` |

The `--via-ssa-cfg --optimize` profile is essentially identical (`StackCompressor` ~79.7 %, `reportStackTooDeep` ~79.6 %, `fillInJunk` ~59.8 %, `Multiplicity::operator[]` ~37.0 %, `clearValues` ~10.5 %), suggesting both pipelines hit the same `StackCompressor` / `StackLayoutGenerator` loop on this input.

Peak RSS at the 12 s cut-off: ~84 MB for both `--via-ir --optimize` and `--via-ssa-cfg --optimize`.

## Notes / possibly related

- #16704 — long chained library-call expressions; same "long single expression on one line" family but trips a YulAssertion (stack-shuffler 1000-iter cap) rather than a hang. NEW-E here is structurally different (it uses `super` + diamond inheritance, no library calls) and the failure mode is a hang in `StackCompressor`/`StackLayoutGenerator`, not an assert.
- #16745 — long `new C(N+1)()` constructor chain; same configs affected (`--via-ir --optimize` and `--via-ssa-cfg`) but a completely different source shape and a different hot path (`Object::summarizeStructure` / `AsmPrinter`), so this is filed separately.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.