Process and validate standard-json optimizer settings.

This commit is contained in:
chriseth 2019-02-21 17:39:47 +01:00
parent cf5c13f9c7
commit 4d10f4b4cf
10 changed files with 309 additions and 24 deletions

View File

@ -191,7 +191,30 @@ Input Description
"enabled": true, "enabled": true,
// Optimize for how many times you intend to run the code. // 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. // 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 "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) // Metadata settings (optional)

View File

@ -292,10 +292,27 @@ boost::optional<Json::Value> checkSettingsKeys(Json::Value const& _input)
boost::optional<Json::Value> checkOptimizerKeys(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"); 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) boost::optional<Json::Value> checkMetadataKeys(Json::Value const& _input)
{ {
if (_input.isObject() && _input.isMember("useLiteralContent") && !_input["useLiteralContent"].isBool()) 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) Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
{ {
m_compilerStack.reset(false); m_compilerStack.reset(false);
@ -512,29 +584,9 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
m_compilerStack.setRemappings(remappings); m_compilerStack.setRemappings(remappings);
if (settings.isMember("optimizer")) if (settings.isMember("optimizer"))
{ if (auto result = parseOptimizerSettings(settings["optimizer"]))
Json::Value optimizerSettings = settings["optimizer"];
if (auto result = checkOptimizerKeys(optimizerSettings))
return *result; 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; map<string, h160> libraries;
Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue)); Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue));
if (!jsonLibraries.isObject()) if (!jsonLibraries.isObject())

View File

@ -24,6 +24,8 @@
#include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/CompilerStack.h>
#include <boost/optional.hpp>
namespace dev namespace dev
{ {
@ -53,6 +55,10 @@ public:
std::string compile(std::string const& _input) noexcept; std::string compile(std::string const& _input) noexcept;
private: 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); Json::Value compileInternal(Json::Value const& _input);
CompilerStack m_compilerStack; CompilerStack m_compilerStack;

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

@ -242,7 +242,7 @@ BOOST_AUTO_TEST_CASE(optimizer_enabled_not_boolean)
} }
)"; )";
Json::Value result = compile(input); 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) 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_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() BOOST_AUTO_TEST_SUITE_END()