From cf5c13f9c7b2a783e64d35f9ff8325c1d20ae3f7 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 17 Jul 2017 12:12:00 +0100 Subject: [PATCH 1/7] 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(); From 4d10f4b4cf51c4bd7acd9c63070eee15094f71e5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 21 Feb 2019 17:39:47 +0100 Subject: [PATCH 2/7] Process and validate standard-json optimizer settings. --- docs/using-the-compiler.rst | 25 ++- libsolidity/interface/StandardCompiler.cpp | 96 ++++++++--- libsolidity/interface/StandardCompiler.h | 6 + .../input.json | 16 ++ .../output.json | 1 + .../input.json | 16 ++ .../output.json | 1 + .../input.json | 16 ++ .../output.json | 1 + test/libsolidity/StandardCompiler.cpp | 155 +++++++++++++++++- 10 files changed, 309 insertions(+), 24 deletions(-) create mode 100644 test/cmdlineTests/standard_optimizer_invalid_detail_type/input.json create mode 100644 test/cmdlineTests/standard_optimizer_invalid_detail_type/output.json create mode 100644 test/cmdlineTests/standard_optimizer_invalid_details/input.json create mode 100644 test/cmdlineTests/standard_optimizer_invalid_details/output.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 0147ff9ca..80e34aa2a 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -191,7 +191,30 @@ Input Description "enabled": true, // Optimize for how many times you intend to run the code. // Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage. - "runs": 200 + "runs": 200, + // Switch optimizer components on or off in detail. + // The "enabled" switch above provides two defaults which can be + // tweaked here. If "details" is given, "enabled" can be omitted. + "details": { + // The peephole optimizer is always on if no details are given, use details to switch it off. + "peephole": true, + // The unused jumpdest remover is always on if no details are given, use details to switch it off. + "jumpdestRemover": true, + // Sometimes re-orders literals in commutative operations. + "orderLiterals": false, + // Removes duplicate code blocks + "deduplicate": false, + // Common subexpression elimination, this is the most complicated step but + // can also provide the largest gain. + "cse": false, + // Optimize representation of literal numbers and strings in code. + "constantOptimizer": false, + // The new Yul optimizer. Mostly operates on the code of ABIEncoderV2. + // It can only be activated through the details here. + "yul": false, + // Future tuning options, currently unused. + "yulDetails": {} + } }, "evmVersion": "byzantium", // Version of the EVM to compile for. Affects type checking and code generation. Can be homestead, tangerineWhistle, spuriousDragon, byzantium or constantinople // Metadata settings (optional) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 4456a504b..b8678706e 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -292,10 +292,27 @@ boost::optional checkSettingsKeys(Json::Value const& _input) boost::optional checkOptimizerKeys(Json::Value const& _input) { - static set keys{"enabled", "runs"}; + static set keys{"details", "enabled", "runs"}; return checkKeys(_input, keys, "settings.optimizer"); } +boost::optional checkOptimizerDetailsKeys(Json::Value const& _input) +{ + static set keys{"peephole", "jumpdestRemover", "orderLiterals", "deduplicate", "cse", "constantOptimizer", "yul", "yulDetails"}; + return checkKeys(_input, keys, "settings.optimizer.details"); +} + +boost::optional checkOptimizerDetail(Json::Value const& _details, std::string const& _name, bool& _setting) +{ + if (_details.isMember(_name)) + { + if (!_details[_name].isBool()) + return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be Boolean"); + _setting = _details[_name].asBool(); + } + return {}; +} + boost::optional checkMetadataKeys(Json::Value const& _input) { if (_input.isObject() && _input.isMember("useLiteralContent") && !_input["useLiteralContent"].isBool()) @@ -351,6 +368,61 @@ boost::optional checkOutputSelection(Json::Value const& _outputSele } +boost::optional StandardCompiler::parseOptimizerSettings(Json::Value const& _jsonInput) +{ + if (auto result = checkOptimizerKeys(_jsonInput)) + return *result; + + OptimiserSettings settings = OptimiserSettings::none(); + + if (_jsonInput.isMember("enabled")) + { + if (!_jsonInput["enabled"].isBool()) + return formatFatalError("JSONError", "The \"enabled\" setting must be a Boolean."); + + settings = _jsonInput["enabled"].asBool() ? OptimiserSettings::enabled() : OptimiserSettings::minimal(); + } + + if (_jsonInput.isMember("runs")) + { + if (!_jsonInput["runs"].isUInt()) + return formatFatalError("JSONError", "The \"runs\" setting must be an unsigned number."); + settings.expectedExecutionsPerDeployment = _jsonInput["runs"].asUInt(); + } + + if (_jsonInput.isMember("details")) + { + Json::Value const& details = _jsonInput["details"]; + if (auto result = checkOptimizerDetailsKeys(details)) + return *result; + + if (auto error = checkOptimizerDetail(details, "peephole", settings.runPeephole)) + return *error; + if (auto error = checkOptimizerDetail(details, "jumpdestRemover", settings.runJumpdestRemover)) + return *error; + if (auto error = checkOptimizerDetail(details, "orderLiterals", settings.runOrderLiterals)) + return *error; + if (auto error = checkOptimizerDetail(details, "deduplicate", settings.runDeduplicate)) + return *error; + if (auto error = checkOptimizerDetail(details, "cse", settings.runCSE)) + return *error; + if (auto error = checkOptimizerDetail(details, "constantOptimizer", settings.runConstantOptimiser)) + return *error; + if (auto error = checkOptimizerDetail(details, "yul", settings.runYulOptimiser)) + return *error; + if (details.isMember("yulDetails")) + { + if (!_jsonInput["yulDetails"].isObject()) + return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting has to be a JSON object."); + if (!_jsonInput["yulDetails"].getMemberNames().empty()) + return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting cannot have any settings yet."); + } + } + m_compilerStack.setOptimiserSettings(std::move(settings)); + return {}; +} + + Json::Value StandardCompiler::compileInternal(Json::Value const& _input) { m_compilerStack.reset(false); @@ -512,29 +584,9 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) m_compilerStack.setRemappings(remappings); if (settings.isMember("optimizer")) - { - Json::Value optimizerSettings = settings["optimizer"]; - - if (auto result = checkOptimizerKeys(optimizerSettings)) + if (auto result = parseOptimizerSettings(settings["optimizer"])) return *result; - if (optimizerSettings.isMember("enabled")) - { - if (!optimizerSettings["enabled"].isBool()) - return formatFatalError("JSONError", "The \"enabled\" setting must be a boolean."); - - bool const optimize = optimizerSettings["enabled"].asBool(); - unsigned optimizeRuns = 200; - if (optimizerSettings.isMember("runs")) - { - if (!optimizerSettings["runs"].isUInt()) - return formatFatalError("JSONError", "The \"runs\" setting must be an unsigned number."); - optimizeRuns = optimizerSettings["runs"].asUInt(); - } - m_compilerStack.setOptimiserSettings(optimize, optimizeRuns); - } - } - map libraries; Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue)); if (!jsonLibraries.isObject()) diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index fc9c3a594..7cc567317 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -24,6 +24,8 @@ #include +#include + namespace dev { @@ -53,6 +55,10 @@ public: std::string compile(std::string const& _input) noexcept; private: + /// Validaes and applies the optimizer settings. + /// On error returns the json-formatted error message. + boost::optional parseOptimizerSettings(Json::Value const& _settings); + Json::Value compileInternal(Json::Value const& _input); CompilerStack m_compilerStack; diff --git a/test/cmdlineTests/standard_optimizer_invalid_detail_type/input.json b/test/cmdlineTests/standard_optimizer_invalid_detail_type/input.json new file mode 100644 index 000000000..f0ce43e37 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_invalid_detail_type/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { "peephole": 7 } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_invalid_detail_type/output.json b/test/cmdlineTests/standard_optimizer_invalid_detail_type/output.json new file mode 100644 index 000000000..15a65e586 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_invalid_detail_type/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"\"settings.optimizer.details.peephole\" must be Boolean","message":"\"settings.optimizer.details.peephole\" must be Boolean","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/standard_optimizer_invalid_details/input.json b/test/cmdlineTests/standard_optimizer_invalid_details/input.json new file mode 100644 index 000000000..850f6f77c --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_invalid_details/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { "notThere": true } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_invalid_details/output.json b/test/cmdlineTests/standard_optimizer_invalid_details/output.json new file mode 100644 index 000000000..8be28f5b3 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_invalid_details/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"Unknown key \"notThere\"","message":"Unknown key \"notThere\"","severity":"error","type":"JSONError"}]} \ No newline at end of file diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json new file mode 100644 index 000000000..056aee91b --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { "yulDetails": 7 } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json new file mode 100644 index 000000000..b71a8a61a --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"The \"yulDetails\" optimizer setting has to be a JSON object.","message":"The \"yulDetails\" optimizer setting has to be a JSON object.","severity":"error","type":"JSONError"}]} diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 0cd395860..ca9bc363a 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -242,7 +242,7 @@ BOOST_AUTO_TEST_CASE(optimizer_enabled_not_boolean) } )"; Json::Value result = compile(input); - BOOST_CHECK(containsError(result, "JSONError", "The \"enabled\" setting must be a boolean.")); + BOOST_CHECK(containsError(result, "JSONError", "The \"enabled\" setting must be a Boolean.")); } BOOST_AUTO_TEST_CASE(optimizer_runs_not_a_number) @@ -859,6 +859,159 @@ BOOST_AUTO_TEST_CASE(evm_version) BOOST_CHECK(result["errors"][0]["message"].asString() == "Invalid EVM version requested."); } +BOOST_AUTO_TEST_CASE(optimizer_settings_default_disabled) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { "A": [ "metadata" ] } + } + }, + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["metadata"].isString()); + Json::Value metadata; + BOOST_CHECK(jsonParseStrict(contract["metadata"].asString(), metadata)); + + Json::Value const& optimizer = metadata["settings"]["optimizer"]; + BOOST_CHECK(optimizer.isMember("enabled")); + BOOST_CHECK(optimizer["enabled"].asBool() == false); + BOOST_CHECK(!optimizer.isMember("details")); + BOOST_CHECK(optimizer["runs"].asUInt() == 200); +} + +BOOST_AUTO_TEST_CASE(optimizer_settings_default_enabled) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { "A": [ "metadata" ] } + }, + "optimizer": { "enabled": true } + }, + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["metadata"].isString()); + Json::Value metadata; + BOOST_CHECK(jsonParseStrict(contract["metadata"].asString(), metadata)); + + Json::Value const& optimizer = metadata["settings"]["optimizer"]; + BOOST_CHECK(optimizer.isMember("enabled")); + BOOST_CHECK(optimizer["enabled"].asBool() == true); + BOOST_CHECK(!optimizer.isMember("details")); + BOOST_CHECK(optimizer["runs"].asUInt() == 200); +} + +BOOST_AUTO_TEST_CASE(optimizer_settings_details_exactly_as_default_disabled) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { "A": [ "metadata" ] } + }, + "optimizer": { "details": { + "constantOptimizer" : false, + "cse" : false, + "deduplicate" : false, + "jumpdestRemover" : true, + "orderLiterals" : false, + "peephole" : true + } } + }, + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["metadata"].isString()); + Json::Value metadata; + BOOST_CHECK(jsonParseStrict(contract["metadata"].asString(), metadata)); + + Json::Value const& optimizer = metadata["settings"]["optimizer"]; + BOOST_CHECK(optimizer.isMember("enabled")); + // enabled is switched to false instead! + BOOST_CHECK(optimizer["enabled"].asBool() == false); + BOOST_CHECK(!optimizer.isMember("details")); + BOOST_CHECK(optimizer["runs"].asUInt() == 200); +} + +BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { "A": [ "metadata" ] } + }, + "optimizer": { "runs": 600, "details": { + "constantOptimizer" : true, + "cse" : false, + "deduplicate" : true, + "jumpdestRemover" : true, + "orderLiterals" : false, + "peephole" : true, + "yul": true + } } + }, + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["metadata"].isString()); + Json::Value metadata; + BOOST_CHECK(jsonParseStrict(contract["metadata"].asString(), metadata)); + + Json::Value const& optimizer = metadata["settings"]["optimizer"]; + BOOST_CHECK(!optimizer.isMember("enabled")); + BOOST_CHECK(optimizer.isMember("details")); + BOOST_CHECK(optimizer["details"]["constantOptimizer"].asBool() == true); + BOOST_CHECK(optimizer["details"]["cse"].asBool() == false); + BOOST_CHECK(optimizer["details"]["deduplicate"].asBool() == true); + BOOST_CHECK(optimizer["details"]["jumpdestRemover"].asBool() == true); + BOOST_CHECK(optimizer["details"]["orderLiterals"].asBool() == false); + BOOST_CHECK(optimizer["details"]["peephole"].asBool() == true); + BOOST_CHECK(optimizer["details"]["yulDetails"].isObject()); + BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 8); + BOOST_CHECK(optimizer["runs"].asUInt() == 600); +} BOOST_AUTO_TEST_SUITE_END() From 1ff562d28a676fe5f59bd059e371b586c3df23aa Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 21 Feb 2019 18:13:52 +0100 Subject: [PATCH 3/7] Provide full optimiser settings to ContractCompiler. --- libsolidity/codegen/Compiler.cpp | 13 ++++++------- libsolidity/codegen/ContractCompiler.cpp | 10 +++++----- libsolidity/codegen/ContractCompiler.h | 14 +++++++------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 6b2bf3761..55db6290d 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -35,18 +35,17 @@ void Compiler::compileContract( bytes const& _metadata ) { - ContractCompiler runtimeCompiler( - nullptr, - m_runtimeContext, - m_optimiserSettings.runOrderLiterals, - m_optimiserSettings.expectedExecutionsPerDeployment - ); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings); 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_optimiserSettings.runOrderLiterals, 1); + OptimiserSettings creationSettings{m_optimiserSettings}; + // The creation code will be executed at most once, so we modify the optimizer + // settings accordingly. + creationSettings.expectedExecutionsPerDeployment = 1; + ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings); m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); m_context.optimise(m_optimiserSettings); diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index d516f7918..12da7ebf9 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_optimiseRuns); + appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimiserSettings.expectedExecutionsPerDeployment); } 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_optimiseOrderLiterals).appendStateVariableInitialization(*variable); + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).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_optimiseOrderLiterals).appendConstStateVariableAccessor(_variableDeclaration); + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendConstStateVariableAccessor(_variableDeclaration); else - ExpressionCompiler(m_context, m_optimiseOrderLiterals).appendStateVariableAccessor(_variableDeclaration); + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).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_optimiseOrderLiterals); + ExpressionCompiler expressionCompiler(m_context, m_optimiserSettings.runOrderLiterals); 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 35615b23a..b6d391e2b 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -28,8 +28,10 @@ #include #include -namespace dev { -namespace solidity { +namespace dev +{ +namespace solidity +{ /** * Code generator at the contract level. Can be used to generate code for exactly one contract @@ -38,9 +40,8 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimiseOrderLiterals, size_t _optimiseRuns): - m_optimiseOrderLiterals(_optimiseOrderLiterals), - m_optimiseRuns(_optimiseRuns), + explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, OptimiserSettings _optimiserSettings): + m_optimiserSettings(std::move(_optimiserSettings)), m_runtimeCompiler(_runtimeCompiler), m_context(_context) { @@ -130,8 +131,7 @@ private: /// Sets the stack height for the visited loop. void storeStackHeight(ASTNode const* _node); - bool const m_optimiseOrderLiterals; - size_t const m_optimiseRuns = 200; + OptimiserSettings const m_optimiserSettings; /// Pointer to the runtime compiler in case this is a creation compiler. ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; From 83d1382e78b0765e699533185cdf7014331dcfbc Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 21 Feb 2019 18:23:46 +0100 Subject: [PATCH 4/7] Take yul optimizer setting into account. --- libsolidity/codegen/CompilerContext.cpp | 67 ++++++++++++++++++------ libsolidity/codegen/CompilerContext.h | 3 +- libsolidity/codegen/ContractCompiler.cpp | 8 ++- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 6c090d1fe..3053e3f7c 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -328,12 +329,19 @@ void CompilerContext::resetVisitedNodes(ASTNode const* _node) void CompilerContext::appendInlineAssembly( string const& _assembly, vector const& _localVariables, - set const&, - bool _system + set const& _externallyUsedFunctions, + bool _system, + bool _optimise ) { int startStackHeight = stackHeight(); + set externallyUsedIdentifiers; + for (auto const& fun: _externallyUsedFunctions) + externallyUsedIdentifiers.insert(yul::YulString(fun)); + for (auto const& var: _localVariables) + externallyUsedIdentifiers.insert(yul::YulString(var)); + yul::ExternalIdentifierAccess identifierAccess; identifierAccess.resolve = [&]( yul::Identifier const& _identifier, @@ -380,20 +388,12 @@ void CompilerContext::appendInlineAssembly( #ifdef SOL_OUTPUT_ASM cout << yul::AsmPrinter()(*parserResult) << endl; #endif - yul::AsmAnalysisInfo analysisInfo; - bool analyzerResult = false; - if (parserResult) - analyzerResult = yul::AsmAnalyzer( - analysisInfo, - errorReporter, - boost::none, - yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), - identifierAccess.resolve - ).analyze(*parserResult); - if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) + + auto reportError = [&](string const& _context) { string message = - "Error parsing/analyzing inline assembly block:\n" + "Error parsing/analyzing inline assembly block:\n" + + _context + "\n" "------------------ Input: -----------------\n" + _assembly + "\n" "------------------ Errors: ----------------\n"; @@ -405,10 +405,47 @@ void CompilerContext::appendInlineAssembly( message += "-------------------------------------------\n"; solAssert(false, message); + }; + + yul::AsmAnalysisInfo analysisInfo; + bool analyzerResult = false; + if (parserResult) + analyzerResult = yul::AsmAnalyzer( + analysisInfo, + errorReporter, + boost::none, + yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), + identifierAccess.resolve + ).analyze(*parserResult); + if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) + reportError("Invalid assembly generated by code generator."); + + // Several optimizer steps cannot handle externally supplied stack variables, + // so we essentially only optimize the ABI functions. + if (_optimise && _localVariables.empty()) + { + yul::OptimiserSuite::run( + yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), + *parserResult, + analysisInfo, + externallyUsedIdentifiers + ); + analysisInfo = yul::AsmAnalysisInfo{}; + if (!yul::AsmAnalyzer( + analysisInfo, + errorReporter, + boost::none, + yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), + identifierAccess.resolve + ).analyze(*parserResult)) + reportError("Optimizer introduced error into inline assembly."); } + if (!errorReporter.errors().empty()) + reportError("Failed to analyze inline assembly block."); + solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block."); - yul::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, m_evmVersion, identifierAccess, _system); + yul::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, m_evmVersion, identifierAccess, _system, _optimise); // Reset the source location to the one of the node (instead of the CODEGEN source location) updateSourceLocation(); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 8a5b89907..0957287a1 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -216,7 +216,8 @@ public: std::string const& _assembly, std::vector const& _localVariables = std::vector(), std::set const& _externallyUsedFunctions = std::set(), - bool _system = false + bool _system = false, + bool _optimise = false ); /// Appends arbitrary data to the end of the bytecode. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 12da7ebf9..8320d23d5 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -978,7 +978,13 @@ void ContractCompiler::appendMissingFunctions() m_context.appendMissingLowLevelFunctions(); auto abiFunctions = m_context.abiFunctions().requestedFunctions(); if (!abiFunctions.first.empty()) - m_context.appendInlineAssembly("{" + move(abiFunctions.first) + "}", {}, abiFunctions.second, true); + m_context.appendInlineAssembly( + "{" + move(abiFunctions.first) + "}", + {}, + abiFunctions.second, + true, + m_optimiserSettings.runYulOptimiser + ); } void ContractCompiler::appendModifierOrFunctionCode() From 5ddbc434d66dfa850d553030e410276c8b82a99f Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 21 Feb 2019 18:35:41 +0100 Subject: [PATCH 5/7] Provide commandline option to activate yul optimizer. --- solc/CommandLineInterface.cpp | 12 ++++++++---- test/cmdlineTests/gas_test_abiv2/args | 1 + test/cmdlineTests/gas_test_abiv2/err | 3 +++ test/cmdlineTests/gas_test_abiv2/input.sol | 15 +++++++++++++++ test/cmdlineTests/gas_test_abiv2/output | 16 ++++++++++++++++ .../gas_test_abiv2_optimize_yul/args | 1 + .../cmdlineTests/gas_test_abiv2_optimize_yul/err | 3 +++ .../gas_test_abiv2_optimize_yul/input.sol | 15 +++++++++++++++ .../gas_test_abiv2_optimize_yul/output | 16 ++++++++++++++++ 9 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 test/cmdlineTests/gas_test_abiv2/args create mode 100644 test/cmdlineTests/gas_test_abiv2/err create mode 100644 test/cmdlineTests/gas_test_abiv2/input.sol create mode 100644 test/cmdlineTests/gas_test_abiv2/output create mode 100644 test/cmdlineTests/gas_test_abiv2_optimize_yul/args create mode 100644 test/cmdlineTests/gas_test_abiv2_optimize_yul/err create mode 100644 test/cmdlineTests/gas_test_abiv2_optimize_yul/input.sol create mode 100644 test/cmdlineTests/gas_test_abiv2_optimize_yul/output diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index dd3f4a125..ec7c06c93 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -132,6 +132,7 @@ static string const g_strNatspecUser = "userdoc"; static string const g_strOpcodes = "opcodes"; static string const g_strOptimize = "optimize"; static string const g_strOptimizeRuns = "optimize-runs"; +static string const g_strOptimizeYul = "optimize-yul"; static string const g_strOutputDir = "output-dir"; static string const g_strOverwrite = "overwrite"; static string const g_strSignatureHashes = "hashes"; @@ -615,6 +616,7 @@ Allowed options)", "Set for how many contract runs to optimize." "Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage." ) + (g_strOptimizeYul.c_str(), "Enable Yul optimizer in Solidity, mostly for ABIEncoderV2.") (g_argPrettyJson.c_str(), "Output JSON in pretty format. Currently it only works with the combined JSON output.") ( g_argLibraries.c_str(), @@ -847,7 +849,7 @@ bool CommandLineInterface::processInput() using Machine = yul::AssemblyStack::Machine; Input inputLanguage = m_args.count(g_argYul) ? Input::Yul : (m_args.count(g_argStrictAssembly) ? Input::StrictAssembly : Input::Assembly); Machine targetMachine = Machine::EVM; - bool optimize = m_args.count(g_argOptimize); + bool optimize = m_args.count(g_argOptimize) || m_args.count(g_strOptimizeYul); if (m_args.count(g_argMachine)) { string machine = m_args[g_argMachine].as(); @@ -901,9 +903,11 @@ bool CommandLineInterface::processInput() m_compiler->setLibraries(m_libraries); m_compiler->setEVMVersion(m_evmVersion); // TODO: Perhaps we should not compile unless requested - bool optimize = m_args.count(g_argOptimize) > 0; - unsigned runs = m_args[g_argOptimizeRuns].as(); - m_compiler->setOptimiserSettings(optimize, runs); + + OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::enabled() : OptimiserSettings::minimal(); + settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as(); + settings.runYulOptimiser = m_args.count(g_strOptimizeYul); + m_compiler->setOptimiserSettings(settings); bool successful = m_compiler->compile(); diff --git a/test/cmdlineTests/gas_test_abiv2/args b/test/cmdlineTests/gas_test_abiv2/args new file mode 100644 index 000000000..3e01bd575 --- /dev/null +++ b/test/cmdlineTests/gas_test_abiv2/args @@ -0,0 +1 @@ +--gas \ No newline at end of file diff --git a/test/cmdlineTests/gas_test_abiv2/err b/test/cmdlineTests/gas_test_abiv2/err new file mode 100644 index 000000000..012ed14fc --- /dev/null +++ b/test/cmdlineTests/gas_test_abiv2/err @@ -0,0 +1,3 @@ +gas_test_abiv2/input.sol:2:1: Warning: Experimental features are turned on. Do not use experimental features on live deployments. +pragma experimental ABIEncoderV2; +^-------------------------------^ \ No newline at end of file diff --git a/test/cmdlineTests/gas_test_abiv2/input.sol b/test/cmdlineTests/gas_test_abiv2/input.sol new file mode 100644 index 000000000..4e80a5fbc --- /dev/null +++ b/test/cmdlineTests/gas_test_abiv2/input.sol @@ -0,0 +1,15 @@ +pragma solidity >=0.0; +pragma experimental ABIEncoderV2; + +contract C { + uint public a; + uint[] public b; + function f1(uint) public pure returns (uint) { } + function f2(uint[] memory, string[] memory, uint16, address) public returns (uint[] memory, uint16[] memory) {} + function f3(uint16[] memory, string[] memory, uint16, address) public returns (uint[] memory, uint16[] memory) {} + function f4(uint32[] memory, string[12] memory, bytes[2][] memory, address) public returns (uint[] memory, uint16[] memory) {} + function f5(address[] memory, string[] memory, bytes memory, address) public returns (uint[] memory, uint16[] memory) {} + function f6(uint[30] memory, string[] memory, uint16, address) public returns (uint16[200] memory, uint16[] memory) {} + function f7(uint[31] memory, string[20] memory, C, address) public returns (bytes[] memory, uint16[] memory) {} + function f8(uint[32] memory, string[] memory, uint32, address) public returns (uint[] memory, uint16[] memory) {} +} diff --git a/test/cmdlineTests/gas_test_abiv2/output b/test/cmdlineTests/gas_test_abiv2/output new file mode 100644 index 000000000..4e9d8be56 --- /dev/null +++ b/test/cmdlineTests/gas_test_abiv2/output @@ -0,0 +1,16 @@ + +======= gas_test_abiv2/input.sol:C ======= +Gas estimation: +construction: + 1154 + 1109000 = 1110154 +external: + a(): 535 + b(uint256): 1129 + f1(uint256): 591 + f2(uint256[],string[],uint16,address): infinite + f3(uint16[],string[],uint16,address): infinite + f4(uint32[],string[12],bytes[2][],address): infinite + f5(address[],string[],bytes,address): infinite + f6(uint256[30],string[],uint16,address): infinite + f7(uint256[31],string[20],address,address): infinite + f8(uint256[32],string[],uint32,address): infinite diff --git a/test/cmdlineTests/gas_test_abiv2_optimize_yul/args b/test/cmdlineTests/gas_test_abiv2_optimize_yul/args new file mode 100644 index 000000000..d5c9702c7 --- /dev/null +++ b/test/cmdlineTests/gas_test_abiv2_optimize_yul/args @@ -0,0 +1 @@ +--gas --optimize --optimize-yul \ No newline at end of file diff --git a/test/cmdlineTests/gas_test_abiv2_optimize_yul/err b/test/cmdlineTests/gas_test_abiv2_optimize_yul/err new file mode 100644 index 000000000..1e205d0e3 --- /dev/null +++ b/test/cmdlineTests/gas_test_abiv2_optimize_yul/err @@ -0,0 +1,3 @@ +gas_test_abiv2_optimize_yul/input.sol:2:1: Warning: Experimental features are turned on. Do not use experimental features on live deployments. +pragma experimental ABIEncoderV2; +^-------------------------------^ diff --git a/test/cmdlineTests/gas_test_abiv2_optimize_yul/input.sol b/test/cmdlineTests/gas_test_abiv2_optimize_yul/input.sol new file mode 100644 index 000000000..4e80a5fbc --- /dev/null +++ b/test/cmdlineTests/gas_test_abiv2_optimize_yul/input.sol @@ -0,0 +1,15 @@ +pragma solidity >=0.0; +pragma experimental ABIEncoderV2; + +contract C { + uint public a; + uint[] public b; + function f1(uint) public pure returns (uint) { } + function f2(uint[] memory, string[] memory, uint16, address) public returns (uint[] memory, uint16[] memory) {} + function f3(uint16[] memory, string[] memory, uint16, address) public returns (uint[] memory, uint16[] memory) {} + function f4(uint32[] memory, string[12] memory, bytes[2][] memory, address) public returns (uint[] memory, uint16[] memory) {} + function f5(address[] memory, string[] memory, bytes memory, address) public returns (uint[] memory, uint16[] memory) {} + function f6(uint[30] memory, string[] memory, uint16, address) public returns (uint16[200] memory, uint16[] memory) {} + function f7(uint[31] memory, string[20] memory, C, address) public returns (bytes[] memory, uint16[] memory) {} + function f8(uint[32] memory, string[] memory, uint32, address) public returns (uint[] memory, uint16[] memory) {} +} diff --git a/test/cmdlineTests/gas_test_abiv2_optimize_yul/output b/test/cmdlineTests/gas_test_abiv2_optimize_yul/output new file mode 100644 index 000000000..c6f0135a4 --- /dev/null +++ b/test/cmdlineTests/gas_test_abiv2_optimize_yul/output @@ -0,0 +1,16 @@ + +======= gas_test_abiv2_optimize_yul/input.sol:C ======= +Gas estimation: +construction: + 676 + 641600 = 642276 +external: + a(): 434 + b(uint256): 892 + f1(uint256): 356 + f2(uint256[],string[],uint16,address): infinite + f3(uint16[],string[],uint16,address): infinite + f4(uint32[],string[12],bytes[2][],address): infinite + f5(address[],string[],bytes,address): infinite + f6(uint256[30],string[],uint16,address): infinite + f7(uint256[31],string[20],address,address): infinite + f8(uint256[32],string[],uint32,address): infinite From 851dd635eb2b34037be34489d439cefb65e28910 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 28 Feb 2019 16:05:20 +0100 Subject: [PATCH 6/7] Remove defaults in constructor arguments. --- libevmasm/Assembly.h | 2 +- liblll/Compiler.cpp | 4 ++-- libsolidity/codegen/CompilerContext.h | 2 +- libsolidity/codegen/ContractCompiler.h | 6 +++++- libsolidity/codegen/ExpressionCompiler.h | 2 +- test/libevmasm/Optimiser.cpp | 2 +- test/libsolidity/SolidityExpressionCompiler.cpp | 2 +- 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index b21077e28..a53c92420 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -122,7 +122,7 @@ public: /// @a _runs specifes 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. /// If @a _enable is not set, will perform some simple peephole optimizations. - Assembly& optimise(bool _enable, langutil::EVMVersion _evmVersion, bool _isCreation = true, size_t _runs = 200); + Assembly& optimise(bool _enable, langutil::EVMVersion _evmVersion, bool _isCreation, size_t _runs); /// Create a text representation of the assembly. std::string assemblyString( diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp index 065ffb32a..03190347a 100644 --- a/liblll/Compiler.cpp +++ b/liblll/Compiler.cpp @@ -36,7 +36,7 @@ bytes dev::lll::compileLLL(string const& _src, langutil::EVMVersion _evmVersion, cs.populateStandard(); auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs); if (_opt) - assembly = assembly.optimise(true, _evmVersion); + assembly = assembly.optimise(true, _evmVersion, true, 200); bytes ret = assembly.assemble().bytecode; for (auto i: cs.treesToKill) killBigints(i); @@ -74,7 +74,7 @@ std::string dev::lll::compileLLLToAsm(std::string const& _src, langutil::EVMVers cs.populateStandard(); auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs); if (_opt) - assembly = assembly.optimise(true, _evmVersion); + assembly = assembly.optimise(true, _evmVersion, true, 200); string ret = assembly.assemblyString(); for (auto i: cs.treesToKill) killBigints(i); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 0957287a1..44f96f5dd 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -52,7 +52,7 @@ class Compiler; class CompilerContext { public: - explicit CompilerContext(langutil::EVMVersion _evmVersion = langutil::EVMVersion{}, CompilerContext* _runtimeContext = nullptr): + explicit CompilerContext(langutil::EVMVersion _evmVersion, CompilerContext* _runtimeContext = nullptr): m_asm(std::make_shared()), m_evmVersion(_evmVersion), m_runtimeContext(_runtimeContext), diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index b6d391e2b..30a841334 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -40,7 +40,11 @@ namespace solidity class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, OptimiserSettings _optimiserSettings): + explicit ContractCompiler( + ContractCompiler* _runtimeCompiler, + CompilerContext& _context, + OptimiserSettings _optimiserSettings + ): m_optimiserSettings(std::move(_optimiserSettings)), m_runtimeCompiler(_runtimeCompiler), m_context(_context) diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 615636d74..ca4e05b5f 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -55,7 +55,7 @@ class ArrayType; class ExpressionCompiler: private ASTConstVisitor { public: - explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimiseOrderLiterals = false): + explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimiseOrderLiterals): m_optimiseOrderLiterals(_optimiseOrderLiterals), m_context(_compilerContext) {} /// Compile the given @a _expression and leave its value on the stack. diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index c65d28d3e..188aa56a7 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1053,7 +1053,7 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies) main.append(t1.toSubAssemblyTag(subId)); main.append(u256(8)); - main.optimise(true, dev::test::Options::get().evmVersion()); + main.optimise(true, dev::test::Options::get().evmVersion(), false, 200); AssemblyItems expectationMain{ AssemblyItem(PushSubSize, 0), diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index f5c4dc5ed..b079b013a 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -153,7 +153,7 @@ bytes compileFirstExpression( parametersSize-- ); - ExpressionCompiler(context).compile(*extractor.expression()); + ExpressionCompiler(context, dev::test::Options::get().optimize).compile(*extractor.expression()); for (vector const& function: _functions) context << context.functionEntryLabel(dynamic_cast( From 43850d55f3bde12c2e88b8df45634b118b82c871 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 28 Feb 2019 23:25:24 +0100 Subject: [PATCH 7/7] Update tests. --- test/libsolidity/Assembly.cpp | 43 ++- .../SolidityExpressionCompiler.cpp | 312 +++++++++++------- 2 files changed, 228 insertions(+), 127 deletions(-) diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index ea685c07c..b028e5176 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -168,20 +168,35 @@ BOOST_AUTO_TEST_CASE(location_test) auto codegenCharStream = make_shared("", "--CODEGEN--"); - vector locations = - vector(4, SourceLocation{2, 82, sourceCode}) + - vector(1, SourceLocation{8, 17, codegenCharStream}) + - vector(3, SourceLocation{5, 7, codegenCharStream}) + - vector(1, SourceLocation{30, 31, codegenCharStream}) + - vector(1, SourceLocation{27, 28, codegenCharStream}) + - vector(1, SourceLocation{20, 32, codegenCharStream}) + - vector(1, SourceLocation{5, 7, codegenCharStream}) + - vector(hasShifts ? 19 : 20, SourceLocation{2, 82, sourceCode}) + - vector(24, SourceLocation{20, 79, sourceCode}) + - vector(1, SourceLocation{49, 58, sourceCode}) + - vector(1, SourceLocation{72, 74, sourceCode}) + - vector(2, SourceLocation{65, 74, sourceCode}) + - vector(2, SourceLocation{20, 79, sourceCode}); + vector locations; + if (dev::test::Options::get().optimize) + locations = + vector(4, SourceLocation{2, 82, sourceCode}) + + vector(1, SourceLocation{8, 17, codegenCharStream}) + + vector(3, SourceLocation{5, 7, codegenCharStream}) + + vector(1, SourceLocation{30, 31, codegenCharStream}) + + vector(1, SourceLocation{27, 28, codegenCharStream}) + + vector(1, SourceLocation{20, 32, codegenCharStream}) + + vector(1, SourceLocation{5, 7, codegenCharStream}) + + vector(19, SourceLocation{2, 82, sourceCode}) + + vector(21, SourceLocation{20, 79, sourceCode}) + + vector(1, SourceLocation{72, 74, sourceCode}) + + vector(2, SourceLocation{20, 79, sourceCode}); + else + locations = + vector(4, SourceLocation{2, 82, sourceCode}) + + vector(1, SourceLocation{8, 17, codegenCharStream}) + + vector(3, SourceLocation{5, 7, codegenCharStream}) + + vector(1, SourceLocation{30, 31, codegenCharStream}) + + vector(1, SourceLocation{27, 28, codegenCharStream}) + + vector(1, SourceLocation{20, 32, codegenCharStream}) + + vector(1, SourceLocation{5, 7, codegenCharStream}) + + vector(hasShifts ? 19 : 20, SourceLocation{2, 82, sourceCode}) + + vector(24, SourceLocation{20, 79, sourceCode}) + + vector(1, SourceLocation{49, 58, sourceCode}) + + vector(1, SourceLocation{72, 74, sourceCode}) + + vector(2, SourceLocation{65, 74, sourceCode}) + + vector(2, SourceLocation{20, 79, sourceCode}); checkAssemblyLocations(items, locations); } diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index b079b013a..65ee97bc0 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -282,12 +282,26 @@ BOOST_AUTO_TEST_CASE(comparison) )"; bytes code = compileFirstExpression(sourceCode); - bytes expectation({uint8_t(Instruction::PUSH1), 0x1, uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), - uint8_t(Instruction::PUSH2), 0x11, 0xaa, - uint8_t(Instruction::PUSH2), 0x10, 0xaa, - uint8_t(Instruction::LT), uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), - uint8_t(Instruction::EQ), - uint8_t(Instruction::ISZERO)}); + bytes expectation; + if (dev::test::Options::get().optimize) + expectation = { + uint8_t(Instruction::PUSH2), 0x11, 0xaa, + uint8_t(Instruction::PUSH2), 0x10, 0xaa, + uint8_t(Instruction::LT), uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), + uint8_t(Instruction::PUSH1), 0x1, + uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), + uint8_t(Instruction::EQ), + uint8_t(Instruction::ISZERO) + }; + else + expectation = { + uint8_t(Instruction::PUSH1), 0x1, uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), + uint8_t(Instruction::PUSH2), 0x11, 0xaa, + uint8_t(Instruction::PUSH2), 0x10, 0xaa, + uint8_t(Instruction::LT), uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), + uint8_t(Instruction::EQ), + uint8_t(Instruction::ISZERO) + }; BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } @@ -300,23 +314,25 @@ BOOST_AUTO_TEST_CASE(short_circuiting) )"; bytes code = compileFirstExpression(sourceCode); - bytes expectation({uint8_t(Instruction::PUSH1), 0x12, // 8 + 10 - uint8_t(Instruction::PUSH1), 0x4, - uint8_t(Instruction::GT), - uint8_t(Instruction::ISZERO), // after this we have 4 <= 8 + 10 - uint8_t(Instruction::DUP1), - uint8_t(Instruction::PUSH1), 0x11, - uint8_t(Instruction::JUMPI), // short-circuit if it is true - uint8_t(Instruction::POP), - uint8_t(Instruction::PUSH1), 0x2, - uint8_t(Instruction::PUSH1), 0x9, - uint8_t(Instruction::EQ), - uint8_t(Instruction::ISZERO), // after this we have 9 != 2 - uint8_t(Instruction::JUMPDEST), - uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), - uint8_t(Instruction::PUSH1), 0x1, uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), - uint8_t(Instruction::EQ), - uint8_t(Instruction::ISZERO)}); + bytes expectation{ + uint8_t(Instruction::PUSH1), 0x12, // 8 + 10 + uint8_t(Instruction::PUSH1), 0x4, + uint8_t(Instruction::GT), + uint8_t(Instruction::ISZERO), // after this we have 4 <= 8 + 10 + uint8_t(Instruction::DUP1), + uint8_t(Instruction::PUSH1), 0x11, + uint8_t(Instruction::JUMPI), // short-circuit if it is true + uint8_t(Instruction::POP), + uint8_t(Instruction::PUSH1), 0x2, + uint8_t(Instruction::PUSH1), 0x9, + uint8_t(Instruction::EQ), + uint8_t(Instruction::ISZERO), // after this we have 9 != 2 + uint8_t(Instruction::JUMPDEST), + uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), + uint8_t(Instruction::PUSH1), 0x1, uint8_t(Instruction::ISZERO), uint8_t(Instruction::ISZERO), + uint8_t(Instruction::EQ), + uint8_t(Instruction::ISZERO) + }; BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } @@ -328,37 +344,76 @@ BOOST_AUTO_TEST_CASE(arithmetic) } )"; bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}}); - bytes expectation({uint8_t(Instruction::PUSH1), 0x1, - uint8_t(Instruction::PUSH1), 0x2, - uint8_t(Instruction::PUSH1), 0x3, - uint8_t(Instruction::PUSH1), 0x4, - uint8_t(Instruction::PUSH1), 0x5, - uint8_t(Instruction::PUSH1), 0x6, - uint8_t(Instruction::PUSH1), 0x7, - uint8_t(Instruction::PUSH1), 0x8, - uint8_t(Instruction::DUP9), - uint8_t(Instruction::XOR), - uint8_t(Instruction::AND), - uint8_t(Instruction::OR), - uint8_t(Instruction::SUB), - uint8_t(Instruction::ADD), - uint8_t(Instruction::DUP2), - uint8_t(Instruction::ISZERO), - uint8_t(Instruction::ISZERO), - uint8_t(Instruction::PUSH1), 0x1d, - uint8_t(Instruction::JUMPI), - uint8_t(Instruction::INVALID), - uint8_t(Instruction::JUMPDEST), - uint8_t(Instruction::MOD), - uint8_t(Instruction::DUP2), - uint8_t(Instruction::ISZERO), - uint8_t(Instruction::ISZERO), - uint8_t(Instruction::PUSH1), 0x26, - uint8_t(Instruction::JUMPI), - uint8_t(Instruction::INVALID), - uint8_t(Instruction::JUMPDEST), - uint8_t(Instruction::DIV), - uint8_t(Instruction::MUL)}); + + bytes expectation; + if (dev::test::Options::get().optimize) + expectation = { + uint8_t(Instruction::PUSH1), 0x2, + uint8_t(Instruction::PUSH1), 0x3, + uint8_t(Instruction::PUSH1), 0x5, + uint8_t(Instruction::DUP4), + uint8_t(Instruction::PUSH1), 0x8, + uint8_t(Instruction::XOR), + uint8_t(Instruction::PUSH1), 0x7, + uint8_t(Instruction::AND), + uint8_t(Instruction::PUSH1), 0x6, + uint8_t(Instruction::OR), + uint8_t(Instruction::SUB), + uint8_t(Instruction::PUSH1), 0x4, + uint8_t(Instruction::ADD), + uint8_t(Instruction::DUP2), + uint8_t(Instruction::ISZERO), + uint8_t(Instruction::ISZERO), + uint8_t(Instruction::PUSH1), 0x1b, + uint8_t(Instruction::JUMPI), + uint8_t(Instruction::INVALID), + uint8_t(Instruction::JUMPDEST), + uint8_t(Instruction::MOD), + uint8_t(Instruction::DUP2), + uint8_t(Instruction::ISZERO), + uint8_t(Instruction::ISZERO), + uint8_t(Instruction::PUSH1), 0x24, + uint8_t(Instruction::JUMPI), + uint8_t(Instruction::INVALID), + uint8_t(Instruction::JUMPDEST), + uint8_t(Instruction::DIV), + uint8_t(Instruction::PUSH1), 0x1, + uint8_t(Instruction::MUL) + }; + else + expectation = { + uint8_t(Instruction::PUSH1), 0x1, + uint8_t(Instruction::PUSH1), 0x2, + uint8_t(Instruction::PUSH1), 0x3, + uint8_t(Instruction::PUSH1), 0x4, + uint8_t(Instruction::PUSH1), 0x5, + uint8_t(Instruction::PUSH1), 0x6, + uint8_t(Instruction::PUSH1), 0x7, + uint8_t(Instruction::PUSH1), 0x8, + uint8_t(Instruction::DUP9), + uint8_t(Instruction::XOR), + uint8_t(Instruction::AND), + uint8_t(Instruction::OR), + uint8_t(Instruction::SUB), + uint8_t(Instruction::ADD), + uint8_t(Instruction::DUP2), + uint8_t(Instruction::ISZERO), + uint8_t(Instruction::ISZERO), + uint8_t(Instruction::PUSH1), 0x1d, + uint8_t(Instruction::JUMPI), + uint8_t(Instruction::INVALID), + uint8_t(Instruction::JUMPDEST), + uint8_t(Instruction::MOD), + uint8_t(Instruction::DUP2), + uint8_t(Instruction::ISZERO), + uint8_t(Instruction::ISZERO), + uint8_t(Instruction::PUSH1), 0x26, + uint8_t(Instruction::JUMPI), + uint8_t(Instruction::INVALID), + uint8_t(Instruction::JUMPDEST), + uint8_t(Instruction::DIV), + uint8_t(Instruction::MUL) + }; BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } @@ -371,13 +426,27 @@ BOOST_AUTO_TEST_CASE(unary_operators) )"; bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}}); - bytes expectation({uint8_t(Instruction::PUSH1), 0x2, - uint8_t(Instruction::DUP2), - uint8_t(Instruction::PUSH1), 0x0, - uint8_t(Instruction::SUB), - uint8_t(Instruction::NOT), - uint8_t(Instruction::EQ), - uint8_t(Instruction::ISZERO)}); + bytes expectation; + if (dev::test::Options::get().optimize) + expectation = { + uint8_t(Instruction::DUP1), + uint8_t(Instruction::PUSH1), 0x0, + uint8_t(Instruction::SUB), + uint8_t(Instruction::NOT), + uint8_t(Instruction::PUSH1), 0x2, + uint8_t(Instruction::EQ), + uint8_t(Instruction::ISZERO) + }; + else + expectation = { + uint8_t(Instruction::PUSH1), 0x2, + uint8_t(Instruction::DUP2), + uint8_t(Instruction::PUSH1), 0x0, + uint8_t(Instruction::SUB), + uint8_t(Instruction::NOT), + uint8_t(Instruction::EQ), + uint8_t(Instruction::ISZERO) + }; BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } @@ -391,48 +460,50 @@ BOOST_AUTO_TEST_CASE(unary_inc_dec) bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}}); // Stack: a, x - bytes expectation({uint8_t(Instruction::DUP2), - uint8_t(Instruction::DUP1), - uint8_t(Instruction::PUSH1), 0x1, - uint8_t(Instruction::ADD), - // Stack here: a x a (a+1) - uint8_t(Instruction::SWAP3), - uint8_t(Instruction::POP), // first ++ - // Stack here: (a+1) x a - uint8_t(Instruction::DUP3), - uint8_t(Instruction::PUSH1), 0x1, - uint8_t(Instruction::ADD), - // Stack here: (a+1) x a (a+2) - uint8_t(Instruction::SWAP3), - uint8_t(Instruction::POP), - // Stack here: (a+2) x a - uint8_t(Instruction::DUP3), // second ++ - uint8_t(Instruction::XOR), - // Stack here: (a+2) x a^(a+2) - uint8_t(Instruction::DUP3), - uint8_t(Instruction::DUP1), - uint8_t(Instruction::PUSH1), 0x1, - uint8_t(Instruction::SWAP1), - uint8_t(Instruction::SUB), - // Stack here: (a+2) x a^(a+2) (a+2) (a+1) - uint8_t(Instruction::SWAP4), - uint8_t(Instruction::POP), // first -- - uint8_t(Instruction::XOR), - // Stack here: (a+1) x a^(a+2)^(a+2) - uint8_t(Instruction::DUP3), - uint8_t(Instruction::PUSH1), 0x1, - uint8_t(Instruction::SWAP1), - uint8_t(Instruction::SUB), - // Stack here: (a+1) x a^(a+2)^(a+2) a - uint8_t(Instruction::SWAP3), - uint8_t(Instruction::POP), // second ++ - // Stack here: a x a^(a+2)^(a+2) - uint8_t(Instruction::DUP3), // will change - uint8_t(Instruction::XOR), - uint8_t(Instruction::SWAP1), - uint8_t(Instruction::POP), - uint8_t(Instruction::DUP1)}); - // Stack here: a x a^(a+2)^(a+2)^a + bytes expectation{ + uint8_t(Instruction::DUP2), + uint8_t(Instruction::DUP1), + uint8_t(Instruction::PUSH1), 0x1, + uint8_t(Instruction::ADD), + // Stack here: a x a (a+1) + uint8_t(Instruction::SWAP3), + uint8_t(Instruction::POP), // first ++ + // Stack here: (a+1) x a + uint8_t(Instruction::DUP3), + uint8_t(Instruction::PUSH1), 0x1, + uint8_t(Instruction::ADD), + // Stack here: (a+1) x a (a+2) + uint8_t(Instruction::SWAP3), + uint8_t(Instruction::POP), + // Stack here: (a+2) x a + uint8_t(Instruction::DUP3), // second ++ + uint8_t(Instruction::XOR), + // Stack here: (a+2) x a^(a+2) + uint8_t(Instruction::DUP3), + uint8_t(Instruction::DUP1), + uint8_t(Instruction::PUSH1), 0x1, + uint8_t(Instruction::SWAP1), + uint8_t(Instruction::SUB), + // Stack here: (a+2) x a^(a+2) (a+2) (a+1) + uint8_t(Instruction::SWAP4), + uint8_t(Instruction::POP), // first -- + uint8_t(Instruction::XOR), + // Stack here: (a+1) x a^(a+2)^(a+2) + uint8_t(Instruction::DUP3), + uint8_t(Instruction::PUSH1), 0x1, + uint8_t(Instruction::SWAP1), + uint8_t(Instruction::SUB), + // Stack here: (a+1) x a^(a+2)^(a+2) a + uint8_t(Instruction::SWAP3), + uint8_t(Instruction::POP), // second ++ + // Stack here: a x a^(a+2)^(a+2) + uint8_t(Instruction::DUP3), // will change + uint8_t(Instruction::XOR), + uint8_t(Instruction::SWAP1), + uint8_t(Instruction::POP), + uint8_t(Instruction::DUP1) + }; + // Stack here: a x a^(a+2)^(a+2)^a BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } @@ -446,16 +517,31 @@ BOOST_AUTO_TEST_CASE(assignment) bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}}); // Stack: a, b - bytes expectation({uint8_t(Instruction::PUSH1), 0x2, - uint8_t(Instruction::DUP2), - uint8_t(Instruction::DUP4), - uint8_t(Instruction::ADD), - // Stack here: a b 2 a+b - uint8_t(Instruction::SWAP3), - uint8_t(Instruction::POP), - uint8_t(Instruction::DUP3), - // Stack here: a+b b 2 a+b - uint8_t(Instruction::MUL)}); + bytes expectation; + if (dev::test::Options::get().optimize) + expectation = { + uint8_t(Instruction::DUP1), + uint8_t(Instruction::DUP3), + uint8_t(Instruction::ADD), + uint8_t(Instruction::SWAP2), + uint8_t(Instruction::POP), + uint8_t(Instruction::DUP2), + uint8_t(Instruction::PUSH1), 0x2, + uint8_t(Instruction::MUL) + }; + else + expectation = { + uint8_t(Instruction::PUSH1), 0x2, + uint8_t(Instruction::DUP2), + uint8_t(Instruction::DUP4), + uint8_t(Instruction::ADD), + // Stack here: a b 2 a+b + uint8_t(Instruction::SWAP3), + uint8_t(Instruction::POP), + uint8_t(Instruction::DUP3), + // Stack here: a+b b 2 a+b + uint8_t(Instruction::MUL) + }; BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); }