Skip to content

Commit ea78c8f

Browse files
authored
Merge pull request #13376 from ethereum/make-hardcoded-parts-of-optimizer-sequence-configurable
2 parents f808855 + e37dc8e commit ea78c8f

File tree

48 files changed

+525
-104
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+525
-104
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Language Features:
44

55

66
Compiler Features:
7+
* Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string.
78

89

910
Bugfixes:

docs/internals/optimizer.rst

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -281,60 +281,85 @@ The following transformation steps are the main components:
281281
- Redundant Assign Eliminator
282282
- Full Inliner
283283

284+
.. _optimizer-steps:
285+
284286
Optimizer Steps
285287
---------------
286288

287289
This is a list of all steps the Yul-based optimizer sorted alphabetically. You can find more information
288290
on the individual steps and their sequence below.
289291

290-
- :ref:`block-flattener`.
291-
- :ref:`circular-reference-pruner`.
292-
- :ref:`common-subexpression-eliminator`.
293-
- :ref:`conditional-simplifier`.
294-
- :ref:`conditional-unsimplifier`.
295-
- :ref:`control-flow-simplifier`.
296-
- :ref:`dead-code-eliminator`.
297-
- :ref:`equal-store-eliminator`.
298-
- :ref:`equivalent-function-combiner`.
299-
- :ref:`expression-joiner`.
300-
- :ref:`expression-simplifier`.
301-
- :ref:`expression-splitter`.
302-
- :ref:`for-loop-condition-into-body`.
303-
- :ref:`for-loop-condition-out-of-body`.
304-
- :ref:`for-loop-init-rewriter`.
305-
- :ref:`expression-inliner`.
306-
- :ref:`full-inliner`.
307-
- :ref:`function-grouper`.
308-
- :ref:`function-hoister`.
309-
- :ref:`function-specializer`.
310-
- :ref:`literal-rematerialiser`.
311-
- :ref:`load-resolver`.
312-
- :ref:`loop-invariant-code-motion`.
313-
- :ref:`redundant-assign-eliminator`.
314-
- :ref:`reasoning-based-simplifier`.
315-
- :ref:`rematerialiser`.
316-
- :ref:`SSA-reverser`.
317-
- :ref:`SSA-transform`.
318-
- :ref:`structural-simplifier`.
319-
- :ref:`unused-function-parameter-pruner`.
320-
- :ref:`unused-pruner`.
321-
- :ref:`var-decl-initializer`.
292+
============ ===============================
293+
Abbreviation Full name
294+
============ ===============================
295+
``f`` :ref:`block-flattener`
296+
``l`` :ref:`circular-reference-pruner`
297+
``c`` :ref:`common-subexpression-eliminator`
298+
``C`` :ref:`conditional-simplifier`
299+
``U`` :ref:`conditional-unsimplifier`
300+
``n`` :ref:`control-flow-simplifier`
301+
``D`` :ref:`dead-code-eliminator`
302+
``E`` :ref:`equal-store-eliminator`
303+
``v`` :ref:`equivalent-function-combiner`
304+
``e`` :ref:`expression-inliner`
305+
``j`` :ref:`expression-joiner`
306+
``s`` :ref:`expression-simplifier`
307+
``x`` :ref:`expression-splitter`
308+
``I`` :ref:`for-loop-condition-into-body`
309+
``O`` :ref:`for-loop-condition-out-of-body`
310+
``o`` :ref:`for-loop-init-rewriter`
311+
``i`` :ref:`full-inliner`
312+
``g`` :ref:`function-grouper`
313+
``h`` :ref:`function-hoister`
314+
``F`` :ref:`function-specializer`
315+
``T`` :ref:`literal-rematerialiser`
316+
``L`` :ref:`load-resolver`
317+
``M`` :ref:`loop-invariant-code-motion`
318+
``r`` :ref:`redundant-assign-eliminator`
319+
``R`` :ref:`reasoning-based-simplifier` - highly experimental
320+
``m`` :ref:`rematerialiser`
321+
``V`` :ref:`SSA-reverser`
322+
``a`` :ref:`SSA-transform`
323+
``t`` :ref:`structural-simplifier`
324+
``p`` :ref:`unused-function-parameter-pruner`
325+
``u`` :ref:`unused-pruner`
326+
``d`` :ref:`var-decl-initializer`
327+
============ ===============================
328+
329+
Some steps depend on properties ensured by ``BlockFlattener``, ``FunctionGrouper``, ``ForLoopInitRewriter``.
330+
For this reason the Yul optimizer always applies them before applying any steps supplied by the user.
331+
332+
The ReasoningBasedSimplifier is an optimizer step that is currently not enabled
333+
in the default set of steps. It uses an SMT solver to simplify arithmetic expressions
334+
and boolean conditions. It has not received thorough testing or validation yet and can produce
335+
non-reproducible results, so please use with care!
322336

323337
Selecting Optimizations
324338
-----------------------
325339

326-
By default the optimizer applies its predefined sequence of optimization steps to
327-
the generated assembly. You can override this sequence and supply your own using
328-
the ``--yul-optimizations`` option:
340+
By default the optimizer applies its predefined sequence of optimization steps to the generated assembly.
341+
You can override this sequence and supply your own using the ``--yul-optimizations`` option:
329342

330343
.. code-block:: bash
331344
332-
solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul'
345+
solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul:fDnTOc'
346+
347+
The order of steps is significant and affects the quality of the output.
348+
Moreover, applying a step may uncover new optimization opportunities for others that were already applied,
349+
so repeating steps is often beneficial.
333350

334351
The sequence inside ``[...]`` will be applied multiple times in a loop until the Yul code
335352
remains unchanged or until the maximum number of rounds (currently 12) has been reached.
353+
Brackets (``[]``) may be used multiple times in a sequence, but can not be nested.
354+
355+
An important thing to note, is that there are some hardcoded steps that are always run before and after the
356+
user-supplied sequence, or the default sequence if one was not supplied by the user.
336357

337-
Available abbreviations are listed in the :ref:`Yul optimizer docs <optimization-step-sequence>`.
358+
The cleanup sequence delimiter ``:`` is optional, and is used to supply a custom cleanup sequence
359+
in order to replace the default one. If omitted, the optimizer will simply apply the default cleanup
360+
sequence. In addition, the delimiter may be placed at the beginning of the user-supplied sequence,
361+
which will result in the optimization sequence being empty, whereas conversely, if placed at the end of
362+
the sequence, will be treated as an empty cleanup sequence.
338363

339364
Preprocessing
340365
-------------

docs/yul.rst

Lines changed: 2 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,65 +1245,8 @@ In Solidity mode, the Yul optimizer is activated together with the regular optim
12451245
Optimization Step Sequence
12461246
--------------------------
12471247

1248-
By default the Yul optimizer applies its predefined sequence of optimization steps to the generated assembly.
1249-
You can override this sequence and supply your own using the ``--yul-optimizations`` option:
1250-
1251-
.. code-block:: sh
1252-
1253-
solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul'
1254-
1255-
The order of steps is significant and affects the quality of the output.
1256-
Moreover, applying a step may uncover new optimization opportunities for others that were already
1257-
applied so repeating steps is often beneficial.
1258-
By enclosing part of the sequence in square brackets (``[]``) you tell the optimizer to repeatedly
1259-
apply that part until it no longer improves the size of the resulting assembly.
1260-
You can use brackets multiple times in a single sequence but they cannot be nested.
1261-
1262-
The following optimization steps are available:
1263-
1264-
============ ===============================
1265-
Abbreviation Full name
1266-
============ ===============================
1267-
``f`` ``BlockFlattener``
1268-
``l`` ``CircularReferencesPruner``
1269-
``c`` ``CommonSubexpressionEliminator``
1270-
``C`` ``ConditionalSimplifier``
1271-
``U`` ``ConditionalUnsimplifier``
1272-
``n`` ``ControlFlowSimplifier``
1273-
``D`` ``DeadCodeEliminator``
1274-
``v`` ``EquivalentFunctionCombiner``
1275-
``e`` ``ExpressionInliner``
1276-
``j`` ``ExpressionJoiner``
1277-
``s`` ``ExpressionSimplifier``
1278-
``x`` ``ExpressionSplitter``
1279-
``I`` ``ForLoopConditionIntoBody``
1280-
``O`` ``ForLoopConditionOutOfBody``
1281-
``o`` ``ForLoopInitRewriter``
1282-
``i`` ``FullInliner``
1283-
``g`` ``FunctionGrouper``
1284-
``h`` ``FunctionHoister``
1285-
``F`` ``FunctionSpecializer``
1286-
``T`` ``LiteralRematerialiser``
1287-
``L`` ``LoadResolver``
1288-
``M`` ``LoopInvariantCodeMotion``
1289-
``r`` ``RedundantAssignEliminator``
1290-
``R`` ``ReasoningBasedSimplifier`` - highly experimental
1291-
``m`` ``Rematerialiser``
1292-
``V`` ``SSAReverser``
1293-
``a`` ``SSATransform``
1294-
``t`` ``StructuralSimplifier``
1295-
``u`` ``UnusedPruner``
1296-
``p`` ``UnusedFunctionParameterPruner``
1297-
``d`` ``VarDeclInitializer``
1298-
============ ===============================
1299-
1300-
Some steps depend on properties ensured by ``BlockFlattener``, ``FunctionGrouper``, ``ForLoopInitRewriter``.
1301-
For this reason the Yul optimizer always applies them before applying any steps supplied by the user.
1302-
1303-
The ReasoningBasedSimplifier is an optimizer step that is currently not enabled
1304-
in the default set of steps. It uses an SMT solver to simplify arithmetic expressions
1305-
and boolean conditions. It has not received thorough testing or validation yet and can produce
1306-
non-reproducible results, so please use with care!
1248+
Detailed information regrading the optimization sequence as well a list of abbreviations is
1249+
available in the :ref:`optimizer docs <optimizer-steps>`.
13071250

13081251
.. _erc20yul:
13091252

libsolidity/codegen/CompilerContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
547547
_object,
548548
_optimiserSettings.optimizeStackAllocation,
549549
_optimiserSettings.yulOptimiserSteps,
550+
_optimiserSettings.yulOptimiserCleanupSteps,
550551
isCreation? nullopt : make_optional(_optimiserSettings.expectedExecutionsPerDeployment),
551552
_externalIdentifiers
552553
);

libsolidity/interface/CompilerStack.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1539,7 +1539,7 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con
15391539
{
15401540
details["yulDetails"] = Json::objectValue;
15411541
details["yulDetails"]["stackAllocation"] = m_optimiserSettings.optimizeStackAllocation;
1542-
details["yulDetails"]["optimizerSteps"] = m_optimiserSettings.yulOptimiserSteps;
1542+
details["yulDetails"]["optimizerSteps"] = m_optimiserSettings.yulOptimiserSteps + ":" + m_optimiserSettings.yulOptimiserCleanupSteps;
15431543
}
15441544

15451545
meta["settings"]["optimizer"]["details"] = std::move(details);

libsolidity/interface/OptimiserSettings.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ struct OptimiserSettings
5959
"]"
6060
"jmul[jul] VcTOcul jmul"; // Make source short and pretty
6161

62+
static char constexpr DefaultYulOptimiserCleanupSteps[] = "fDnTOc";
63+
6264
/// No optimisations at all - not recommended.
6365
static OptimiserSettings none()
6466
{
@@ -146,6 +148,10 @@ struct OptimiserSettings
146148
/// them just by setting this to an empty string. Set @a runYulOptimiser to false if you want
147149
/// no optimisations.
148150
std::string yulOptimiserSteps = DefaultYulOptimiserSteps;
151+
/// Sequence of clean-up optimisation steps after yulOptimiserSteps is run. Note that if the string
152+
/// is left empty, there will still be hard-coded optimisation steps that will run regardless.
153+
/// Set @a runYulOptimiser to false if you want no optimisations.
154+
std::string yulOptimiserCleanupSteps = DefaultYulOptimiserCleanupSteps;
149155
/// This specifies an estimate on how often each opcode in this assembly will be executed,
150156
/// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage.
151157
size_t expectedExecutionsPerDeployment = 200;

libsolidity/interface/StandardCompiler.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ std::optional<Json::Value> checkOptimizerDetail(Json::Value const& _details, std
472472
return {};
473473
}
474474

475-
std::optional<Json::Value> checkOptimizerDetailSteps(Json::Value const& _details, std::string const& _name, string& _setting)
475+
std::optional<Json::Value> checkOptimizerDetailSteps(Json::Value const& _details, std::string const& _name, string& _optimiserSetting, string& _cleanupSetting)
476476
{
477477
if (_details.isMember(_name))
478478
{
@@ -490,7 +490,14 @@ std::optional<Json::Value> checkOptimizerDetailSteps(Json::Value const& _details
490490
);
491491
}
492492

493-
_setting = _details[_name].asString();
493+
string const fullSequence = _details[_name].asString();
494+
auto const delimiterPos = fullSequence.find(":");
495+
_optimiserSetting = fullSequence.substr(0, delimiterPos);
496+
497+
if (delimiterPos != string::npos)
498+
_cleanupSetting = fullSequence.substr(delimiterPos + 1);
499+
else
500+
solAssert(_cleanupSetting == OptimiserSettings::DefaultYulOptimiserCleanupSteps);
494501
}
495502
else
496503
return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be a string");
@@ -616,7 +623,7 @@ std::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Value
616623
return *result;
617624
if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation))
618625
return *error;
619-
if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps))
626+
if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps, settings.yulOptimiserCleanupSteps))
620627
return *error;
621628
}
622629
}

libyul/YulStack.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ void YulStack::optimize(Object& _object, bool _isCreation)
209209
_object,
210210
m_optimiserSettings.optimizeStackAllocation,
211211
m_optimiserSettings.yulOptimiserSteps,
212+
m_optimiserSettings.yulOptimiserCleanupSteps,
212213
_isCreation ? nullopt : make_optional(m_optimiserSettings.expectedExecutionsPerDeployment),
213214
{}
214215
);

libyul/optimiser/Suite.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ void OptimiserSuite::run(
9393
Object& _object,
9494
bool _optimizeStackAllocation,
9595
string_view _optimisationSequence,
96+
string_view _optimisationCleanupSequence,
9697
optional<size_t> _expectedExecutionsPerDeployment,
9798
set<YulString> const& _externallyUsedIdentifiers
9899
)
@@ -139,7 +140,13 @@ void OptimiserSuite::run(
139140
_optimizeStackAllocation,
140141
stackCompressorMaxIterations
141142
);
142-
suite.runSequence("fDnTOc g", ast);
143+
144+
// Run the user-supplied clean up sequence
145+
suite.runSequence(_optimisationCleanupSequence, ast);
146+
// Hard-coded FunctionGrouper step is used to bring the AST into a canonical form required by the StackCompressor
147+
// and StackLimitEvader. This is hard-coded as the last step, as some previously executed steps may break the
148+
// aforementioned form, thus causing the StackCompressor/StackLimitEvader to throw.
149+
suite.runSequence("g", ast);
143150

144151
if (evmDialect)
145152
{
@@ -296,6 +303,7 @@ map<char, string> const& OptimiserSuite::stepAbbreviationToNameMap()
296303
void OptimiserSuite::validateSequence(string_view _stepAbbreviations)
297304
{
298305
int8_t nestingLevel = 0;
306+
int8_t colonDelimiters = 0;
299307
for (char abbreviation: _stepAbbreviations)
300308
switch (abbreviation)
301309
{
@@ -310,6 +318,11 @@ void OptimiserSuite::validateSequence(string_view _stepAbbreviations)
310318
nestingLevel--;
311319
assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets");
312320
break;
321+
case ':':
322+
++colonDelimiters;
323+
assertThrow(nestingLevel == 0, OptimizerException, "Cleanup sequence delimiter cannot be placed inside the brackets");
324+
assertThrow(colonDelimiters <= 1, OptimizerException, "Too many cleanup sequence delimiters");
325+
break;
313326
default:
314327
{
315328
yulAssert(

libyul/optimiser/Suite.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class OptimiserSuite
5151

5252
/// Special characters that do not represent optimiser steps but are allowed in abbreviation sequences.
5353
/// Some of them (like whitespace) are ignored, others (like brackets) are a part of the syntax.
54-
static constexpr char NonStepAbbreviations[] = " \n[]";
54+
static constexpr char NonStepAbbreviations[] = " \n[]:";
5555

5656
enum class Debug
5757
{
@@ -68,6 +68,7 @@ class OptimiserSuite
6868
Object& _object,
6969
bool _optimizeStackAllocation,
7070
std::string_view _optimisationSequence,
71+
std::string_view _optimisationCleanupSequence,
7172
std::optional<size_t> _expectedExecutionsPerDeployment,
7273
std::set<YulString> const& _externallyUsedIdentifiers = {}
7374
);

0 commit comments

Comments
 (0)