From cf5c13f9c7b2a783e64d35f9ff8325c1d20ae3f7 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 17 Jul 2017 12:12:00 +0100 Subject: [PATCH] Introduce global optimiser settings. --- docs/metadata.rst | 17 +++- libevmasm/Assembly.h | 3 +- libsolidity/CMakeLists.txt | 1 + libsolidity/codegen/Compiler.cpp | 11 ++- libsolidity/codegen/Compiler.h | 9 +- libsolidity/codegen/CompilerContext.cpp | 15 +++ libsolidity/codegen/CompilerContext.h | 6 +- libsolidity/codegen/ContractCompiler.cpp | 10 +- libsolidity/codegen/ContractCompiler.h | 10 +- libsolidity/codegen/ExpressionCompiler.cpp | 2 +- libsolidity/codegen/ExpressionCompiler.h | 9 +- libsolidity/interface/CompilerStack.cpp | 46 +++++++-- libsolidity/interface/CompilerStack.h | 8 +- libsolidity/interface/OptimiserSettings.h | 105 +++++++++++++++++++++ test/libsolidity/Assembly.cpp | 5 +- 15 files changed, 218 insertions(+), 39 deletions(-) create mode 100644 libsolidity/interface/OptimiserSettings.h diff --git a/docs/metadata.rst b/docs/metadata.rst index c0613809a..0e6282ddb 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -66,10 +66,23 @@ explanatory purposes. { // Required for Solidity: Sorted list of remappings remappings: [ ":g/dir" ], - // Optional: Optimizer settings (enabled defaults to false) + // Optional: Optimizer settings. The fields "enabled" and "runs" are deprecated + // and are only given for backwards-compatibility. optimizer: { enabled: true, - runs: 500 + runs: 500, + details: { + // peephole defaults to "true" + peephole: true, + // jumpdestRemover defaults to "true" + jumpdestRemover: true, + orderLiterals: false, + deduplicate: false, + cse: false, + constantOptimizer: false, + yul: false, + yulDetails: {} + } }, // Required for Solidity: File and name of the contract or library this // metadata is created for. diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index f3098f282..b21077e28 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -113,7 +113,8 @@ public: size_t expectedExecutionsPerDeployment = 200; }; - /// Execute optimisation passes as defined by @a _settings and return the optimised assembly. + /// Modify and return the current assembly such that creation and execution gas usage + /// is optimised according to the settings in @a _settings. Assembly& optimise(OptimiserSettings const& _settings); /// Modify (if @a _enable is set) and return the current assembly such that creation and diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 121c7330c..36125c2ef 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -86,6 +86,7 @@ set(sources interface/GasEstimator.h interface/Natspec.cpp interface/Natspec.h + interface/OptimiserSettings.h interface/ReadFile.h interface/StandardCompiler.cpp interface/StandardCompiler.h diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 72efed33a..6b2bf3761 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -35,16 +35,21 @@ void Compiler::compileContract( bytes const& _metadata ) { - ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns); + ContractCompiler runtimeCompiler( + nullptr, + m_runtimeContext, + m_optimiserSettings.runOrderLiterals, + m_optimiserSettings.expectedExecutionsPerDeployment + ); runtimeCompiler.compileContract(_contract, _otherCompilers); m_runtimeContext.appendAuxiliaryData(_metadata); // This might modify m_runtimeContext because it can access runtime functions at // creation time. - ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize, 1); + ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimiserSettings.runOrderLiterals, 1); m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); - m_context.optimise(m_optimize, m_optimizeRuns); + m_context.optimise(m_optimiserSettings); } std::shared_ptr Compiler::runtimeAssemblyPtr() const diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 53f3108a2..34bae7a27 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -23,6 +23,7 @@ #pragma once #include +#include #include #include #include @@ -34,9 +35,8 @@ namespace solidity { class Compiler { public: - explicit Compiler(langutil::EVMVersion _evmVersion = langutil::EVMVersion{}, bool _optimize = false, unsigned _runs = 200): - m_optimize(_optimize), - m_optimizeRuns(_runs), + explicit Compiler(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + m_optimiserSettings(std::move(_optimiserSettings)), m_runtimeContext(_evmVersion), m_context(_evmVersion, &m_runtimeContext) { } @@ -78,8 +78,7 @@ public: eth::AssemblyItem functionEntryLabel(FunctionDefinition const& _function) const; private: - bool const m_optimize; - unsigned const m_optimizeRuns; + OptimiserSettings const m_optimiserSettings; CompilerContext m_runtimeContext; size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. CompilerContext m_context; diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 8085751bc..6c090d1fe 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -447,6 +447,21 @@ void CompilerContext::updateSourceLocation() m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); } +eth::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings) +{ + // Constructing it this way so that we notice changes in the fields. + eth::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, m_evmVersion, 0}; + asmSettings.isCreation = true; + asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; + asmSettings.runPeephole = _settings.runPeephole; + asmSettings.runDeduplicate = _settings.runDeduplicate; + asmSettings.runCSE = _settings.runCSE; + asmSettings.runConstantOptimiser = _settings.runConstantOptimiser; + asmSettings.expectedExecutionsPerDeployment = _settings.expectedExecutionsPerDeployment; + asmSettings.evmVersion = m_evmVersion; + return asmSettings; +} + eth::AssemblyItem CompilerContext::FunctionCompilationQueue::entryLabel( Declaration const& _declaration, CompilerContext& _context diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index e730b46e0..8a5b89907 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -27,6 +27,8 @@ #include #include +#include + #include #include #include @@ -221,7 +223,7 @@ public: void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); } /// Run optimisation step. - void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, m_evmVersion, true, _runs); } + void optimise(OptimiserSettings const& _settings) { m_asm->optimise(translateOptimiserSettings(_settings)); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. CompilerContext* runtimeContext() const { return m_runtimeContext; } @@ -271,6 +273,8 @@ private: /// Updates source location set in the assembly. void updateSourceLocation(); + eth::Assembly::OptimiserSettings translateOptimiserSettings(OptimiserSettings const& _settings); + /** * Helper class that manages function labels and ensures that referenced functions are * compiled in a specific order. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index beaa95ae0..d516f7918 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -391,7 +391,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac sortedIDs.emplace_back(it.first); } std::sort(sortedIDs.begin(), sortedIDs.end()); - appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimise_runs); + appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimiseRuns); } m_context << notFound; @@ -484,7 +484,7 @@ void ContractCompiler::initializeStateVariables(ContractDefinition const& _contr solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); for (VariableDeclaration const* variable: _contract.stateVariables()) if (variable->value() && !variable->isConstant()) - ExpressionCompiler(m_context, m_optimise).appendStateVariableInitialization(*variable); + ExpressionCompiler(m_context, m_optimiseOrderLiterals).appendStateVariableInitialization(*variable); } bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) @@ -497,9 +497,9 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) m_continueTags.clear(); if (_variableDeclaration.isConstant()) - ExpressionCompiler(m_context, m_optimise).appendConstStateVariableAccessor(_variableDeclaration); + ExpressionCompiler(m_context, m_optimiseOrderLiterals).appendConstStateVariableAccessor(_variableDeclaration); else - ExpressionCompiler(m_context, m_optimise).appendStateVariableAccessor(_variableDeclaration); + ExpressionCompiler(m_context, m_optimiseOrderLiterals).appendStateVariableAccessor(_variableDeclaration); return false; } @@ -1053,7 +1053,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) { - ExpressionCompiler expressionCompiler(m_context, m_optimise); + ExpressionCompiler expressionCompiler(m_context, m_optimiseOrderLiterals); expressionCompiler.compile(_expression); if (_targetType) CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 9ab006f6e..35615b23a 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -38,9 +38,9 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise, size_t _optimise_runs = 200): - m_optimise(_optimise), - m_optimise_runs(_optimise_runs), + explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimiseOrderLiterals, size_t _optimiseRuns): + m_optimiseOrderLiterals(_optimiseOrderLiterals), + m_optimiseRuns(_optimiseRuns), m_runtimeCompiler(_runtimeCompiler), m_context(_context) { @@ -130,8 +130,8 @@ private: /// Sets the stack height for the visited loop. void storeStackHeight(ASTNode const* _node); - bool const m_optimise; - size_t const m_optimise_runs = 200; + bool const m_optimiseOrderLiterals; + size_t const m_optimiseRuns = 200; /// Pointer to the runtime compiler in case this is a creation compiler. ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 69e66066a..c126e61dd 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -449,7 +449,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) { return dynamic_cast(&_e) || _e.annotation().type->category() == Type::Category::RationalNumber; }; - bool swap = m_optimize && TokenTraits::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); + bool swap = m_optimiseOrderLiterals && TokenTraits::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); if (swap) { leftExpression.accept(*this); diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 01538b4e5..615636d74 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -55,11 +55,8 @@ class ArrayType; class ExpressionCompiler: private ASTConstVisitor { public: - /// Appends code for a State Variable accessor function - static void appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize = false); - - explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimize = false): - m_optimize(_optimize), m_context(_compilerContext) {} + explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimiseOrderLiterals = false): + m_optimiseOrderLiterals(_optimiseOrderLiterals), m_context(_compilerContext) {} /// Compile the given @a _expression and leave its value on the stack. void compile(Expression const& _expression); @@ -127,7 +124,7 @@ private: /// @returns the CompilerUtils object containing the current context. CompilerUtils utils(); - bool m_optimize; + bool m_optimiseOrderLiterals; CompilerContext& m_context; std::unique_ptr m_currentLValue; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index ca958ff96..8911523b2 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -108,11 +108,17 @@ void CompilerStack::setLibraries(std::map const& _libraries) } void CompilerStack::setOptimiserSettings(bool _optimize, unsigned _runs) +{ + OptimiserSettings settings = _optimize ? OptimiserSettings::enabled() : OptimiserSettings::minimal(); + settings.expectedExecutionsPerDeployment = _runs; + setOptimiserSettings(std::move(settings)); +} + +void CompilerStack::setOptimiserSettings(OptimiserSettings _settings) { if (m_stackState >= ParsingSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set optimiser settings before parsing.")); - m_optimize = _optimize; - m_optimizeRuns = _runs; + m_optimiserSettings = std::move(_settings); } void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources) @@ -146,8 +152,7 @@ void CompilerStack::reset(bool _keepSources) m_unhandledSMTLib2Queries.clear(); m_libraries.clear(); m_evmVersion = langutil::EVMVersion(); - m_optimize = false; - m_optimizeRuns = 200; + m_optimiserSettings = OptimiserSettings::minimal(); m_globalContext.reset(); m_scopes.clear(); m_sourceOrder.clear(); @@ -840,7 +845,7 @@ void CompilerStack::compileContract( Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); - shared_ptr compiler = make_shared(m_evmVersion, m_optimize, m_optimizeRuns); + shared_ptr compiler = make_shared(m_evmVersion, m_optimiserSettings); compiledContract.compiler = compiler; string metadata = createMetadata(compiledContract); @@ -953,8 +958,35 @@ string CompilerStack::createMetadata(Contract const& _contract) const meta["sources"][s.first]["urls"].append("bzzr://" + toHex(s.second.swarmHash().asBytes())); } } - meta["settings"]["optimizer"]["enabled"] = m_optimize; - meta["settings"]["optimizer"]["runs"] = m_optimizeRuns; + + static_assert(sizeof(m_optimiserSettings.expectedExecutionsPerDeployment) <= sizeof(Json::LargestUInt), "Invalid word size."); + solAssert(static_cast(m_optimiserSettings.expectedExecutionsPerDeployment) < std::numeric_limits::max(), ""); + meta["settings"]["optimizer"]["runs"] = Json::Value(Json::LargestUInt(m_optimiserSettings.expectedExecutionsPerDeployment)); + + /// Backwards compatibility: If set to one of the default settings, do not provide details. + OptimiserSettings settingsWithoutRuns = m_optimiserSettings; + // reset to default + settingsWithoutRuns.expectedExecutionsPerDeployment = OptimiserSettings::minimal().expectedExecutionsPerDeployment; + if (settingsWithoutRuns == OptimiserSettings::minimal()) + meta["settings"]["optimizer"]["enabled"] = false; + else if (settingsWithoutRuns == OptimiserSettings::enabled()) + meta["settings"]["optimizer"]["enabled"] = true; + else + { + Json::Value details{Json::objectValue}; + + details["orderLiterals"] = m_optimiserSettings.runOrderLiterals; + details["jumpdestRemover"] = m_optimiserSettings.runJumpdestRemover; + details["peephole"] = m_optimiserSettings.runPeephole; + details["deduplicate"] = m_optimiserSettings.runDeduplicate; + details["cse"] = m_optimiserSettings.runCSE; + details["constantOptimizer"] = m_optimiserSettings.runConstantOptimiser; + details["yul"] = m_optimiserSettings.runYulOptimiser; + details["yulDetails"] = Json::objectValue; + + meta["settings"]["optimizer"]["details"] = std::move(details); + } + meta["settings"]["evmVersion"] = m_evmVersion.name(); meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] = _contract.contract->annotation().canonicalName; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index ffae921ab..c54be261e 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -24,6 +24,7 @@ #pragma once #include +#include #include #include @@ -128,6 +129,10 @@ public: /// Must be set before parsing. void setOptimiserSettings(bool _optimize, unsigned _runs = 200); + /// Changes the optimiser settings. + /// Must be set before parsing. + void setOptimiserSettings(OptimiserSettings _settings); + /// Set the EVM version used before running compile. /// When called without an argument it will revert to the default version. /// Must be set before parsing. @@ -342,8 +347,7 @@ private: ) const; ReadCallback::Callback m_readFile; - bool m_optimize = false; - unsigned m_optimizeRuns = 200; + OptimiserSettings m_optimiserSettings; langutil::EVMVersion m_evmVersion; std::set m_requestedContractNames; std::map m_libraries; diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h new file mode 100644 index 000000000..aae5fa1ce --- /dev/null +++ b/libsolidity/interface/OptimiserSettings.h @@ -0,0 +1,105 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author Alex Beregszaszi + * @date 2017 + * Helper class for optimiser settings. + */ + +#pragma once + +#include + +namespace dev +{ +namespace solidity +{ + +struct OptimiserSettings +{ + /// No optimisations at all - not recommended. + static OptimiserSettings none() + { + return {}; + } + /// Minimal optimisations: Peephole and jumpdest remover + static OptimiserSettings minimal() + { + OptimiserSettings s = none(); + s.runJumpdestRemover = true; + s.runPeephole = true; + return s; + } + /// Standard optimisations. + static OptimiserSettings enabled() + { + OptimiserSettings s; + s.runOrderLiterals = true; + s.runJumpdestRemover = true; + s.runPeephole = true; + s.runDeduplicate = true; + s.runCSE = true; + s.runConstantOptimiser = true; + // The only disabled one + s.runYulOptimiser = false; + s.expectedExecutionsPerDeployment = 200; + return s; + } + /// Standard optimisations plus yul optimiser. + static OptimiserSettings full() + { + OptimiserSettings s = enabled(); + s.runYulOptimiser = true; + return s; + } + + bool operator==(OptimiserSettings const& _other) const + { + return + runOrderLiterals == _other.runOrderLiterals && + runJumpdestRemover == _other.runJumpdestRemover && + runPeephole == _other.runPeephole && + runDeduplicate == _other.runDeduplicate && + runCSE == _other.runCSE && + runConstantOptimiser == _other.runConstantOptimiser && + runYulOptimiser == _other.runYulOptimiser && + expectedExecutionsPerDeployment == _other.expectedExecutionsPerDeployment; + } + + /// Move literals to the right of commutative binary operators during code generation. + /// This helps exploiting associativity. + bool runOrderLiterals = false; + /// Non-referenced jump destination remover. + bool runJumpdestRemover = false; + /// Peephole optimizer + bool runPeephole = false; + /// Assembly block deduplicator + bool runDeduplicate = false; + /// Common subexpression eliminator based on assembly items. + bool runCSE = false; + /// Constant optimizer, which tries to find better representations that satisfy the given + /// size/cost-trade-off. + bool runConstantOptimiser = false; + /// Yul optimiser with default settings. Will only run on certain parts of the code for now. + bool runYulOptimiser = false; + /// 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/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index b5a1797b0..ea685c07c 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -84,7 +84,10 @@ eth::AssemblyItems compileContract(std::shared_ptr _sourceCode) for (ASTPointer const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { - Compiler compiler(dev::test::Options::get().evmVersion()); + Compiler compiler( + dev::test::Options::get().evmVersion(), + dev::test::Options::get().optimize ? OptimiserSettings::enabled() : OptimiserSettings::minimal() + ); compiler.compileContract(*contract, map>{}, bytes()); return compiler.runtimeAssemblyItems();