Skip to content

Commit

Permalink
Optimizer docs: add missing prerequisites and missing bits of extra i…
Browse files Browse the repository at this point in the history
…nfo from step docstrings
  • Loading branch information
cameel committed Apr 25, 2024
1 parent 5d02c2f commit 7559f94
Showing 1 changed file with 99 additions and 8 deletions.
107 changes: 99 additions & 8 deletions docs/internals/optimizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ its visibility and it is impossible to reference variables defined in a differen
The benefit of this stage is that function definitions can be looked up more easily
and functions can be optimized in isolation without having to traverse the AST completely.

Prerequisites: Disambiguator.

.. _function-grouper:

FunctionGrouper
Expand All @@ -431,6 +433,8 @@ and ``F`` is a list of function definitions such that no function contains a fun

The benefit of this stage is that we always know where the list of functions begins.

Prerequisites: Disambiguator, FunctionHoister.

.. _for-loop-condition-into-body:

ForLoopConditionIntoBody
Expand Down Expand Up @@ -458,6 +462,12 @@ is transformed to
This transformation can also be useful when paired with LoopInvariantCodeMotion, since
invariants in the loop-invariant conditions can then be taken outside the loop.

Loops that already have a literal constant as iteration condition are not transformed.

To avoid unnecessary rewriting, it is recommended to run this step after StructuralSimplifier.

Prerequisites: Disambiguator.

.. _for-loop-init-rewriter:

ForLoopInitRewriter
Expand All @@ -484,6 +494,8 @@ is transformed to
This eases the rest of the optimization process because we can ignore
the complicated scoping rules of the ``for`` loop initialization block.

Prerequisites: Disambiguator.

.. _var-decl-initializer:

VarDeclInitializer
Expand Down Expand Up @@ -660,6 +672,8 @@ are run right before it, because then it does not generate excessive amounts of
On the other hand, the CommonSubexpressionEliminator could be more efficient if run after the
SSA transform.

Prerequisites: Disambiguator, ForLoopInitRewriter.

.. _unused-assign-eliminator:

UnusedAssignEliminator
Expand Down Expand Up @@ -774,6 +788,8 @@ In the second traversal, all assignments that are in the "unused" state are remo
This step is usually run right after the SSA transform to complete
the generation of the pseudo-SSA.

Prerequisites: Disambiguator, ForLoopInitRewriter.

Tools
-----

Expand Down Expand Up @@ -807,6 +823,8 @@ in any of the control-flow paths. For instance, upon entering a
``for`` loop, all variables are cleared that will be assigned during the
body or the post block.

Prerequisites: Disambiguator, ForLoopInitRewriter.

Expression-Scale Simplifications
--------------------------------

Expand Down Expand Up @@ -840,6 +858,8 @@ have a higher chance of expressions to be replaceable.
The expression simplifier will be able to perform better replacements
if the CommonSubexpressionEliminator was run right before it.

Prerequisites: Disambiguator, ForLoopInitRewriter.

.. _expression-simplifier:

ExpressionSimplifier
Expand All @@ -853,12 +873,15 @@ It tries to match patterns like ``X + 0`` on each subexpression.
During the matching procedure, it resolves variables to their currently
assigned expressions to be able to match more deeply nested patterns
even when the code is in pseudo-SSA form.
In fact, running CommonSubexpressionEliminator before helps this component track equivalent subexpressions.

Some of the patterns like ``X - X -> 0`` can only be applied as long
as the expression ``X`` is movable, because otherwise it would remove its potential side-effects.
Since variable references are always movable, even if their current
value might not be, the ExpressionSimplifier is again more powerful
in split or pseudo-SSA form.
in :ref:`expression-split <expression-splitter>` or :ref:`pseudo-SSA <ssa-transform>` form.

Prerequisites: Disambiguator, ForLoopInitRewriter.

.. _literal-rematerialiser:

Expand All @@ -880,6 +903,9 @@ LoadResolver
Optimisation stage that replaces expressions of type ``sload(x)`` and ``mload(x)`` by the value
currently stored in storage resp. memory, if known.

Also evaluates simple ``keccak256(a, c)`` when the value at memory location ``a`` is known and ``c``
is a constant ``<= 32``.

Works best if the code is in SSA form.

Prerequisites: Disambiguator, ForLoopInitRewriter.
Expand All @@ -895,6 +921,8 @@ CircularReferencesPruner
This stage removes functions that call each other but are
neither externally referenced nor referenced from the outermost context.

Prerequisites: Disambiguator, FunctionHoister.

.. _conditional-simplifier:

ConditionalSimplifier
Expand All @@ -919,7 +947,7 @@ Future features:
- allow replacements by ``1``
- take termination of user-defined functions into account

Works best with SSA form and if dead code removal has run before.
Works best with SSA form and if DeadCodeEliminator has run before.

Prerequisite: Disambiguator.

Expand Down Expand Up @@ -990,6 +1018,9 @@ CommonSubexpressionEliminator, because SSA will make sure that the variables
will not change and the CommonSubexpressionEliminator re-uses exactly the same
variable if the value is known to be the same.

Works best on code without literal arguments, which is the case when the code is in the
:ref:`expression-split <expression-splitter>` form.

Prerequisites: Disambiguator, ForLoopInitRewriter.

.. _unused-pruner:
Expand All @@ -1005,6 +1036,10 @@ but its value is discarded.

All movable expression statements (expressions that are not assigned) are removed.

Does not remove circular references.

Prerequisites: Disambiguator.

.. _structural-simplifier:

StructuralSimplifier
Expand All @@ -1023,6 +1058,12 @@ a structural level:

This component uses the DataflowAnalyzer.

LiteralRematerialiser should be run before this step.

Prerequisites: Disambiguator.

