Address review comments

This commit is contained in:
Nikola Matic 2022-09-12 10:57:02 +02:00
parent feba1bfeff
commit e37dc8e975
23 changed files with 108 additions and 243 deletions

View File

@ -4,7 +4,8 @@ Language Features:
Compiler Features:
* Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (`:`) in the sequence string.
* Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string.
Bugfixes:
@ -21,7 +22,7 @@ Compiler Features:
* Yul Optimizer: Simplify the starting offset of zero-length operations to zero.
Bugfixes:`````
Bugfixes:
* Type Checker: Fix internal compiler error on tuple assignments with invalid left-hand side.
* Yul IR Code Generation: Fix internal compiler error when accessing the ``.slot`` member of a mapping through a storage reference in inline assembly.

View File

@ -281,50 +281,85 @@ The following transformation steps are the main components:
- Redundant Assign Eliminator
- Full Inliner
.. _optimizer-steps:
Optimizer Steps
---------------
This is a list of all steps the Yul-based optimizer sorted alphabetically. You can find more information
on the individual steps and their sequence below.
- :ref:`block-flattener`.
- :ref:`circular-reference-pruner`.
- :ref:`common-subexpression-eliminator`.
- :ref:`conditional-simplifier`.
- :ref:`conditional-unsimplifier`.
- :ref:`control-flow-simplifier`.
- :ref:`dead-code-eliminator`.
- :ref:`equal-store-eliminator`.
- :ref:`equivalent-function-combiner`.
- :ref:`expression-joiner`.
- :ref:`expression-simplifier`.
- :ref:`expression-splitter`.
- :ref:`for-loop-condition-into-body`.
- :ref:`for-loop-condition-out-of-body`.
- :ref:`for-loop-init-rewriter`.
- :ref:`expression-inliner`.
- :ref:`full-inliner`.
- :ref:`function-grouper`.
- :ref:`function-hoister`.
- :ref:`function-specializer`.
- :ref:`literal-rematerialiser`.
- :ref:`load-resolver`.
- :ref:`loop-invariant-code-motion`.
- :ref:`redundant-assign-eliminator`.
- :ref:`reasoning-based-simplifier`.
- :ref:`rematerialiser`.
- :ref:`SSA-reverser`.
- :ref:`SSA-transform`.
- :ref:`structural-simplifier`.
- :ref:`unused-function-parameter-pruner`.
- :ref:`unused-pruner`.
- :ref:`var-decl-initializer`.
============ ===============================
Abbreviation Full name
============ ===============================
``f`` :ref:`block-flattener`
``l`` :ref:`circular-reference-pruner`
``c`` :ref:`common-subexpression-eliminator`
``C`` :ref:`conditional-simplifier`
``U`` :ref:`conditional-unsimplifier`
``n`` :ref:`control-flow-simplifier`
``D`` :ref:`dead-code-eliminator`
``E`` :ref:`equal-store-eliminator`
``v`` :ref:`equivalent-function-combiner`
``e`` :ref:`expression-inliner`
``j`` :ref:`expression-joiner`
``s`` :ref:`expression-simplifier`
``x`` :ref:`expression-splitter`
``I`` :ref:`for-loop-condition-into-body`
``O`` :ref:`for-loop-condition-out-of-body`
``o`` :ref:`for-loop-init-rewriter`
``i`` :ref:`full-inliner`
``g`` :ref:`function-grouper`
``h`` :ref:`function-hoister`
``F`` :ref:`function-specializer`
``T`` :ref:`literal-rematerialiser`
``L`` :ref:`load-resolver`
``M`` :ref:`loop-invariant-code-motion`
``r`` :ref:`redundant-assign-eliminator`
``R`` :ref:`reasoning-based-simplifier` - highly experimental
``m`` :ref:`rematerialiser`
``V`` :ref:`SSA-reverser`
``a`` :ref:`SSA-transform`
``t`` :ref:`structural-simplifier`
``p`` :ref:`unused-function-parameter-pruner`
``u`` :ref:`unused-pruner`
``d`` :ref:`var-decl-initializer`
============ ===============================
Some steps depend on properties ensured by ``BlockFlattener``, ``FunctionGrouper``, ``ForLoopInitRewriter``.
For this reason the Yul optimizer always applies them before applying any steps supplied by the user.
The ReasoningBasedSimplifier is an optimizer step that is currently not enabled
in the default set of steps. It uses an SMT solver to simplify arithmetic expressions
and boolean conditions. It has not received thorough testing or validation yet and can produce
non-reproducible results, so please use with care!
Selecting Optimizations
-----------------------
Detailed information regrading the optimization sequence as well a list of abbreviations is
available in the :ref:`Yul optimizer docs <optimization-step-sequence>`.
By default the optimizer applies its predefined sequence of optimization steps to the generated assembly.
You can override this sequence and supply your own using the ``--yul-optimizations`` option:
.. code-block:: bash
solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul:fDnTOc'
The order of steps is significant and affects the quality of the output.
Moreover, applying a step may uncover new optimization opportunities for others that were already applied,
so repeating steps is often beneficial.
The sequence inside ``[...]`` will be applied multiple times in a loop until the Yul code
remains unchanged or until the maximum number of rounds (currently 12) has been reached.
Brackets (``[]``) may be used multiple times in a sequence, but can not be nested.
An important thing to note, is that there are some hardcoded steps that are always run before and after the
user-supplied sequence, or the default sequence if one was not supplied by the user.
The cleanup sequence delimiter ``:`` is optional, and is used to supply a custom cleanup sequence
in order to replace the default one. If omitted, the optimizer will simply apply the default cleanup
sequence. In addition, the delimiter may be placed at the beginning of the user-supplied sequence,
which will result in the optimization sequence being empty, whereas conversely, if placed at the end of
the sequence, will be treated as an empty cleanup sequence.
Preprocessing
-------------

View File

@ -1245,72 +1245,8 @@ In Solidity mode, the Yul optimizer is activated together with the regular optim
Optimization Step Sequence
--------------------------
By default the optimizer applies its predefined sequence of optimization steps to
the generated assembly. You can override this sequence and supply your own using
the ``--yul-optimizations`` option:
.. code-block:: bash
solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul:fDnTOc'
The sequence inside ``[...]`` will be applied multiple times in a loop until the Yul code
remains unchanged or until the maximum number of rounds (currently 12) has been reached.
An important thing to note, is that there are hardcoded sequences that are run before and after the
user-supplied sequence, or the default sequence if one was not supplied by the user.
The cleanup sequence delimiter ``:`` is optional, and is used to supply a custom cleanup sequence
in order to replace the default one. If omitted, the optimizer will simply apply the default cleanup
sequence. In addition, the delimiter may be placed at the beginning of the user-supplied sequence,
which will be treated as an empty optimization sequence, whereas conversely, if placed at the end of
the sequence, will be treated as an empty cleanup sequence.
The following optimization steps are available:
============ ===============================
Abbreviation Full name
============ ===============================
``f`` ``BlockFlattener``
``l`` ``CircularReferencesPruner``
``c`` ``CommonSubexpressionEliminator``
``C`` ``ConditionalSimplifier``
``U`` ``ConditionalUnsimplifier``
``n`` ``ControlFlowSimplifier``
``D`` ``DeadCodeEliminator``
``v`` ``EquivalentFunctionCombiner``
``e`` ``ExpressionInliner``
``j`` ``ExpressionJoiner``
``s`` ``ExpressionSimplifier``
``x`` ``ExpressionSplitter``
``I`` ``ForLoopConditionIntoBody``
``O`` ``ForLoopConditionOutOfBody``
``o`` ``ForLoopInitRewriter``
``i`` ``FullInliner``
``g`` ``FunctionGrouper``
``h`` ``FunctionHoister``
``F`` ``FunctionSpecializer``
``T`` ``LiteralRematerialiser``
``L`` ``LoadResolver``
``M`` ``LoopInvariantCodeMotion``
``r`` ``RedundantAssignEliminator``
``R`` ``ReasoningBasedSimplifier`` - highly experimental
``m`` ``Rematerialiser``
``V`` ``SSAReverser``
``a`` ``SSATransform``
``t`` ``StructuralSimplifier``
``u`` ``UnusedPruner``
``p`` ``UnusedFunctionParameterPruner``
``d`` ``VarDeclInitializer``
============ ===============================
Some steps depend on properties ensured by ``BlockFlattener``, ``FunctionGrouper``, ``ForLoopInitRewriter``.
For this reason the Yul optimizer always applies them before applying any steps supplied by the user.
The ReasoningBasedSimplifier is an optimizer step that is currently not enabled
in the default set of steps. It uses an SMT solver to simplify arithmetic expressions
and boolean conditions. It has not received thorough testing or validation yet and can produce
non-reproducible results, so please use with care!
Detailed information regrading the optimization sequence as well a list of abbreviations is
available in the :ref:`optimizer docs <optimizer-steps>`.
.. _erc20yul:

View File

@ -496,6 +496,8 @@ std::optional<Json::Value> checkOptimizerDetailSteps(Json::Value const& _details
if (delimiterPos != string::npos)
_cleanupSetting = fullSequence.substr(delimiterPos + 1);
else
solAssert(_cleanupSetting == OptimiserSettings::DefaultYulOptimiserCleanupSteps);
}
else
return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be a string");

View File

@ -143,6 +143,9 @@ void OptimiserSuite::run(
// Run the user-supplied clean up sequence
suite.runSequence(_optimisationCleanupSequence, ast);
// Hard-coded FunctionGrouper step is used to bring the AST into a canonical form required by the StackCompressor
// and StackLimitEvader. This is hard-coded as the last step, as some previously executed steps may break the
// aforementioned form, thus causing the StackCompressor/StackLimitEvader to throw.
suite.runSequence("g", ast);
if (evmDialect)
@ -318,7 +321,7 @@ void OptimiserSuite::validateSequence(string_view _stepAbbreviations)
case ':':
++colonDelimiters;
assertThrow(nestingLevel == 0, OptimizerException, "Cleanup sequence delimiter cannot be placed inside the brackets");
assertThrow(colonDelimiters <=1, OptimizerException, "Too many cleanup sequence delimiters");
assertThrow(colonDelimiters <= 1, OptimizerException, "Too many cleanup sequence delimiters");
break;
default:
{

View File

@ -273,6 +273,8 @@ OptimiserSettings CommandLineOptions::optimiserSettings() const
if (delimiterPos != string::npos)
settings.yulOptimiserCleanupSteps = fullSequence.substr(delimiterPos + 1);
else
solAssert(settings.yulOptimiserCleanupSteps == OptimiserSettings::DefaultYulOptimiserCleanupSteps);
}
return settings;

View File

@ -1 +0,0 @@
--ir-optimized --optimize --yul-optimizations dhfo[Dg:vu]lfnTUtnIf

View File

@ -1 +0,0 @@
Invalid optimizer step sequence in --yul-optimizations: Cleanup sequence delimiter cannot be placed inside the brackets

View File

@ -1,7 +0,0 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
contract C
{
function f() public pure {}
}

View File

@ -1 +0,0 @@
--ir-optimized --optimize --yul-optimizations dhfoDg:vulfn:TUtnIf

View File

@ -1 +0,0 @@
Invalid optimizer step sequence in --yul-optimizations: Too many cleanup sequence delimiters

View File

@ -1,7 +0,0 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
contract C
{
function f() public pure {}
}

View File

@ -1 +0,0 @@
--ir-optimized --optimize --yul-optimizations dhfoDgvulfnTUtnIf:fDnTOc

View File

@ -1,8 +0,0 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
pragma abicoder v2;
contract C
{
constructor() {}
}

View File

@ -1,34 +0,0 @@
Optimized IR:
/// @use-src 0:"yul_optimizer_steps_with_cleanup_sequence/input.sol"
object "C_7" {
code {
{
/// @src 0:80:115 "contract C..."
mstore(64, memoryguard(0x80))
if callvalue()
{
revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb()
}
let _1 := allocate_unbounded()
codecopy(_1, dataoffset("C_7_deployed"), datasize("C_7_deployed"))
return(_1, datasize("C_7_deployed"))
}
function allocate_unbounded() -> memPtr
{ memPtr := mload(64) }
function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb()
{ revert(0, 0) }
}
/// @use-src 0:"yul_optimizer_steps_with_cleanup_sequence/input.sol"
object "C_7_deployed" {
code {
{
/// @src 0:80:115 "contract C..."
mstore(64, memoryguard(0x80))
revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74()
}
function revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74()
{ revert(0, 0) }
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -1 +0,0 @@
--ir-optimized --optimize --yul-optimizations :

View File

@ -1,8 +0,0 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
pragma abicoder v2;
contract C
{
constructor() {}
}

View File

@ -1,42 +0,0 @@
Optimized IR:
/// @use-src 0:"yul_optimizer_steps_with_empty_sequences/input.sol"
object "C_7" {
code {
{
/// @src 0:80:115 "contract C..."
mstore(64, memoryguard(0x80))
if callvalue()
{
revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb()
}
constructor_C()
let _1 := allocate_unbounded()
codecopy(_1, dataoffset("C_7_deployed"), datasize("C_7_deployed"))
return(_1, datasize("C_7_deployed"))
}
function allocate_unbounded() -> memPtr
{ memPtr := mload(64) }
function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb()
{ revert(0, 0) }
/// @ast-id 6 @src 0:97:113 "constructor() {}"
function constructor_C()
{ }
}
/// @use-src 0:"yul_optimizer_steps_with_empty_sequences/input.sol"
object "C_7_deployed" {
code {
{
/// @src 0:80:115 "contract C..."
mstore(64, memoryguard(0x80))
revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74()
}
function shift_right_unsigned(value) -> newValue
{ newValue := shr(224, value) }
function allocate_unbounded() -> memPtr
{ memPtr := mload(64) }
function revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74()
{ revert(0, 0) }
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -404,25 +404,25 @@ BOOST_AUTO_TEST_CASE(metadata_optimiser_sequence)
{
char const* sourceCode = R"(
pragma solidity >=0.0;
contract test {
contract C {
}
)";
vector<tuple<string, string>> sequences =
{
// { "<optimizer sequence>", "<optimizer cleanup sequence>" }
{ "", "" },
{ "", "fDn" },
{ "dhfoDgvulfnTUtnIf", "" },
{ "dhfoDgvulfnTUtnIf", "fDn" }
// {"<optimizer sequence>", "<optimizer cleanup sequence>"}
{"", ""},
{"", "fDn"},
{"dhfoDgvulfnTUtnIf", "" },
{"dhfoDgvulfnTUtnIf", "fDn"}
};
auto check = [sourceCode](string const& optimizerSequence, string const& optimizerCleanupSequence)
auto check = [sourceCode](string const& _optimizerSequence, string const& _optimizerCleanupSequence)
{
OptimiserSettings optimizerSettings = OptimiserSettings::minimal();
optimizerSettings.runYulOptimiser = true;
optimizerSettings.yulOptimiserSteps = optimizerSequence;
optimizerSettings.yulOptimiserCleanupSteps = optimizerCleanupSequence;
optimizerSettings.yulOptimiserSteps = _optimizerSequence;
optimizerSettings.yulOptimiserCleanupSteps = _optimizerCleanupSequence;
CompilerStack compilerStack;
compilerStack.setSources({{"", std::string(sourceCode)}});
compilerStack.setEVMVersion(solidity::test::CommonOptions::get().evmVersion());
@ -430,7 +430,7 @@ BOOST_AUTO_TEST_CASE(metadata_optimiser_sequence)
BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed");
std::string const& serialisedMetadata = compilerStack.metadata("test");
std::string const& serialisedMetadata = compilerStack.metadata("C");
Json::Value metadata;
BOOST_REQUIRE(util::jsonParseStrict(serialisedMetadata, metadata));
BOOST_CHECK(solidity::test::isValidMetadata(metadata));
@ -439,14 +439,12 @@ BOOST_AUTO_TEST_CASE(metadata_optimiser_sequence)
BOOST_CHECK(metadata["settings"]["optimizer"]["details"]["yulDetails"].isMember("optimizerSteps"));
string const metadataOptimizerSteps = metadata["settings"]["optimizer"]["details"]["yulDetails"]["optimizerSteps"].asString();
string const expectedMetadataOptimiserSteps = optimizerSequence + ":" + optimizerCleanupSequence;
string const expectedMetadataOptimiserSteps = _optimizerSequence + ":" + _optimizerCleanupSequence;
BOOST_CHECK_EQUAL(metadataOptimizerSteps, expectedMetadataOptimiserSteps);
};
for (auto const& [sequence, cleanupSequence] : sequences)
{
check(sequence, cleanupSequence);
}
}
BOOST_AUTO_TEST_CASE(metadata_license_missing)

View File

@ -1247,8 +1247,10 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different)
(set<string>{"stackAllocation", "optimizerSteps"})
);
BOOST_CHECK(optimizer["details"]["yulDetails"]["stackAllocation"].asBool() == true);
BOOST_CHECK(optimizer["details"]["yulDetails"]["optimizerSteps"].asString() ==
string{OptimiserSettings::DefaultYulOptimiserSteps} + ":" + string{OptimiserSettings::DefaultYulOptimiserCleanupSteps});
BOOST_CHECK(
optimizer["details"]["yulDetails"]["optimizerSteps"].asString() ==
OptimiserSettings::DefaultYulOptimiserSteps + ":"s + OptimiserSettings::DefaultYulOptimiserCleanupSteps
);
BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 9);
BOOST_CHECK(optimizer["runs"].asUInt() == 600);
}

View File

@ -428,7 +428,7 @@ BOOST_AUTO_TEST_CASE(invalid_options_input_modes_combinations)
BOOST_AUTO_TEST_CASE(default_optimiser_sequence)
{
auto const& commandLineOptions = parseCommandLine({"solc", "contract.sol", "--optimize"});
CommandLineOptions const& commandLineOptions = parseCommandLine({"solc", "contract.sol", "--optimize"});
BOOST_CHECK_EQUAL(commandLineOptions.optimiserSettings().yulOptimiserSteps, OptimiserSettings::DefaultYulOptimiserSteps);
BOOST_CHECK_EQUAL(commandLineOptions.optimiserSettings().yulOptimiserCleanupSteps, OptimiserSettings::DefaultYulOptimiserCleanupSteps);
}
@ -436,26 +436,26 @@ BOOST_AUTO_TEST_CASE(default_optimiser_sequence)
BOOST_AUTO_TEST_CASE(valid_optimiser_sequences)
{
vector<string> validSequenceInputs {
":", // Empty optimizaiton sequence and empty cleanup sequence
":fDn", // Empty optimization sequence and specified cleanup sequence
"dhfoDgvulfnTUtnIf:", // Specified optimizaiton sequence and empty cleanup sequence
"dhfoDgvulfnTUtnIf:fDn", // Specified optimization sequence and cleanup sequence
":", // Empty optimization sequence and empty cleanup sequence
":fDn", // Empty optimization sequence and specified cleanup sequence
"dhfoDgvulfnTUtnIf:", // Specified optimization sequence and empty cleanup sequence
"dhfoDgvulfnTUtnIf:fDn", // Specified optimization sequence and cleanup sequence
"dhfo[Dgvulfn]TUtnIf:f[D]n" // Specified and nested optimization and cleanup sequence
};
vector<tuple<string, string>> expectedParsedSequences {
{ "", "" },
{ "", "fDn"},
{ "dhfoDgvulfnTUtnIf", ""},
{ "dhfoDgvulfnTUtnIf", "fDn"},
{ "dhfo[Dgvulfn]TUtnIf", "f[D]n"}
{"", ""},
{"", "fDn"},
{"dhfoDgvulfnTUtnIf", ""},
{"dhfoDgvulfnTUtnIf", "fDn"},
{"dhfo[Dgvulfn]TUtnIf", "f[D]n"}
};
BOOST_CHECK_EQUAL(validSequenceInputs.size(), expectedParsedSequences.size());
for (size_t i = 0; i < validSequenceInputs.size(); ++i)
{
auto const& commandLineOptions = parseCommandLine({"solc", "contract.sol", "--optimize", "--yul-optimizations=" + validSequenceInputs[i]});
CommandLineOptions const& commandLineOptions = parseCommandLine({"solc", "contract.sol", "--optimize", "--yul-optimizations=" + validSequenceInputs[i]});
auto const& [expectedYulOptimiserSteps, expectedYulCleanupSteps] = expectedParsedSequences[i];
BOOST_CHECK_EQUAL(commandLineOptions.optimiserSettings().yulOptimiserSteps, expectedYulOptimiserSteps);
BOOST_CHECK_EQUAL(commandLineOptions.optimiserSettings().yulOptimiserCleanupSteps, expectedYulCleanupSteps);