Skip to content

Commit

Permalink
Merge pull request #14650 from ethereum/uncheckedForLoopIncrementDocs
Browse files Browse the repository at this point in the history
Update optimizer docs to include new unchecked loop increment
  • Loading branch information
matheusaaguiar authored Nov 23, 2023
2 parents 58811f1 + 158330b commit efed3b2
Showing 1 changed file with 78 additions and 3 deletions.
81 changes: 78 additions & 3 deletions docs/internals/optimizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
The Optimizer
*************

The Solidity compiler uses two different optimizer modules: The "old" optimizer
that operates at the opcode level and the "new" optimizer that operates on Yul IR code.
The Solidity compiler involves optimizations at three different levels (in order of execution):

- Optimizations during code generation based on a direct analysis of Solidity code.
- Optimizing transformations on the Yul IR code.
- Optimizations at the opcode level.

The opcode-based optimizer applies a set of `simplification rules <https://github.com/ethereum/solidity/blob/develop/libevmasm/RuleList.h>`_
to opcodes. It also combines equal code sets and removes unused code.
Expand All @@ -20,14 +23,26 @@ the function calls. Similarly, if a function is
side-effect free and its result is multiplied by zero, you can remove the function
call completely.

The codegen-based optimizer affects the initial low-level code produced from the Solidity input.
In the legacy pipeline, the bytecode is generated immediately and most of the optimizations of this
kind are implicit and not configurable, the only exception being an optimization which changes the
order of literals in binary operations.
The IR-based pipeline takes a different approach and produces Yul IR closely matching the structure
of the Solidity code, with nearly all optimizations deferred to the Yul optimizer module.
In that case codegen-level optimization is done only in very limited cases which are difficult to
handle in Yul IR, but are straightforward with the high-level information from analysis phase at hand.
An example of such an optimization is the bypass of checked arithmetic when incrementing the counter
in certain idiomatic ``for`` loops.

Currently, the parameter ``--optimize`` activates the opcode-based optimizer for the
generated bytecode and the Yul optimizer for the Yul code generated internally, for example for ABI coder v2.
One can use ``solc --ir-optimized --optimize`` to produce an
optimized Yul IR for a Solidity source. Similarly, one can use ``solc --strict-assembly --optimize``
for a stand-alone Yul mode.

.. note::
The `peephole optimizer <https://en.wikipedia.org/wiki/Peephole_optimization>`_ is always
Some optimizer steps, such as, for example, the `peephole optimizer <https://en.wikipedia.org/wiki/Peephole_optimization>`_
and the :ref:`unchecked loop increment optimizer <unchecked-loop-optimizer>` are always
enabled by default and can only be turned off via the :ref:`Standard JSON <compiler-api>`.

.. note::
Expand Down Expand Up @@ -1388,3 +1403,63 @@ into
}
The LiteralRematerialiser should be run before this step.

Codegen-Based Optimizer Module
==============================

Currently, the codegen-based optimizer module provides two optimizations.

The first one, available in the legacy code generator, moves literals to the right side of
commutative binary operators, which helps exploit their associativity.

The other one, available in the IR-based code generator, enables the use of unchecked arithmetic
when generating code for incrementing the counter variable of certain idiomatic ``for`` loops.
This avoids wasting gas by identifying some conditions that guarantee that the counter variable
cannot overflow.
This eliminates the need to use a verbose unchecked arithmetic block inside the loop body to
increment the counter variable.

.. _unchecked-loop-optimizer:

Unchecked Loop Increment
------------------------

Introduced in Solidity ``0.8.22``, the overflow check optimization step is concerned with identifying
the conditions under which the ``for`` loop counter can be safely incremented
without overflow checks.

This optimization is **only** applied to ``for`` loops of the general form:

.. code-block:: solidity
for (uint i = X; i < Y; ++i) {
// variable i is not modified in the loop body
}
The condition and the fact that the counter variable is only ever incremented
guarantee that it never overflows.
The precise requirements for the loop to be eligible for the optimization are as follows:

- The loop condition is a comparison of the form ``i < Y``, for a local counter variable ``i``
(called the "loop counter" hereon) and an expression ``Y``.
- The built-in operator ``<`` is necessarily used in the loop condition and is the only operator
that triggers the optimization. ``<=`` and the like are intentionally excluded. Additionally,
user-defined operators are **not** eligible.
- The loop expression is a prefix or postfix increment of the counter variable, i.e, ``i++`` or ``++i``.
- The loop counter is a local variable of a built-in integer type.
- The loop counter is **not** modified by the loop body or by the expression used as the loop condition.
- The comparison is performed on the same type as the loop counter, meaning that the type of the
right-hand-side expression is implicitly convertible to the type of the counter, such that the latter
is not implicitly widened before the comparison.

To clarify the last condition, consider the following example:

.. code-block:: solidity
for (uint8 i = 0; i < uint16(1000); i++) {
// ...
}
In this case, the counter ``i`` has its type implicitly converted from ``uint8``
to ``uint16`` before the comparison and the condition is in fact never false, so
the overflow check for the increment cannot be removed.

0 comments on commit efed3b2

Please sign in to comment.