Merge pull request #8701 from ethereum/solc-yul-chromosome

solc option for selecting yul optimisations
This commit is contained in:
chriseth 2020-04-27 10:44:14 +02:00 committed by GitHub
commit 61b1369fc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 343 additions and 46 deletions

View File

@ -502,6 +502,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
&meter,
_object,
_optimiserSettings.optimizeStackAllocation,
_optimiserSettings.yulOptimiserSteps,
_externalIdentifiers
);

View File

@ -1265,6 +1265,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
{
details["yulDetails"] = Json::objectValue;
details["yulDetails"]["stackAllocation"] = m_optimiserSettings.optimizeStackAllocation;
details["yulDetails"]["optimizerSteps"] = m_optimiserSettings.yulOptimiserSteps;
}
meta["settings"]["optimizer"]["details"] = std::move(details);

View File

@ -23,12 +23,31 @@
#pragma once
#include <cstddef>
#include <string>
namespace solidity::frontend
{
struct OptimiserSettings
{
static char constexpr DefaultYulOptimiserSteps[] =
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse
"["
"xarrscLM" // Turn into SSA and simplify
"cCTUtTOntnfDIul" // Perform structural simplification
"Lcul" // Simplify again
"Vcul jj" // Reverse SSA
// should have good "compilability" property here.
"eul" // Run functional expression inliner
"xarulrul" // Prune a bit more in SSA
"xarrcL" // Turn into SSA again and simplify
"gvif" // Run full inliner
"CTUcarrLsTOtfDncarrIulc" // SSA plus simplify
"]"
"jmuljuljul VcTOcul jmul"; // Make source short and pretty
/// No optimisations at all - not recommended.
static OptimiserSettings none()
{
@ -74,6 +93,7 @@ struct OptimiserSettings
runConstantOptimiser == _other.runConstantOptimiser &&
optimizeStackAllocation == _other.optimizeStackAllocation &&
runYulOptimiser == _other.runYulOptimiser &&
yulOptimiserSteps == _other.yulOptimiserSteps &&
expectedExecutionsPerDeployment == _other.expectedExecutionsPerDeployment;
}
@ -95,6 +115,11 @@ struct OptimiserSettings
bool optimizeStackAllocation = false;
/// Yul optimiser with default settings. Will only run on certain parts of the code for now.
bool runYulOptimiser = false;
/// Sequence of optimisation steps to be performed by Yul optimiser.
/// Note that there are some hard-coded steps in the optimiser and you cannot disable
/// them just by setting this to an empty string. Set @a runYulOptimiser to false if you want
/// no optimisations.
std::string yulOptimiserSteps = DefaultYulOptimiserSteps;
/// 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

@ -25,6 +25,7 @@
#include <libsolidity/ast/ASTJsonConverter.h>
#include <libyul/AssemblyStack.h>
#include <libyul/Exceptions.h>
#include <libyul/optimiser/Suite.h>
#include <liblangutil/SourceReferenceFormatter.h>
#include <libevmasm/Instruction.h>
#include <libsolutil/JSON.h>
@ -402,6 +403,33 @@ std::optional<Json::Value> checkOptimizerDetail(Json::Value const& _details, std
return {};
}
std::optional<Json::Value> checkOptimizerDetailSteps(Json::Value const& _details, std::string const& _name, string& _setting)
{
if (_details.isMember(_name))
{
if (_details[_name].isString())
{
try
{
yul::OptimiserSuite::validateSequence(_details[_name].asString());
}
catch (yul::OptimizerException const& _exception)
{
return formatFatalError(
"JSONError",
"Invalid optimizer step sequence in \"settings.optimizer.details." + _name + "\": " + _exception.what()
);
}
_setting = _details[_name].asString();
}
else
return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be a string");
}
return {};
}
std::optional<Json::Value> checkMetadataKeys(Json::Value const& _input)
{
if (_input.isObject())
@ -511,10 +539,12 @@ boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Valu
if (!settings.runYulOptimiser)
return formatFatalError("JSONError", "\"Providing yulDetails requires Yul optimizer to be enabled.");
if (auto result = checkKeys(details["yulDetails"], {"stackAllocation"}, "settings.optimizer.details.yulDetails"))
if (auto result = checkKeys(details["yulDetails"], {"stackAllocation", "optimizerSteps"}, "settings.optimizer.details.yulDetails"))
return *result;
if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation))
return *error;
if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps))
return *error;
}
}
return { std::move(settings) };

View File

@ -183,7 +183,8 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
dialect,
meter.get(),
_object,
m_optimiserSettings.optimizeStackAllocation
m_optimiserSettings.optimizeStackAllocation,
m_optimiserSettings.yulOptimiserSteps
);
}

View File

