-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Description
Summary
This issue proposes adding a small but provably-safe peephole optimization to the Solidity EVM backend. The optimization rewrites the common function selector guard (checking for CALLDATASIZE β₯ 4) into a shorter and cheaper equivalent sequence.
This optimization:
-
Saves 1 byte of runtime bytecode (β 200 gas deploy-time).
-
Saves 2 gas per external call (measured using evmone).
-
Is proven semantically equivalent to the original code for all possible calldata sizes.
-
Applies only in the function dispatcher immediately following a
Tag/JUMPDEST, so it does not interact with ABI decoding or other uses ofCALLDATASIZE.
A PR implementing this change is available here:
π[PR #16315]
Motivation
Every Solidity-generated dispatcher performs a check that CALLDATASIZE β₯ 4 before reading the 4-byte function selector.
The canonical pattern emitted by the compiler is:
PUSH1 0x04 CALLDATASIZE LT ISZERO PUSH <tag> JUMPI
This can be equivalently rewritten as:
PUSH1 0x03 CALLDATASIZE GT PUSH <tag> JUMPI
because for all natural numbers cds:
!(cds < 4) β‘ (cds β₯ 4) β‘ (cds > 3)
The new sequence is shorter (4 bytes instead of 5) and avoids an ISZERO.
Bytecode and Gas Improvement
Measured execution was performed using evmone on a minimal contract with a single external entry point.
Formal Equivalence
A formal equivalence argument is included in the associated PR.
Summary:
Let cds = CALLDATASIZE β₯ 0.
Before:
b_before = !(cds < 4)
After:
b_after = (cds > 3)
Prove:
β cds β β: !(cds < 4) = (cds > 3)
This has also been expressed in SMT form, where a Z3 model search for a counterexample is unsatisfiable:
(assert (>= cds 0)) (assert (not (= (ite (< cds 4) 0 1) (ite (> cds 3) 1 0)))) (check-sat) ; returns "unsat"
Thus the transformation is semantics-preserving.
Safety Conditions
To avoid rewriting unrelated code, the peephole optimization is only applied when:
-
The sequence appears immediately after a
Tag/JUMPDEST, -
The constant is exactly PUSH1 4,
-
The
<tag>operand is a PushTag, -
The branch is a JUMPI,
-
No intermediate instructions modify the stack in a way incompatible with the canonical selector guard.
This prevents optimizations of ABI decoding paths such as string/bytes length checks, which often use similar patterns but require different semantics.
Request
I propose integrating this optimization into the Solidity backend.
The PR linked below contains:
-
Implementation in
PeepholeOptimiser.cpp -
A formal explanation of correctness
-
Unit tests
-
Demonstrations of gas and size savings
π [PR #16315]
Feedback from the maintainers on design, safety constraints, and test coverage would be very welcome.