Merge pull request #5959 from ethereum/optimiser2

Introduce global optimiser settings.
This commit is contained in:
chriseth 2019-03-04 12:54:59 +01:00 committed by GitHub
commit 2e0ea16a0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 905 additions and 219 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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
@ -121,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(

View File

@ -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);

View File

@ -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

View File

@ -35,16 +35,20 @@ void Compiler::compileContract(
bytes const& _metadata
)
{
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns);
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_optimize, 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_optimize, m_optimizeRuns);
m_context.optimise(m_optimiserSettings);
}
std::shared_ptr<eth::Assembly> Compiler::runtimeAssemblyPtr() const

View File

@ -23,6 +23,7 @@
#pragma once
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <liblangutil/EVMVersion.h>
#include <libevmasm/Assembly.h>
#include <functional>
@ -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;

View File

@ -32,6 +32,7 @@
#include <libyul/AsmAnalysisInfo.h>
#include <libyul/backends/evm/AsmCodeGen.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/optimiser/Suite.h>
#include <libyul/YulString.h>
#include <liblangutil/ErrorReporter.h>
@ -328,12 +329,19 @@ void CompilerContext::resetVisitedNodes(ASTNode const* _node)
void CompilerContext::appendInlineAssembly(
string const& _assembly,
vector<string> const& _localVariables,
set<string> const&,
bool _system
set<string> const& _externallyUsedFunctions,
bool _system,
bool _optimise
)
{
int startStackHeight = stackHeight();
set<yul::YulString> 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();
@ -447,6 +484,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

View File

@ -27,6 +27,8 @@
#include <libsolidity/ast/Types.h>
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libevmasm/Assembly.h>
#include <libevmasm/Instruction.h>
#include <liblangutil/EVMVersion.h>
@ -50,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<eth::Assembly>()),
m_evmVersion(_evmVersion),
m_runtimeContext(_runtimeContext),
@ -214,14 +216,15 @@ public:
std::string const& _assembly,
std::vector<std::string> const& _localVariables = std::vector<std::string>(),
std::set<std::string> const& _externallyUsedFunctions = std::set<std::string>(),
bool _system = false
bool _system = false,
bool _optimise = false
);
/// Appends arbitrary data to the end of the bytecode.
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 +274,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.

View File

@ -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_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_optimise).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_optimise).appendConstStateVariableAccessor(_variableDeclaration);
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendConstStateVariableAccessor(_variableDeclaration);
else
ExpressionCompiler(m_context, m_optimise).appendStateVariableAccessor(_variableDeclaration);
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableAccessor(_variableDeclaration);
return false;
}
@ -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()
@ -1053,7 +1059,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_optimiserSettings.runOrderLiterals);
expressionCompiler.compile(_expression);
if (_targetType)
CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType);

View File

@ -28,8 +28,10 @@
#include <functional>
#include <ostream>
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,12 @@ 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,
OptimiserSettings _optimiserSettings
):
m_optimiserSettings(std::move(_optimiserSettings)),
m_runtimeCompiler(_runtimeCompiler),
m_context(_context)
{
@ -129,8 +134,7 @@ 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;
OptimiserSettings const m_optimiserSettings;
/// Pointer to the runtime compiler in case this is a creation compiler.
ContractCompiler* m_runtimeCompiler = nullptr;
CompilerContext& m_context;

View File

@ -449,7 +449,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
{
return dynamic_cast<Literal const*>(&_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);

View File

@ -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):
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<LValue> m_currentLValue;

View File

@ -108,11 +108,17 @@ void CompilerStack::setLibraries(std::map<std::string, h160> 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> compiler = make_shared<Compiler>(m_evmVersion, m_optimize, m_optimizeRuns);
shared_ptr<Compiler> compiler = make_shared<Compiler>(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<Json::LargestUInt>(m_optimiserSettings.expectedExecutionsPerDeployment) < std::numeric_limits<Json::LargestUInt>::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;

View File

@ -24,6 +24,7 @@
#pragma once
#include <libsolidity/interface/ReadFile.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h>
@ -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<std::string> m_requestedContractNames;
std::map<std::string, h160> m_libraries;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @author Alex Beregszaszi
* @date 2017
* Helper class for optimiser settings.
*/
#pragma once
#include <cstddef>
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;
};
}
}

View File