@ -67,6 +67,7 @@
#include <libsolutil/CommonData.h>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
using namespace std;
@ -78,6 +79,7 @@ void OptimiserSuite::run(
GasMeter const* _meter,
Object& _object,
bool _optimizeStackAllocation,
string const& _optimisationSequence,
set<YulString> const& _externallyUsedIdentifiers
)
{
@ -93,25 +95,12 @@ void OptimiserSuite::run(
OptimiserSuite suite(_dialect, reservedIdentifiers, Debug::None, ast);
suite.runSequence(
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse
"("
"xarrscLM" // Turn into SSA and simplify
"cCTUtTOntnfDIul" // Perform structural simplification
"Lcul" // Simplify again
"Vcul jj" // Reverse SSA
// Some steps depend on properties ensured by FunctionHoister, FunctionGrouper and
// ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely.
suite.runSequence("fgo", ast);
// should have good "compilability" property here.
"eul" // Run functional expression inliner
"xarulrul" // Prune a bit more in SSA
"xarrcL" // Turn into SSA again and simplify
"gvif" // Run full inliner
"CTUcarrLsTOtfDncarrIulc" // SSA plus simplify
")"
"jmuljuljul VcTOcul jmul", // Make source short and pretty
ast
);
// Now the user-supplied part
suite.runSequence(_optimisationSequence, ast);
// This is a tuning parameter, but actually just prevents infinite loops.
size_t stackCompressorMaxIterations = 16;
@ -235,6 +224,12 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
{VarDeclInitializer::name, 'd'},
};
yulAssert(lookupTable.size() == allSteps().size(), "");
yulAssert((
util::convertContainer<set<char>>(string(NonStepAbbreviations)) -
util::convertContainer<set<char>>(lookupTable | boost::adaptors::map_values)
).size() == string(NonStepAbbreviations).size(),
"Step abbreviation conflicts with a character reserved for another syntactic element"
);
return lookupTable;
}
@ -246,32 +241,44 @@ map<char, string> const& OptimiserSuite::stepAbbreviationToNameMap()
return lookupTable;
}
void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast)
void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
{
string input = _stepAbbreviations;
boost::remove_erase(input, ' ');
boost::remove_erase(input, '\n');
bool insideLoop = false;
for (char abbreviation: input)
for (char abbreviation: _stepAbbreviations)
switch (abbreviation)
{
case '(':
assertThrow(!insideLoop, OptimizerException, "Nested parentheses not supported");
case ' ':
case '\n':
break;
case '[':
assertThrow(!insideLoop, OptimizerException, "Nested brackets are not supported");
insideLoop = true;
break;
case ')':
assertThrow(insideLoop, OptimizerException, "Unbalanced parenthesis");
case ']':
assertThrow(insideLoop, OptimizerException, "Unbalanced brackets");
insideLoop = false;
break;
default:
yulAssert(
string(NonStepAbbreviations).find(abbreviation) == string::npos,
"Unhandled syntactic element in the abbreviation sequence"
);
assertThrow(
stepAbbreviationToNameMap().find(abbreviation) != stepAbbreviationToNameMap().end(),
OptimizerException,
"Invalid optimisation step abbreviation"
"'"s + abbreviation + "' is not a valid step abbreviation"
);
}
assertThrow(!insideLoop, OptimizerException, "Unbalanced parenthesis");
assertThrow(!insideLoop, OptimizerException, "Unbalanced brackets");
}
void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast)
{
validateSequence(_stepAbbreviations);
string input = _stepAbbreviations;
boost::remove_erase(input, ' ');
boost::remove_erase(input, '\n');
auto abbreviationsToSteps = [](string const& _sequence) -> vector<string>
{
@ -281,21 +288,21 @@ void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast)
return steps;
};
// The sequence has now been validated and must consist of pairs of segments that look like this: `aaa(bbb)`
// `aaa` or `(bbb)` can be empty. For example we consider a sequence like `fgo(aaf)Oo` to have
// four segments, the last of which is an empty parenthesis.
// The sequence has now been validated and must consist of pairs of segments that look like this: `aaa[bbb]`
// `aaa` or `[bbb]` can be empty. For example we consider a sequence like `fgo[aaf]Oo` to have
// four segments, the last of which is an empty bracket.
size_t currentPairStart = 0;
while (currentPairStart < input.size())
{
size_t openingParenthesis = input.find('(', currentPairStart);
size_t closingParenthesis = input.find(')', openingParenthesis);
size_t firstCharInside = (openingParenthesis == string::npos ? input.size() : openingParenthesis + 1);
yulAssert((openingParenthesis == string::npos) == (closingParenthesis == string::npos), "");
size_t openingBracket = input.find('[', currentPairStart);
size_t closingBracket = input.find(']', openingBracket);
size_t firstCharInside = (openingBracket == string::npos ? input.size() : openingBracket + 1);
yulAssert((openingBracket == string::npos) == (closingBracket == string::npos), "");
runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingParenthesis - currentPairStart)), _ast);
runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingParenthesis - firstCharInside)), _ast);
runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingBracket - currentPairStart)), _ast);
runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingBracket - firstCharInside)), _ast);
currentPairStart = (closingParenthesis == string::npos ? input.size() : closingParenthesis + 1);
currentPairStart = (closingBracket == string::npos ? input.size() : closingBracket + 1);
}
}