Important: Can only be used on EVM code.

.. _block-flattener:

BlockFlattener
Expand Down Expand Up @@ -1060,6 +1101,8 @@ is transformed to
As long as the code is disambiguated, this does not cause a problem because
the scopes of variables can only grow.

Prerequisites: Disambiguator, FunctionGrouper.

.. _loop-invariant-code-motion:

LoopInvariantCodeMotion
Expand All @@ -1071,6 +1114,9 @@ declarations inside conditional branches will not be moved out of the loop.

ExpressionSplitter and SSATransform should be run upfront to obtain better results.

The transformation will not move loop-invariant condition out of the condition block of the loop.
This can be addressed by running ForLoopConditionIntoBody beforehand.

Prerequisites: Disambiguator, ForLoopInitRewriter, FunctionHoister.


Expand Down Expand Up @@ -1100,15 +1146,15 @@ optimization step is mainly useful for functions that would not be inlined.

Prerequisites: Disambiguator, FunctionHoister.

LiteralRematerialiser is recommended as a prerequisite, even though it's not required for
LiteralRematerialiser is recommended before, even though it's not required for
correctness.

.. _unused-function-parameter-pruner:

UnusedFunctionParameterPruner
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This step removes unused parameters in a function.
This step removes unused parameters from function definitions.

If a parameter is unused, like ``c`` and ``y`` in, ``function f(a,b,c) -> x, y { x := div(a,b) }``, we
remove the parameter and create a new "linking" function as follows:
Expand All @@ -1122,9 +1168,9 @@ and replace all references to ``f`` by ``f2``.
The inliner should be run afterwards to make sure that all references to ``f2`` are replaced by
``f``.

Prerequisites: Disambiguator, FunctionHoister, LiteralRematerialiser.
Prerequisites: Disambiguator, FunctionHoister.

The step LiteralRematerialiser is not required for correctness. It helps deal with cases such as:
The step LiteralRematerialiser is recommended but not required for correctness. It helps deal with cases such as:
``function f(x) -> y { revert(y, y} }`` where the literal ``y`` will be replaced by its value ``0``,
allowing us to rewrite the function.

Expand Down Expand Up @@ -1186,6 +1232,8 @@ functions is replaced by the other.

The actual removal of the function is performed by the UnusedPruner.

Prerequisites: Disambiguator, FunctionHoister.

Function Inlining
-----------------

Expand All @@ -1212,7 +1260,7 @@ Example: The function to be inlined has the form of ``function f(...) -> r { r :

The result of this inlining is always a single expression.

This component can only be used on sources with unique names.
Prerequisites: Disambiguator.

.. _full-inliner:

Expand All @@ -1227,6 +1275,25 @@ code than more efficient code. In some cases, though, inlining a function
can have positive effects on subsequent optimizer steps. This is the case
if one of the function arguments is a constant, for example.

The transform changes code of the form

.. code-block:: yul
function f(a, b) -> c { /* ... */ }
let z := f(x, y)
into

.. code-block:: yul
function f(a, b) -> c { /* ... */ }
let f_b := y
let f_a := x
let f_c
/* ... code of f, with replacements: a -> f_a, b -> f_b, c -> f_c */
let z := f_c
During inlining, a heuristic is used to tell if the function call
should be inlined or not.
The current heuristic does not inline into "large" functions unless
Expand All @@ -1243,11 +1310,13 @@ we can run the optimizer on this specialized function. If it
results in heavy gains, the specialized function is kept,
otherwise the original function is used instead.

FunctionHoister and ExpressionSplitter are recommended as prerequisites since they make the step
FunctionHoister and ExpressionSplitter are recommended before this step since they make it
more efficient, but are not required for correctness.
In particular, function calls with other function calls as arguments are not inlined, but running
ExpressionSplitter beforehand ensures that there are no such calls in the input.

Prerequisites: Disambiguator.

Cleanup
-------

Expand All @@ -1268,8 +1337,24 @@ It does not make use of any information concerning the commutativity of the opco
if moving the value of a variable to its place of use would change the order
of any function call or opcode execution, the transformation is not performed.

Code of the form

.. code-block:: yul
let a1 := mload(y)
let a2 := mul(x, 4)
sstore(a2, a1)
is transformed into

.. code-block:: yul
sstore(mul(x, 4), mload(y))
Note that the component will not move the assigned value of a variable assignment
or a variable that is referenced more than once.
The transformation is also not applied to loop conditions, because those are
evaluated with each loop.

The snippet ``let x := add(0, 2) let y := mul(x, mload(2))`` is not transformed,
because it would cause the order of the call to the opcodes ``add`` and
Expand All @@ -1281,6 +1366,8 @@ Because of that, the snippet ``let x := add(0, 2) let y := mul(x, 3)`` is
transformed to ``let y := mul(add(0, 2), 3)``, even though the ``add`` opcode
would be executed after the evaluation of the literal ``3``.

Prerequisites: Disambiguator.

.. _ssa-reverser:

SSAReverser
Expand Down Expand Up @@ -1331,6 +1418,8 @@ by ``a`` (until ``a`` is re-assigned). The UnusedPruner will then
eliminate the variable ``a_1`` altogether and thus fully reverse the
SSATransform.

Prerequisites: Disambiguator.

.. _stack-compressor:

StackCompressor
Expand Down Expand Up @@ -1367,6 +1456,8 @@ which are always movable.
If the value is very cheap or the variable was explicitly requested to be eliminated,
the variable reference is replaced by its current value.

Prerequisites: Disambiguator, ForLoopInitRewriter.

.. _for-loop-condition-out-of-body:

ForLoopConditionOutOfBody
Expand Down

0 comments on commit 7559f94

Please sign in to comment.