Skip to content

autosetup: try the optimizer under legacy codegen before reaching for via-ir - #180

Open
shellygr wants to merge 2 commits into
masterfrom
shelly/stack-too-deep-optimizer-first
Open

autosetup: try the optimizer under legacy codegen before reaching for via-ir#180
shellygr wants to merge 2 commits into
masterfrom
shelly/stack-too-deep-optimizer-first

Conversation

@shellygr

Copy link
Copy Markdown
Contributor

What was wrong

Solc's stack-too-deep message names two remedies: compile with --via-ir while enabling the
optimizer
. The ladder acted on the first half only.

stack_too_deep_via_ir (compilation_workarounds.py:385) answers CompilerError: Stack too deep by
switching via-ir on for the failing contract. The optimizer rung sits behind
_detect_yul_exception_stack_too_deep, whose regex requires a literal YulException: prefix, and
only the via-ir pipeline emits that. So the loop tried legacy, then via-ir with the optimizer, and
never tried legacy with the optimizer.

That matters because via-ir is not a free substitution. It inlines internal functions and takes the
CVL internal summaries with them, which is why build_systems/base.py:137 refuses to inherit a
project's declared via_ir in the first place.

What changed

Three rungs now precede via-ir on a legacy stack-too-deep:

  1. stack_too_deep_optimizer enables the optimizer, keeping legacy codegen.
  2. stack_too_deep_autofinder handles the case where only the autofinder-instrumented compile is
    over the stack limit. The contracts themselves compile; accepting the finder fallback for those
    files costs local-variable finders there and keeps every contract on legacy codegen.
  3. stack_too_deep_via_ir, as before, per contract, and only once the two above have been tried.

Per-contract via-ir also stops walking a large scene one compile at a time. Past ten contracts, or
when the project's own build config declares via-ir, the remaining contracts are switched together.
The declared value is still never inherited: it only says where the walk ends, and the optimizer
value emitted is ours.

The optimizer apply is shared with the Yul rung rather than duplicated, so Foundry's
compilation_restrictions map keeps its explicit runs values and the scalar is never written beside
the map.

Measurements

Three experiments ran before the code, in the fleet image, compilation analysis only.

before after
43-contract project ladder ends with via-ir on 1 contract, fails rc=0 in 7m40s, solc_via_ir_map all False
171-contract project ladder ends with via-ir on 7 contracts, fails optimizer then autofinder rung, no via-ir
105-contract project 18 accretion passes, 60-minute cap, no result scene-wide via-ir at our own solc_optimize 200, rc=0 in 13m32s

The 43-contract run went through the real ladder end to end, not a hand-built conf: exactly two rungs
fired, stack_too_deep_optimizer then stack_too_deep_autofinder, and certoraRun reported
all solc_via_ir_map values are set to False.

Two findings worth carrying forward:

  • Legacy plus optimizer is not enough on its own. With assert_autofinder_success: True, which is
    what setup_prover.py:409 emits, both projects still fail: 35 of 171 and 15 of 43 files fall back
    during autofinder instrumentation. That is why rung 2 exists, and it is a real cost, paid instead of
    the larger one of inlining every contract.
  • The declared optimizer_runs values (10,000,000 and 100,000) make no difference. 200 compiles the
    same scenes, so nothing needs to be inherited from the project.

Tests

pytest -m "not expensive": 978 passed, 11 skipped. pyright: 0 errors.

Five new cases pin the ladder: the optimizer fires first and sets no via-ir; via-ir follows and stays
scoped to the failing contract; the autofinder rung relaxes the assertion without touching codegen;
the scene flips past the threshold; a declared via_ir skips the walk. Three existing tests moved by
one compile each, since the ladder now spends a pass on the optimizer before via-ir. Their assertions
were tightened to the new sequence rather than loosened.

… via-ir

Solc's stack-too-deep advice names two things, "compile with --via-ir while enabling the
optimizer", and the ladder acted on the first half only. stack_too_deep_via_ir answered
CompilerError: Stack too deep by switching via-ir on for the failing contract, while the
optimizer rung sat behind a YulException regex that only the via-ir pipeline ever emits. So
legacy plus optimizer was unreachable, and projects that compile fine with the optimizer
alone were pushed onto via-ir, which inlines internal functions and takes the CVL internal
summaries with it.

Three rungs now precede via-ir on a legacy stack-too-deep: enable the optimizer; then, if
only the autofinder-instrumented compile is over the limit, accept the finder fallback for
those files; then per-contract via-ir as before. Measured on two corpus projects, the
optimizer alone compiles scenes of 171 and 43 contracts that the old ladder failed to
compile at all, in 554s and 153s, with no via-ir anywhere in the final conf.

Per-contract via-ir also no longer walks a large scene one compile at a time. Past ten
contracts, or when the project's own build config declares via-ir, the remaining contracts
are switched together. One corpus project spent 18 passes and hit a 60-minute cap doing
that walk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@shellygr
shellygr requested a review from jar-ben August 19, 2026 15:06
),
# The optimizer can clear the contract compile and still leave the
# autofinder-instrumented one over the stack limit, since instrumentation adds
# slots of its own. Accepting the fallback costs local-variable finders in the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is only an example. localvars can actually pass. or they can fail. same with internal summaries. it's not clear-cut that enabling via-ir kills both always.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude answers: Fixed. The comment now puts both sides as exposures rather than losses: falling back puts the local-variable finders at risk, via-ir puts the internal summaries at risk, and neither is certain, since a file can fall back and still give useful finders and an inlined contract can still be summarized. What orders the rungs is that the via-ir exposure is the wider one. 0fe823b

# enabling the optimizer" — the optimizer is what reclaims stack slots, and it
# does so under legacy codegen too. Enabling it alone keeps legacy codegen,
# which via-ir would replace: via-ir inlines internal functions and with them
# the internal summaries CVL applies, so it is the more expensive answer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a maybe, not a definite, but a risk we prefer to avoid

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude answers: Reworded. It now says via-ir inlines internal functions, "which can leave the internal summaries CVL applies with nothing to attach to", and that it does not always cost them but is a risk worth not taking when the optimizer alone may do. 0fe823b

them one at a time pays off, for the whole scene.

Per contract is the better answer while the count is small: every contract left on
legacy codegen keeps the internal-function summaries via-ir would inline away. But

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
legacy codegen keeps the internal-function summaries via-ir would inline away. But
legacy codegen keeps the internal-function summaries via-ir might inline away. But

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude answers: Applied verbatim. I also changed "the finder loss is already broad" two lines down to "the exposure is already broad", for the same reason. 0fe823b

Both comments claimed a certainty the code does not have. Inlining can leave an internal
summary with nothing to attach to, and a file that falls back during instrumentation can
still yield useful finders, but neither is guaranteed either way. What decides the order is
the width of the exposure, not a known loss.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants