diff --git a/Changelog.md b/Changelog.md index 0e45c2ea5..84aa53023 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ 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. Bugfixes: diff --git a/docs/internals/optimizer.rst b/docs/internals/optimizer.rst index 3c12f0adf..c9d10a8f6 100644 --- a/docs/internals/optimizer.rst +++ b/docs/internals/optimizer.rst @@ -281,60 +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 ----------------------- -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: +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' + 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. -Available abbreviations are listed in the :ref:`Yul optimizer docs `. +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 ------------- diff --git a/docs/yul.rst b/docs/yul.rst index 7ce010169..f6109a125 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -1245,65 +1245,8 @@ 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: - -.. code-block:: sh - - solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul' - -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 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 `. .. _erc20yul: diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index f1cc5b6dd..948502e69 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -547,6 +547,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _ _object, _optimiserSettings.optimizeStackAllocation, _optimiserSettings.yulOptimiserSteps, + _optimiserSettings.yulOptimiserCleanupSteps, isCreation? nullopt : make_optional(_optimiserSettings.expectedExecutionsPerDeployment), _externalIdentifiers ); diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 702d5a0d2..109650130 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1539,7 +1539,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"]["optimizerSteps"] = m_optimiserSettings.yulOptimiserSteps + ":" + m_optimiserSettings.yulOptimiserCleanupSteps; } meta["settings"]["optimizer"]["details"] = std::move(details); diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index e233048c1..bae343f2d 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -59,6 +59,8 @@ struct OptimiserSettings "]" "jmul[jul] VcTOcul jmul"; // Make source short and pretty + static char constexpr DefaultYulOptimiserCleanupSteps[] = "fDnTOc"; + /// No optimisations at all - not recommended. static OptimiserSettings none() { @@ -146,6 +148,10 @@ 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 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. size_t expectedExecutionsPerDeployment = 200; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index c6cd741c1..0af55df16 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -472,7 +472,7 @@ std::optional checkOptimizerDetail(Json::Value const& _details, std return {}; } -std::optional checkOptimizerDetailSteps(Json::Value const& _details, std::string const& _name, string& _setting) +std::optional checkOptimizerDetailSteps(Json::Value const& _details, std::string const& _name, string& _optimiserSetting, string& _cleanupSetting) { if (_details.isMember(_name)) { @@ -490,7 +490,14 @@ std::optional checkOptimizerDetailSteps(Json::Value const& _details ); } - _setting = _details[_name].asString(); + string const fullSequence = _details[_name].asString(); + auto const delimiterPos = fullSequence.find(":"); + _optimiserSetting = fullSequence.substr(0, delimiterPos); + + 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"); @@ -616,7 +623,7 @@ std::variant parseOptimizerSettings(Json::Value return *result; if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation)) return *error; - if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps)) + if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps, settings.yulOptimiserCleanupSteps)) return *error; } } diff --git a/libyul/YulStack.cpp b/libyul/YulStack.cpp index 3bca7288c..7553ee469 100644 --- a/libyul/YulStack.cpp +++ b/libyul/YulStack.cpp @@ -209,6 +209,7 @@ void YulStack::optimize(Object& _object, bool _isCreation) _object, m_optimiserSettings.optimizeStackAllocation, m_optimiserSettings.yulOptimiserSteps, + m_optimiserSettings.yulOptimiserCleanupSteps, _isCreation ? nullopt : make_optional(m_optimiserSettings.expectedExecutionsPerDeployment), {} ); diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 181c9bdcb..c71a44001 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -93,6 +93,7 @@ void OptimiserSuite::run( Object& _object, bool _optimizeStackAllocation, string_view _optimisationSequence, + string_view _optimisationCleanupSequence, optional _expectedExecutionsPerDeployment, set const& _externallyUsedIdentifiers ) @@ -139,7 +140,13 @@ void OptimiserSuite::run( _optimizeStackAllocation, stackCompressorMaxIterations ); - suite.runSequence("fDnTOc g", ast); + + // 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) { @@ -296,6 +303,7 @@ map const& OptimiserSuite::stepAbbreviationToNameMap() void OptimiserSuite::validateSequence(string_view _stepAbbreviations) { int8_t nestingLevel = 0; + int8_t colonDelimiters = 0; for (char abbreviation: _stepAbbreviations) switch (abbreviation) { @@ -310,6 +318,11 @@ void OptimiserSuite::validateSequence(string_view _stepAbbreviations) nestingLevel--; assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets"); break; + case ':': + ++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: { yulAssert( diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 3c85fc559..2db6288c0 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -51,7 +51,7 @@ public: /// Special characters that do not represent optimiser steps but are allowed in abbreviation sequences. /// Some of them (like whitespace) are ignored, others (like brackets) are a part of the syntax. - static constexpr char NonStepAbbreviations[] = " \n[]"; + static constexpr char NonStepAbbreviations[] = " \n[]:"; enum class Debug { @@ -68,6 +68,7 @@ public: Object& _object, bool _optimizeStackAllocation, std::string_view _optimisationSequence, + std::string_view _optimisationCleanupSequence, std::optional _expectedExecutionsPerDeployment, std::set const& _externallyUsedIdentifiers = {} ); diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index 2c765d0e3..c6ddfec67 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -266,7 +266,16 @@ OptimiserSettings CommandLineOptions::optimiserSettings() const settings.expectedExecutionsPerDeployment = optimizer.expectedExecutionsPerDeployment.value(); if (optimizer.yulSteps.has_value()) - settings.yulOptimiserSteps = optimizer.yulSteps.value(); + { + string const fullSequence = optimizer.yulSteps.value(); + auto const delimiterPos = fullSequence.find(":"); + settings.yulOptimiserSteps = fullSequence.substr(0, delimiterPos); + + if (delimiterPos != string::npos) + settings.yulOptimiserCleanupSteps = fullSequence.substr(delimiterPos + 1); + else + solAssert(settings.yulOptimiserCleanupSteps == OptimiserSettings::DefaultYulOptimiserCleanupSteps); + } return settings; } diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/args b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/args new file mode 100644 index 000000000..a905f1fe6 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/args @@ -0,0 +1 @@ +--pretty-json --json-indent 4 diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/in.sol b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/in.sol new file mode 100644 index 000000000..9e5350072 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/in.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + function f() public pure {} +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/input.json new file mode 100644 index 000000000..5be769f76 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": { + "A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/in.sol"]} + }, + "settings": { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": "dhfoDgvulfnTUtnIf\n[ xar:rscLM\n]\njmuljuljul VcTOcul jmul" + } + } + } + } +} 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 new file mode 100644 index 000000000..f44ca7e2e --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nested_delimiter/output.json @@ -0,0 +1,12 @@ +{ + "errors": + [ + { + "component": "general", + "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/args b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/args new file mode 100644 index 000000000..a905f1fe6 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/args @@ -0,0 +1 @@ +--pretty-json --json-indent 4 diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/in.sol b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/in.sol new file mode 100644 index 000000000..9e5350072 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/in.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + function f() public pure {} +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/input.json new file mode 100644 index 000000000..0bddd9d9d --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": { + "A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/in.sol"]} + }, + "settings": { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": "dhfoDgvu:lfnTUtnIf\n[ xarrscLM\n]\njmuljulj:ul VcTOcul jmul" + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/output.json new file mode 100644 index 000000000..16cba14d7 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_mutliple_delimiters/output.json @@ -0,0 +1,12 @@ +{ + "errors": + [ + { + "component": "general", + "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/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/args b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/args new file mode 100644 index 000000000..a905f1fe6 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/args @@ -0,0 +1 @@ +--pretty-json --json-indent 4 diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/in.sol b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/in.sol new file mode 100644 index 000000000..9e5350072 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/in.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + function f() public pure {} +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/input.json new file mode 100644 index 000000000..b557a9662 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": { + "A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/in.sol"]} + }, + "settings": { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": "dhfoDgvulfnTUtnIf\n[ xarrscLM\n]\njmuljuljul VcTOcul jmul:fDnTOc" + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/output.json new file mode 100644 index 000000000..acf3b74ef --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/output.json @@ -0,0 +1,9 @@ +{ + "sources": + { + "A": + { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/args b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/args new file mode 100644 index 000000000..a905f1fe6 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/args @@ -0,0 +1 @@ +--pretty-json --json-indent 4 diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/in.sol b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/in.sol new file mode 100644 index 000000000..9e5350072 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/in.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + function f() public pure {} +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/input.json new file mode 100644 index 000000000..0903ccfeb --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": { + "A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/in.sol"]} + }, + "settings": { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": "dhfoDgvulfnTUtnIf\n[ xarrscLM\n]\njmuljuljul VcTOcul jmul:" + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/output.json new file mode 100644 index 000000000..acf3b74ef --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_cleanup_sequence/output.json @@ -0,0 +1,9 @@ +{ + "sources": + { + "A": + { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/args b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/args new file mode 100644 index 000000000..a905f1fe6 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/args @@ -0,0 +1 @@ +--pretty-json --json-indent 4 diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/in.sol b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/in.sol new file mode 100644 index 000000000..9e5350072 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/in.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + function f() public pure {} +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/input.json new file mode 100644 index 000000000..4086aff9f --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": { + "A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/in.sol"]} + }, + "settings": { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": ":dhfoDgvulfnTUtnIf\n[ xarrscLM\n]\njmuljuljul VcTOcul jmul" + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/output.json new file mode 100644 index 000000000..acf3b74ef --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_optimisation_sequence/output.json @@ -0,0 +1,9 @@ +{ + "sources": + { + "A": + { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/args b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/args new file mode 100644 index 000000000..a905f1fe6 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/args @@ -0,0 +1 @@ +--pretty-json --json-indent 4 diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/in.sol b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/in.sol new file mode 100644 index 000000000..9e5350072 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/in.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract C { + function f() public pure {} +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/input.json new file mode 100644 index 000000000..4f6668222 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": { + "A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_with_cleanup_sequence/in.sol"]} + }, + "settings": { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": ":" + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/output.json new file mode 100644 index 000000000..acf3b74ef --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_with_empty_sequence/output.json @@ -0,0 +1,9 @@ +{ + "sources": + { + "A": + { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/yul_optimizer_steps_short_sequence/args b/test/cmdlineTests/yul_optimizer_steps_short_sequence/args new file mode 100644 index 000000000..ba2f47ebe --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_short_sequence/args @@ -0,0 +1 @@ +--ir-optimized --optimize --yul-optimizations iDu diff --git a/test/cmdlineTests/yul_optimizer_steps_short_sequence/input.sol b/test/cmdlineTests/yul_optimizer_steps_short_sequence/input.sol new file mode 100644 index 000000000..2d82716f0 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_short_sequence/input.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; +pragma abicoder v2; + +contract C { + constructor() payable { + assembly ("memory-safe") { + let a := 0 + revert(0, a) + } + } +} diff --git a/test/cmdlineTests/yul_optimizer_steps_short_sequence/output b/test/cmdlineTests/yul_optimizer_steps_short_sequence/output new file mode 100644 index 000000000..0a4a2c5a2 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_short_sequence/output @@ -0,0 +1,24 @@ +Optimized IR: +/// @use-src 0:"yul_optimizer_steps_short_sequence/input.sol" +object "C_8" { + code { + { + /// @src 0:80:221 "contract C {..." + mstore(64, memoryguard(0x80)) + /// @src 0:129:213 "assembly (\"memory-safe\") {..." + let usr$a := 0 + revert(usr$a, usr$a) + } + } + /// @use-src 0:"yul_optimizer_steps_short_sequence/input.sol" + object "C_8_deployed" { + code { + { + /// @src 0:80:221 "contract C {..." + mstore(64, memoryguard(0x80)) + revert(0, 0) + } + } + data ".metadata" hex"" + } +} diff --git a/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/args b/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/args new file mode 100644 index 000000000..6673a776a --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/args @@ -0,0 +1 @@ +--ir-optimized --optimize --yul-optimizations iDu: 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 new file mode 100644 index 000000000..fc9ba5baf --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/input.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; +pragma abicoder v2; + +contract C { + constructor() payable { + assembly ("memory-safe") { + let a := 0 + // 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_cleanup_sequence/output b/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/output new file mode 100644 index 000000000..01641c53b --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_with_empty_cleanup_sequence/output @@ -0,0 +1,24 @@ +Optimized IR: +/// @use-src 0:"yul_optimizer_steps_with_empty_cleanup_sequence/input.sol" +object "C_8" { + code { + { + /// @src 0:80:314 "contract C {..." + mstore(64, memoryguard(0x80)) + /// @src 0:129:306 "assembly (\"memory-safe\") {..." + let usr$a := 0 + revert(0, usr$a) + } + } + /// @use-src 0:"yul_optimizer_steps_with_empty_cleanup_sequence/input.sol" + object "C_8_deployed" { + code { + { + /// @src 0:80:314 "contract C {..." + mstore(64, memoryguard(0x80)) + revert(0, 0) + } + } + data ".metadata" hex"" + } +} diff --git a/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/args b/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/args new file mode 100644 index 000000000..b5d1d3c65 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/args @@ -0,0 +1 @@ +--ir-optimized --optimize --yul-optimizations :iDu 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 new file mode 100644 index 000000000..fc9ba5baf --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/input.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; +pragma abicoder v2; + +contract C { + constructor() payable { + assembly ("memory-safe") { + let a := 0 + // 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/output b/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/output new file mode 100644 index 000000000..b13e6e282 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_with_empty_optimization_sequence/output @@ -0,0 +1,24 @@ +Optimized IR: +/// @use-src 0:"yul_optimizer_steps_with_empty_optimization_sequence/input.sol" +object "C_8" { + code { + { + /// @src 0:80:314 "contract C {..." + mstore(64, memoryguard(0x80)) + /// @src 0:129:306 "assembly (\"memory-safe\") {..." + let usr$a := 0 + revert(0, usr$a) + } + } + /// @use-src 0:"yul_optimizer_steps_with_empty_optimization_sequence/input.sol" + object "C_8_deployed" { + code { + { + /// @src 0:80:314 "contract C {..." + mstore(64, memoryguard(0x80)) + revert(0, 0) + } + } + data ".metadata" hex"" + } +} diff --git a/test/libsolidity/Metadata.cpp b/test/libsolidity/Metadata.cpp index 9cbf73f84..a5383b544 100644 --- a/test/libsolidity/Metadata.cpp +++ b/test/libsolidity/Metadata.cpp @@ -400,6 +400,53 @@ BOOST_AUTO_TEST_CASE(metadata_revert_strings) BOOST_CHECK_EQUAL(metadata["settings"]["debug"]["revertStrings"], "strip"); } +BOOST_AUTO_TEST_CASE(metadata_optimiser_sequence) +{ + char const* sourceCode = R"( + pragma solidity >=0.0; + contract C { + } + )"; + + vector> sequences = + { + // {"", ""} + {"", ""}, + {"", "fDn"}, + {"dhfoDgvulfnTUtnIf", "" }, + {"dhfoDgvulfnTUtnIf", "fDn"} + }; + + auto check = [sourceCode](string const& _optimizerSequence, string const& _optimizerCleanupSequence) + { + OptimiserSettings optimizerSettings = OptimiserSettings::minimal(); + optimizerSettings.runYulOptimiser = true; + optimizerSettings.yulOptimiserSteps = _optimizerSequence; + optimizerSettings.yulOptimiserCleanupSteps = _optimizerCleanupSequence; + CompilerStack compilerStack; + compilerStack.setSources({{"", std::string(sourceCode)}}); + compilerStack.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); + compilerStack.setOptimiserSettings(optimizerSettings); + + BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); + + std::string const& serialisedMetadata = compilerStack.metadata("C"); + Json::Value metadata; + BOOST_REQUIRE(util::jsonParseStrict(serialisedMetadata, metadata)); + BOOST_CHECK(solidity::test::isValidMetadata(metadata)); + BOOST_CHECK(metadata["settings"]["optimizer"].isMember("details")); + BOOST_CHECK(metadata["settings"]["optimizer"]["details"].isMember("yulDetails")); + BOOST_CHECK(metadata["settings"]["optimizer"]["details"]["yulDetails"].isMember("optimizerSteps")); + + string const metadataOptimizerSteps = metadata["settings"]["optimizer"]["details"]["yulDetails"]["optimizerSteps"].asString(); + 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) { char const* sourceCode = R"( diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 319a60885..54c12d30c 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -1247,7 +1247,10 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) (set{"stackAllocation", "optimizerSteps"}) ); BOOST_CHECK(optimizer["details"]["yulDetails"]["stackAllocation"].asBool() == true); - BOOST_CHECK(optimizer["details"]["yulDetails"]["optimizerSteps"].asString() == OptimiserSettings::DefaultYulOptimiserSteps); + 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); } diff --git a/test/libyul/YulOptimizerTestCommon.cpp b/test/libyul/YulOptimizerTestCommon.cpp index 60826665a..7623efc12 100644 --- a/test/libyul/YulOptimizerTestCommon.cpp +++ b/test/libyul/YulOptimizerTestCommon.cpp @@ -339,6 +339,7 @@ YulOptimizerTestCommon::YulOptimizerTestCommon( *m_object, true, frontend::OptimiserSettings::DefaultYulOptimiserSteps, + frontend::OptimiserSettings::DefaultYulOptimiserCleanupSteps, frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment ); }}, diff --git a/test/solc/CommandLineParser.cpp b/test/solc/CommandLineParser.cpp index 47eda71ad..f2d020ae7 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) +{ + CommandLineOptions 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 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> 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) + { + 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); + } +} + +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