@ -292,10 +292,27 @@ boost::optional<Json::Value> checkSettingsKeys(Json::Value const& _input)
boost::optional<Json::Value> checkOptimizerKeys(Json::Value const& _input)
{
static set<string> keys{"enabled", "runs"};
static set<string> keys{"details", "enabled", "runs"};
return checkKeys(_input, keys, "settings.optimizer");
}
boost::optional<Json::Value> checkOptimizerDetailsKeys(Json::Value const& _input)
{
static set<string> keys{"peephole", "jumpdestRemover", "orderLiterals", "deduplicate", "cse", "constantOptimizer", "yul", "yulDetails"};
return checkKeys(_input, keys, "settings.optimizer.details");
}
boost::optional<Json::Value> 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<Json::Value> checkMetadataKeys(Json::Value const& _input)
{
if (_input.isObject() && _input.isMember("useLiteralContent") && !_input["useLiteralContent"].isBool())
@ -351,6 +368,61 @@ boost::optional<Json::Value> checkOutputSelection(Json::Value const& _outputSele
}
boost::optional<Json::Value> 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<string, h160> libraries;
Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue));
if (!jsonLibraries.isObject())

View File

@ -24,6 +24,8 @@
#include <libsolidity/interface/CompilerStack.h>
#include <boost/optional.hpp>
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<Json::Value> parseOptimizerSettings(Json::Value const& _settings);
Json::Value compileInternal(Json::Value const& _input);
CompilerStack m_compilerStack;

View File

@ -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<string>();
@ -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<unsigned>();
m_compiler->setOptimiserSettings(optimize, runs);
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::enabled() : OptimiserSettings::minimal();
settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as<unsigned>();
settings.runYulOptimiser = m_args.count(g_strOptimizeYul);
m_compiler->setOptimiserSettings(settings);
bool successful = m_compiler->compile();

View File

@ -0,0 +1 @@
--gas

View File

@ -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;
^-------------------------------^

View File

@ -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) {}
}

View File

@ -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

View File

@ -0,0 +1 @@
--gas --optimize --optimize-yul

View File

@ -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;
^-------------------------------^

View File

@ -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) {}
}

View File

@ -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

View File

@ -0,0 +1,16 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": { "peephole": 7 }
}
}
}

View File

@ -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"}]}

View File

@ -0,0 +1,16 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": { "notThere": true }
}
}
}

View File

@ -0,0 +1 @@
{"errors":[{"component":"general","formattedMessage":"Unknown key \"notThere\"","message":"Unknown key \"notThere\"","severity":"error","type":"JSONError"}]}

View File

@ -0,0 +1,16 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": { "yulDetails": 7 }
}
}
}

View File

@ -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"}]}

View File

@ -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),

View File

@ -84,7 +84,10 @@ eth::AssemblyItems compileContract(std::shared_ptr<CharStream> _sourceCode)
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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<ContractDefinition const*, shared_ptr<Compiler const>>{}, bytes());
return compiler.runtimeAssemblyItems();
@ -165,20 +168,35 @@ BOOST_AUTO_TEST_CASE(location_test)
auto codegenCharStream = make_shared<CharStream>("", "--CODEGEN--");
vector<SourceLocation> locations =
vector<SourceLocation>(4, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{8, 17, codegenCharStream}) +
vector<SourceLocation>(3, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{30, 31, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{27, 28, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{20, 32, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(hasShifts ? 19 : 20, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(24, SourceLocation{20, 79, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{49, 58, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{72, 74, sourceCode}) +
vector<SourceLocation>(2, SourceLocation{65, 74, sourceCode}) +
vector<SourceLocation>(2, SourceLocation{20, 79, sourceCode});
vector<SourceLocation> locations;
if (dev::test::Options::get().optimize)
locations =
vector<SourceLocation>(4, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{8, 17, codegenCharStream}) +
vector<SourceLocation>(3, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{30, 31, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{27, 28, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{20, 32, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(19, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(21, SourceLocation{20, 79, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{72, 74, sourceCode}) +
vector<SourceLocation>(2, SourceLocation{20, 79, sourceCode});
else
locations =
vector<SourceLocation>(4, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{8, 17, codegenCharStream}) +
vector<SourceLocation>(3, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{30, 31, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{27, 28, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{20, 32, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(hasShifts ? 19 : 20, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(24, SourceLocation{20, 79, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{49, 58, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{72, 74, sourceCode}) +
vector<SourceLocation>(2, SourceLocation{65, 74, sourceCode}) +
vector<SourceLocation>(2, SourceLocation{20, 79, sourceCode});
checkAssemblyLocations(items, locations);
}

View File

@ -153,7 +153,7 @@ bytes compileFirstExpression(
parametersSize--
);
ExpressionCompiler(context).compile(*extractor.expression());
ExpressionCompiler(context, dev::test::Options::get().optimize).compile(*extractor.expression());
for (vector<string> const& function: _functions)
context << context.functionEntryLabel(dynamic_cast<FunctionDefinition const&>(
@ -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());
}

View File

@ -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()