autosetup: try the optimizer under legacy codegen before reaching for via-ir - #180
autosetup: try the optimizer under legacy codegen before reaching for via-ir#180shellygr wants to merge 2 commits into
Conversation
… 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>
| ), | ||
| # 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
it's a maybe, not a definite, but a risk we prefer to avoid
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
| 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 |
There was a problem hiding this comment.
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>
What was wrong
Solc's stack-too-deep message names two remedies: compile with
--via-irwhile enabling theoptimizer. The ladder acted on the first half only.
stack_too_deep_via_ir(compilation_workarounds.py:385) answersCompilerError: Stack too deepbyswitching via-ir on for the failing contract. The optimizer rung sits behind
_detect_yul_exception_stack_too_deep, whose regex requires a literalYulException:prefix, andonly 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:137refuses to inherit aproject's declared
via_irin the first place.What changed
Three rungs now precede via-ir on a legacy stack-too-deep:
stack_too_deep_optimizerenables the optimizer, keeping legacy codegen.stack_too_deep_autofinderhandles the case where only the autofinder-instrumented compile isover 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.
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_restrictionsmap keeps its explicit runs values and the scalar is never written besidethe map.
Measurements
Three experiments ran before the code, in the fleet image, compilation analysis only.
solc_via_ir_mapall Falsesolc_optimize 200, rc=0 in 13m32sThe 43-contract run went through the real ladder end to end, not a hand-built conf: exactly two rungs
fired,
stack_too_deep_optimizerthenstack_too_deep_autofinder, and certoraRun reportedall solc_via_ir_map values are set to False.Two findings worth carrying forward:
assert_autofinder_success: True, which iswhat
setup_prover.py:409emits, both projects still fail: 35 of 171 and 15 of 43 files fall backduring autofinder instrumentation. That is why rung 2 exists, and it is a real cost, paid instead of
the larger one of inlining every contract.
optimizer_runsvalues (10,000,000 and 100,000) make no difference. 200 compiles thesame 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_irskips the walk. Three existing tests moved byone 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.