diff --git a/docs/internals/optimizer.rst b/docs/internals/optimizer.rst index 6650e79b5..b752a0766 100644 --- a/docs/internals/optimizer.rst +++ b/docs/internals/optimizer.rst @@ -323,22 +323,8 @@ on the individual steps and their sequence below. Selecting Optimizations ----------------------- -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. - -The colon delimiter ``:`` is used to supply a custom clean up sequence in order to replace the -default (``fDnTOc``) one, which is run after the stack compressor when using the legacy EVM -code transform. - -Available abbreviations are listed in the :ref:`Yul optimizer docs `. +Detailed information regrading the optimization sequence as well a list of abbreviations is +available in the :ref:`Yul optimizer docs `. Preprocessing ------------- diff --git a/docs/yul.rst b/docs/yul.rst index df409a3ac..31180b432 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -1245,24 +1245,25 @@ In Solidity mode, the Yul optimizer is activated together with the regular optim Optimization Step Sequence -------------------------- -By default the Yul 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: +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:: sh +.. 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. -By enclosing part of the sequence in square brackets (``[]``) you tell the optimizer to repeatedly -apply that part until it no longer improves the size of the resulting assembly. -You can use brackets multiple times in a single sequence but they cannot be nested. +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. -The colon delimiter (``:``) in the example is used to specify a custom cleanup sequence, which is run after -the main part of the sequence before the delimiter (and, when using the legacy EVM code transform, -only *after* the stack compressor). If no such delimiter is present, the default cleanup sequence (``fDnTOc``) -is run after the supplied sequence. +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: diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 702d5a0d2..167ef6e28 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1540,6 +1540,7 @@ string CompilerStack::createMetadata(Contract const& _contract, bool _forIR) con details["yulDetails"] = Json::objectValue; details["yulDetails"]["stackAllocation"] = m_optimiserSettings.optimizeStackAllocation; details["yulDetails"]["optimizerSteps"] = m_optimiserSettings.yulOptimiserSteps; + details["yulDetails"]["optimizerCleanupSteps"] = m_optimiserSettings.yulOptimiserCleanupSteps; } meta["settings"]["optimizer"]["details"] = std::move(details); diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index 82324d3f7..bae343f2d 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -148,10 +148,9 @@ struct OptimiserSettings /// them just by setting this to an empty string. Set @a runYulOptimiser to false if you want /// no optimisations. std::string yulOptimiserSteps = DefaultYulOptimiserSteps; - /// Sequence of clean up optimisation steps after the above (default or user supplied) set of - /// optimisation steps is completed. Note that if the string is left empty, there will still - /// be hard-coded optimisation steps that will run regardless. Set @a runYulOptimiser to false - /// if you want no optimisations. + /// Sequence of clean-up optimisation steps after yulOptimiserSteps is run. Note that if the string + /// is left empty, there will still be hard-coded optimisation steps that will run regardless. + /// Set @a runYulOptimiser to false if you want no optimisations. std::string yulOptimiserCleanupSteps = DefaultYulOptimiserCleanupSteps; /// This specifies an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 7ccb71d78..129ed845a 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -490,7 +490,7 @@ std::optional checkOptimizerDetailSteps(Json::Value const& _details ); } - std::string const fullSequence = _details[_name].asString(); + string const fullSequence = _details[_name].asString(); auto const delimiterPos = fullSequence.find(":"); _optimiserSetting = fullSequence.substr(0, delimiterPos); diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index ae32b25c3..7b75ea966 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -141,7 +141,7 @@ void OptimiserSuite::run( stackCompressorMaxIterations ); - // Run the user supplied (otherwise default) clean up sequence + // Run the user-supplied clean up sequence suite.runSequence(_optimisationCleanupSequence, ast); suite.runSequence("g", ast); @@ -316,8 +316,9 @@ void OptimiserSuite::validateSequence(string_view _stepAbbreviations) assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets"); break; case ':': - assertThrow(nestingLevel == 0, OptimizerException, "Cleanup delimiter may only be placed at nesting level zero"); - assertThrow(++colonDelimiters <=1, OptimizerException, "Too many colon delimiters"); + ++colonDelimiters; + assertThrow(nestingLevel == 0, OptimizerException, "Cleanup sequence delimiter cannot be placed inside the brackets"); + assertThrow(colonDelimiters <=1, OptimizerException, "Too many cleanup sequence delimiters"); break; default: { diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index ce2d4d044..18eef374c 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -267,7 +267,7 @@ OptimiserSettings CommandLineOptions::optimiserSettings() const if (optimizer.yulSteps.has_value()) { - std::string const fullSequence = optimizer.yulSteps.value(); + string const fullSequence = optimizer.yulSteps.value(); auto const delimiterPos = fullSequence.find(":"); settings.yulOptimiserSteps = fullSequence.substr(0, delimiterPos); diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/output.json index 41c8a307f..f44ca7e2e 100644 --- a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/output.json +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/output.json @@ -3,8 +3,8 @@ [ { "component": "general", - "formattedMessage": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Cleanup delimiter may only be placed at nesting level zero", - "message": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Cleanup delimiter may only be placed at nesting level zero", + "formattedMessage": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Cleanup sequence delimiter cannot be placed inside the brackets", + "message": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Cleanup sequence delimiter cannot be placed inside the brackets", "severity": "error", "type": "JSONError" } diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/output.json index b6778a492..16cba14d7 100644 --- a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/output.json +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/output.json @@ -3,8 +3,8 @@ [ { "component": "general", - "formattedMessage": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Too many colon delimiters", - "message": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Too many colon delimiters", + "formattedMessage": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Too many cleanup sequence delimiters", + "message": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Too many cleanup sequence delimiters", "severity": "error", "type": "JSONError" } diff --git a/test/cmdlineTests/yul_optimizer_steps_invalid_nested_delimiter/err b/test/cmdlineTests/yul_optimizer_steps_invalid_nested_delimiter/err index cb7b570f1..807212793 100644 --- a/test/cmdlineTests/yul_optimizer_steps_invalid_nested_delimiter/err +++ b/test/cmdlineTests/yul_optimizer_steps_invalid_nested_delimiter/err @@ -1 +1 @@ -Invalid optimizer step sequence in --yul-optimizations: Cleanup delimiter may only be placed at nesting level zero +Invalid optimizer step sequence in --yul-optimizations: Cleanup sequence delimiter cannot be placed inside the brackets diff --git a/test/cmdlineTests/yul_optimizer_steps_multiple_delimiters/err b/test/cmdlineTests/yul_optimizer_steps_multiple_delimiters/err index 70b1b2c21..5aa76bd43 100644 --- a/test/cmdlineTests/yul_optimizer_steps_multiple_delimiters/err +++ b/test/cmdlineTests/yul_optimizer_steps_multiple_delimiters/err @@ -1 +1 @@ -Invalid optimizer step sequence in --yul-optimizations: Too many colon delimiters +Invalid optimizer step sequence in --yul-optimizations: Too many cleanup sequence delimiters diff --git a/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/input.sol b/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/input.sol index 45fec390e..fc9ba5baf 100644 --- a/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/input.sol +++ b/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/input.sol @@ -6,8 +6,8 @@ contract C { constructor() payable { assembly ("memory-safe") { let a := 0 - revert(0, a) // Without the cleanup sequence this will not be simplified to ``revert(a, a)``. + revert(0, a) } } } diff --git a/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/input.sol b/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/input.sol index 45fec390e..fc9ba5baf 100644 --- a/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/input.sol +++ b/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/input.sol @@ -6,8 +6,8 @@ contract C { constructor() payable { assembly ("memory-safe") { let a := 0 - revert(0, a) // Without the cleanup sequence this will not be simplified to ``revert(a, a)``. + revert(0, a) } } } diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 319a60885..9480e7049 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -1244,10 +1244,11 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) BOOST_CHECK(optimizer["details"]["yulDetails"].isObject()); BOOST_CHECK( util::convertContainer>(optimizer["details"]["yulDetails"].getMemberNames()) == - (set{"stackAllocation", "optimizerSteps"}) + (set{"stackAllocation", "optimizerSteps", "optimizerCleanupSteps"}) ); BOOST_CHECK(optimizer["details"]["yulDetails"]["stackAllocation"].asBool() == true); BOOST_CHECK(optimizer["details"]["yulDetails"]["optimizerSteps"].asString() == OptimiserSettings::DefaultYulOptimiserSteps); + BOOST_CHECK(optimizer["details"]["yulDetails"]["optimizerCleanupSteps"].asString() == OptimiserSettings::DefaultYulOptimiserCleanupSteps); BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 9); BOOST_CHECK(optimizer["runs"].asUInt() == 600); } diff --git a/test/solc/CommandLineParser.cpp b/test/solc/CommandLineParser.cpp index 47eda71ad..74cb79cd6 100644 --- a/test/solc/CommandLineParser.cpp +++ b/test/solc/CommandLineParser.cpp @@ -426,6 +426,58 @@ BOOST_AUTO_TEST_CASE(invalid_options_input_modes_combinations) } } +BOOST_AUTO_TEST_CASE(default_optimiser_sequence) +{ + auto const& commandLineOptions = parseCommandLine({"solc", "contract.sol", "--optimize"}); + BOOST_CHECK_EQUAL(commandLineOptions.optimiserSettings().yulOptimiserSteps, OptimiserSettings::DefaultYulOptimiserSteps); + BOOST_CHECK_EQUAL(commandLineOptions.optimiserSettings().yulOptimiserCleanupSteps, OptimiserSettings::DefaultYulOptimiserCleanupSteps); +} + +BOOST_AUTO_TEST_CASE(valid_optimiser_sequences) +{ + vector 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 + "dhfo[Dgvulfn]TUtnIf:f[D]n" // Specified and nested optimization and cleanup sequence + }; + + vector> expectedParsedSequences { + { "", "" }, + { "", "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]}); + auto const& [expectedYulOptimiserSteps, expectedYulCleanupSteps] = expectedParsedSequences[i]; + BOOST_CHECK_EQUAL(commandLineOptions.optimiserSettings().yulOptimiserSteps, expectedYulOptimiserSteps); + BOOST_CHECK_EQUAL(commandLineOptions.optimiserSettings().yulOptimiserCleanupSteps, expectedYulCleanupSteps); + } +} + +BOOST_AUTO_TEST_CASE(invalid_nested_cleanup_sequence_delimiter) +{ + vector commandLine {"solc", "contract.sol", "--optimize", "--yul-optimizations=dhfoDgvulfnTUt[nIf:fd]N"}; + string expectedMessage = "Invalid optimizer step sequence in --yul-optimizations: Cleanup sequence delimiter cannot be placed inside the brackets"; + auto hasCorrectMessage = [&](CommandLineValidationError const& _exception) { return _exception.what() == expectedMessage; }; + BOOST_CHECK_EXCEPTION(parseCommandLine(commandLine), CommandLineValidationError, hasCorrectMessage); +} + +BOOST_AUTO_TEST_CASE(too_many_cleanup_sequence_delimiters) +{ + vector commandLine {"solc", "contract.sol", "--optimize", "--yul-optimizations=dhfoDgvulfnTU:tnIf:fdN"}; + string expectedMessage = "Invalid optimizer step sequence in --yul-optimizations: Too many cleanup sequence delimiters"; + auto hasCorrectMessage = [&](CommandLineValidationError const& _exception) { return _exception.what() == expectedMessage; }; + BOOST_CHECK_EXCEPTION(parseCommandLine(commandLine), CommandLineValidationError, hasCorrectMessage); +} + BOOST_AUTO_TEST_SUITE_END() } // namespace solidity::frontend::test