View File

@ -47,6 +47,10 @@ class OptimiserSuite
public:
static constexpr size_t MaxRounds = 12;
/// Special characters that do not represent optimiser steps but are allowed in abbreviation sequences.
/// Some of them (like whitespace) are ignored, others (like brackets) are a part of the syntax.
static constexpr char NonStepAbbreviations[] = " \n[]";
enum class Debug
{
None,
@ -58,9 +62,14 @@ public:
GasMeter const* _meter,
Object& _object,
bool _optimizeStackAllocation,
std::string const& _optimisationSequence,
std::set<YulString> const& _externallyUsedIdentifiers = {}
);
/// Ensures that specified sequence of step abbreviations is well-formed and can be executed.
/// @throw OptimizerException if the sequence is invalid
static void validateSequence(std::string const& _stepAbbreviations);
void runSequence(std::vector<std::string> const& _steps, Block& _ast);
void runSequence(std::string const& _stepAbbreviations, Block& _ast);
void runSequenceUntilStable(

View File

@ -37,6 +37,7 @@
#include <libsolidity/interface/StorageLayout.h>
#include <libyul/AssemblyStack.h>
#include <libyul/optimiser/Suite.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/GasMeter.h>
@ -145,6 +146,7 @@ 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_strYulOptimizations = "yul-optimizations";
static string const g_strOutputDir = "output-dir";
static string const g_strOverwrite = "overwrite";
static string const g_strRevertStrings = "revert-strings";
@ -781,7 +783,6 @@ Allowed options)",
"Import ASTs to be compiled, assumes input holds the AST in compact JSON format. "
"Supported Inputs is the output of the --standard-json or the one produced by --combined-json ast,compact-format"
)
(
g_argAssemble.c_str(),
"Switch to assembly mode, ignoring all options except --machine, --yul-dialect and --optimize and assumes input is assembly."
@ -835,7 +836,12 @@ Allowed options)",
"Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage."
)
(g_strOptimizeYul.c_str(), "Legacy option, ignored. Use the general --optimize to enable Yul optimizer.")
(g_strNoOptimizeYul.c_str(), "Disable Yul optimizer in Solidity.");
(g_strNoOptimizeYul.c_str(), "Disable Yul optimizer in Solidity.")
(
g_strYulOptimizations.c_str(),
po::value<string>()->value_name("steps"),
"Forces yul optimizer to use the specified sequence of optimization steps instead of the built-in one."
);
desc.add(optimizerOptions);
po::options_description outputComponents("Output Components");
outputComponents.add_options()
@ -1168,6 +1174,26 @@ bool CommandLineInterface::processInput()
settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as<unsigned>();
if (m_args.count(g_strNoOptimizeYul))
settings.runYulOptimiser = false;
if (m_args.count(g_strYulOptimizations))
{
if (!settings.runYulOptimiser)
{
serr() << "--" << g_strYulOptimizations << " is invalid if Yul optimizer is disabled" << endl;
return false;
}
try
{
yul::OptimiserSuite::validateSequence(m_args[g_strYulOptimizations].as<string>());
}
catch (yul::OptimizerException const& _exception)
{
serr() << "Invalid optimizer step sequence in --" << g_strYulOptimizations << ": " << _exception.what() << endl;
return false;
}
settings.yulOptimiserSteps = m_args[g_strYulOptimizations].as<string>();
}
settings.optimizeStackAllocation = settings.runYulOptimiser;
m_compiler->setOptimiserSettings(settings);

View File

@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "dhfoDgvulfnTUtnIf\n[ xarrscLM\n]\njmuljuljul VcTOcul jmul"
}
}
}
}
}

View File

@ -0,0 +1 @@
{"sources":{"A":{"id":0}}}

View File

@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "abcdefg{hijklmno}pqr[st]uvwxyz"
}
}
}
}
}

View File

@ -0,0 +1 @@
{"errors":[{"component":"general","formattedMessage":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": 'b' is not a valid step abbreviation","message":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": 'b' is not a valid step abbreviation","severity":"error","type":"JSONError"}]}

View File

@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "a[a][aa[aa]]a"
}
}
}
}
}

View File

@ -0,0 +1 @@
{"errors":[{"component":"general","formattedMessage":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Nested brackets are not supported","message":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Nested brackets are not supported","severity":"error","type":"JSONError"}]}

View File

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

View File

@ -0,0 +1 @@
{"errors":[{"component":"general","formattedMessage":"\"settings.optimizer.details.optimizerSteps\" must be a string","message":"\"settings.optimizer.details.optimizerSteps\" must be a string","severity":"error","type":"JSONError"}]}

View File

@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "a[a]["
}
}
}
}
}

View File

@ -0,0 +1 @@
{"errors":[{"component":"general","formattedMessage":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Unbalanced brackets","message":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Unbalanced brackets","severity":"error","type":"JSONError"}]}

View File

@ -0,0 +1 @@
--ir-optimized --optimize --yul-optimizations dhfoDgvulfnTUtnIf

View File

@ -0,0 +1,6 @@
pragma solidity >=0.0;
contract C
{
constructor() public {}
}

View File

@ -0,0 +1,34 @@
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "C_6" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
codecopy(0, dataoffset("C_6_deployed"), datasize("C_6_deployed"))
return(0, datasize("C_6_deployed"))
}
}
object "C_6_deployed" {
code {
{
mstore(64, 128)
if iszero(lt(calldatasize(), 4))
{
let selector := shift_right_224_unsigned(calldataload(0))
pop(selector)
}
pop(iszero(calldatasize()))
revert(0, 0)
}
function shift_right_224_unsigned(value) -> newValue
{ newValue := shr(224, value) }
}
}
}

View File

@ -0,0 +1 @@
--ir-optimized --yul-optimizations dhfoDgvulfnTUtnIf

View File

@ -0,0 +1 @@
--yul-optimizations is invalid if Yul optimizer is disabled

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
pragma solidity >=0.0;
contract C
{
function f() public pure {}
}

View File

@ -0,0 +1 @@
--ir-optimized --optimize --yul-optimizations abcdefg{hijklmno}pqr[st]uvwxyz

View File

@ -0,0 +1 @@
Invalid optimizer step sequence in --yul-optimizations: 'b' is not a valid step abbreviation

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
pragma solidity >=0.0;
contract C
{
function f() public pure {}
}

View File

@ -0,0 +1 @@
--ir-optimized --optimize --yul-optimizations a[a][aa[aa]]a

View File

@ -0,0 +1 @@
Invalid optimizer step sequence in --yul-optimizations: Nested brackets are not supported

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
pragma solidity >=0.0;
contract C
{
function f() public pure {}
}

View File

@ -0,0 +1 @@
--ir-optimized --optimize --yul-optimizations a[a][

View File

@ -0,0 +1 @@
Invalid optimizer step sequence in --yul-optimizations: Unbalanced brackets

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
pragma solidity >=0.0;
contract C
{
function f() public pure {}
}

View File

@ -21,11 +21,15 @@
#include <string>
#include <boost/test/unit_test.hpp>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/Version.h>
#include <libsolutil/JSON.h>
#include <libsolutil/CommonData.h>
#include <test/Metadata.h>
#include <set>
using namespace std;
using namespace solidity::evmasm;
@ -1058,8 +1062,12 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different)
BOOST_CHECK(optimizer["details"]["peephole"].asBool() == true);
BOOST_CHECK(optimizer["details"]["yul"].asBool() == true);
BOOST_CHECK(optimizer["details"]["yulDetails"].isObject());
BOOST_CHECK(optimizer["details"]["yulDetails"].getMemberNames() == vector<string>{"stackAllocation"});
BOOST_CHECK(
util::convertContainer<set<string>>(optimizer["details"]["yulDetails"].getMemberNames()) ==
(set<string>{"stackAllocation", "optimizerSteps"})
);
BOOST_CHECK(optimizer["details"]["yulDetails"]["stackAllocation"].asBool() == true);
BOOST_CHECK(optimizer["details"]["yulDetails"]["optimizerSteps"].asString() == OptimiserSettings::DefaultYulOptimiserSteps);
BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 8);
BOOST_CHECK(optimizer["runs"].asUInt() == 600);
}

View File

@ -74,6 +74,8 @@
#include <libsolutil/AnsiColorized.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <boost/test/unit_test.hpp>
#include <boost/algorithm/string.hpp>
@ -342,7 +344,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line
yul::Object obj;
obj.code = m_ast;
obj.analysisInfo = m_analysisInfo;
OptimiserSuite::run(*m_dialect, &meter, obj, true);
OptimiserSuite::run(*m_dialect, &meter, obj, true, solidity::frontend::OptimiserSettings::DefaultYulOptimiserSteps);
}
else
{