Skip to content

Peephole Optimization: Simplify Function Selector Guard (PUSH1 4 / CALLDATASIZE / LT / ISZERO) β†’ (PUSH1 3 / CALLDATASIZE / GT)Β #16316

@philippecyberian

Description

@philippecyberian

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 of CALLDATASIZE.

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

Metric | Before | After | Improvement -- | -- | -- | -- Guard byte length | 5 bytes | 4 bytes | βˆ’1 byte Deployment gas | β€” | β€” | ~200 gas saved per guard Execution gas | 11 gas | 8 gas | 3 gas theoretical, 2 gas measured

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions