diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 0147ff9ca..80e34aa2a 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -191,7 +191,30 @@ Input Description "enabled": true, // Optimize for how many times you intend to run the code. // Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage. - "runs": 200 + "runs": 200, + // Switch optimizer components on or off in detail. + // The "enabled" switch above provides two defaults which can be + // tweaked here. If "details" is given, "enabled" can be omitted. + "details": { + // The peephole optimizer is always on if no details are given, use details to switch it off. + "peephole": true, + // The unused jumpdest remover is always on if no details are given, use details to switch it off. + "jumpdestRemover": true, + // Sometimes re-orders literals in commutative operations. + "orderLiterals": false, + // Removes duplicate code blocks + "deduplicate": false, + // Common subexpression elimination, this is the most complicated step but + // can also provide the largest gain. + "cse": false, + // Optimize representation of literal numbers and strings in code. + "constantOptimizer": false, + // The new Yul optimizer. Mostly operates on the code of ABIEncoderV2. + // It can only be activated through the details here. + "yul": false, + // Future tuning options, currently unused. + "yulDetails": {} + } }, "evmVersion": "byzantium", // Version of the EVM to compile for. Affects type checking and code generation. Can be homestead, tangerineWhistle, spuriousDragon, byzantium or constantinople // Metadata settings (optional) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 4456a504b..b8678706e 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -292,10 +292,27 @@ boost::optional checkSettingsKeys(Json::Value const& _input) boost::optional checkOptimizerKeys(Json::Value const& _input) { - static set keys{"enabled", "runs"}; + static set keys{"details", "enabled", "runs"}; return checkKeys(_input, keys, "settings.optimizer"); } +boost::optional checkOptimizerDetailsKeys(Json::Value const& _input) +{ + static set keys{"peephole", "jumpdestRemover", "orderLiterals", "deduplicate", "cse", "constantOptimizer", "yul", "yulDetails"}; + return checkKeys(_input, keys, "settings.optimizer.details"); +} + +boost::optional checkOptimizerDetail(Json::Value const& _details, std::string const& _name, bool& _setting) +{ + if (_details.isMember(_name)) + { + if (!_details[_name].isBool()) + return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be Boolean"); + _setting = _details[_name].asBool(); + } + return {}; +} + boost::optional checkMetadataKeys(Json::Value const& _input) { if (_input.isObject() && _input.isMember("useLiteralContent") && !_input["useLiteralContent"].isBool()) @@ -351,6 +368,61 @@ boost::optional checkOutputSelection(Json::Value const& _outputSele } +boost::optional StandardCompiler::parseOptimizerSettings(Json::Value const& _jsonInput) +{ + if (auto result = checkOptimizerKeys(_jsonInput)) + return *result; + + OptimiserSettings settings = OptimiserSettings::none(); + + if (_jsonInput.isMember("enabled")) + { + if (!_jsonInput["enabled"].isBool()) + return formatFatalError("JSONError", "The \"enabled\" setting must be a Boolean."); + + settings = _jsonInput["enabled"].asBool() ? OptimiserSettings::enabled() : OptimiserSettings::minimal(); + } + + if (_jsonInput.isMember("runs")) + { + if (!_jsonInput["runs"].isUInt()) + return formatFatalError("JSONError", "The \"runs\" setting must be an unsigned number."); + settings.expectedExecutionsPerDeployment = _jsonInput["runs"].asUInt(); + } + + if (_jsonInput.isMember("details")) + { + Json::Value const& details = _jsonInput["details"]; + if (auto result = checkOptimizerDetailsKeys(details)) + return *result; + + if (auto error = checkOptimizerDetail(details, "peephole", settings.runPeephole)) + return *error; + if (auto error = checkOptimizerDetail(details, "jumpdestRemover", settings.runJumpdestRemover)) + return *error; + if (auto error = checkOptimizerDetail(details, "orderLiterals", settings.runOrderLiterals)) + return *error; + if (auto error = checkOptimizerDetail(details, "deduplicate", settings.runDeduplicate)) + return *error; + if (auto error = checkOptimizerDetail(details, "cse", settings.runCSE)) + return *error; + if (auto error = checkOptimizerDetail(details, "constantOptimizer", settings.runConstantOptimiser)) + return *error; + if (auto error = checkOptimizerDetail(details, "yul", settings.runYulOptimiser)) + return *error; + if (details.isMember("yulDetails")) + { + if (!_jsonInput["yulDetails"].isObject()) + return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting has to be a JSON object."); + if (!_jsonInput["yulDetails"].getMemberNames().empty()) + return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting cannot have any settings yet."); + } + } + m_compilerStack.setOptimiserSettings(std::move(settings)); + return {}; +} + + Json::Value StandardCompiler::compileInternal(Json::Value const& _input) { m_compilerStack.reset(false); @@ -512,29 +584,9 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) m_compilerStack.setRemappings(remappings); if (settings.isMember("optimizer")) - { - Json::Value optimizerSettings = settings["optimizer"]; - - if (auto result = checkOptimizerKeys(optimizerSettings)) + if (auto result = parseOptimizerSettings(settings["optimizer"])) return *result; - if (optimizerSettings.isMember("enabled")) - { - if (!optimizerSettings["enabled"].isBool()) - return formatFatalError("JSONError", "The \"enabled\" setting must be a boolean."); - - bool const optimize = optimizerSettings["enabled"].asBool(); - unsigned optimizeRuns = 200; - if (optimizerSettings.isMember("runs")) - { - if (!optimizerSettings["runs"].isUInt()) - return formatFatalError("JSONError", "The \"runs\" setting must be an unsigned number."); - optimizeRuns = optimizerSettings["runs"].asUInt(); - } - m_compilerStack.setOptimiserSettings(optimize, optimizeRuns); - } - } - map libraries; Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue)); if (!jsonLibraries.isObject()) diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index fc9c3a594..7cc567317 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -24,6 +24,8 @@ #include +#include + namespace dev { @@ -53,6 +55,10 @@ public: std::string compile(std::string const& _input) noexcept; private: + /// Validaes and applies the optimizer settings. + /// On error returns the json-formatted error message. + boost::optional parseOptimizerSettings(Json::Value const& _settings); + Json::Value compileInternal(Json::Value const& _input); CompilerStack m_compilerStack; diff --git a/test/cmdlineTests/standard_optimizer_invalid_detail_type/input.json b/test/cmdlineTests/standard_optimizer_invalid_detail_type/input.json new file mode 100644 index 000000000..f0ce43e37 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_invalid_detail_type/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { "peephole": 7 } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_invalid_detail_type/output.json b/test/cmdlineTests/standard_optimizer_invalid_detail_type/output.json new file mode 100644 index 000000000..15a65e586 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_invalid_detail_type/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"\"settings.optimizer.details.peephole\" must be Boolean","message":"\"settings.optimizer.details.peephole\" must be Boolean","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/standard_optimizer_invalid_details/input.json b/test/cmdlineTests/standard_optimizer_invalid_details/input.json new file mode 100644 index 000000000..850f6f77c --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_invalid_details/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { "notThere": true } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_invalid_details/output.json b/test/cmdlineTests/standard_optimizer_invalid_details/output.json new file mode 100644 index 000000000..8be28f5b3 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_invalid_details/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"Unknown key \"notThere\"","message":"Unknown key \"notThere\"","severity":"error","type":"JSONError"}]} \ No newline at end of file diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json new file mode 100644 index 000000000..056aee91b --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { "yulDetails": 7 } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json new file mode 100644 index 000000000..b71a8a61a --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"The \"yulDetails\" optimizer setting has to be a JSON object.","message":"The \"yulDetails\" optimizer setting has to be a JSON object.","severity":"error","type":"JSONError"}]} diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 0cd395860..ca9bc363a 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -242,7 +242,7 @@ BOOST_AUTO_TEST_CASE(optimizer_enabled_not_boolean) } )"; Json::Value result = compile(input); - BOOST_CHECK(containsError(result, "JSONError", "The \"enabled\" setting must be a boolean.")); + BOOST_CHECK(containsError(result, "JSONError", "The \"enabled\" setting must be a Boolean.")); } BOOST_AUTO_TEST_CASE(optimizer_runs_not_a_number) @@ -859,6 +859,159 @@ BOOST_AUTO_TEST_CASE(evm_version) BOOST_CHECK(result["errors"][0]["message"].asString() == "Invalid EVM version requested."); } +BOOST_AUTO_TEST_CASE(optimizer_settings_default_disabled) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { "A": [ "metadata" ] } + } + }, + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["metadata"].isString()); + Json::Value metadata; + BOOST_CHECK(jsonParseStrict(contract["metadata"].asString(), metadata)); + + Json::Value const& optimizer = metadata["settings"]["optimizer"]; + BOOST_CHECK(optimizer.isMember("enabled")); + BOOST_CHECK(optimizer["enabled"].asBool() == false); + BOOST_CHECK(!optimizer.isMember("details")); + BOOST_CHECK(optimizer["runs"].asUInt() == 200); +} + +BOOST_AUTO_TEST_CASE(optimizer_settings_default_enabled) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { "A": [ "metadata" ] } + }, + "optimizer": { "enabled": true } + }, + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["metadata"].isString()); + Json::Value metadata; + BOOST_CHECK(jsonParseStrict(contract["metadata"].asString(), metadata)); + + Json::Value const& optimizer = metadata["settings"]["optimizer"]; + BOOST_CHECK(optimizer.isMember("enabled")); + BOOST_CHECK(optimizer["enabled"].asBool() == true); + BOOST_CHECK(!optimizer.isMember("details")); + BOOST_CHECK(optimizer["runs"].asUInt() == 200); +} + +BOOST_AUTO_TEST_CASE(optimizer_settings_details_exactly_as_default_disabled) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { "A": [ "metadata" ] } + }, + "optimizer": { "details": { + "constantOptimizer" : false, + "cse" : false, + "deduplicate" : false, + "jumpdestRemover" : true, + "orderLiterals" : false, + "peephole" : true + } } + }, + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["metadata"].isString()); + Json::Value metadata; + BOOST_CHECK(jsonParseStrict(contract["metadata"].asString(), metadata)); + + Json::Value const& optimizer = metadata["settings"]["optimizer"]; + BOOST_CHECK(optimizer.isMember("enabled")); + // enabled is switched to false instead! + BOOST_CHECK(optimizer["enabled"].asBool() == false); + BOOST_CHECK(!optimizer.isMember("details")); + BOOST_CHECK(optimizer["runs"].asUInt() == 200); +} + +BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { "A": [ "metadata" ] } + }, + "optimizer": { "runs": 600, "details": { + "constantOptimizer" : true, + "cse" : false, + "deduplicate" : true, + "jumpdestRemover" : true, + "orderLiterals" : false, + "peephole" : true, + "yul": true + } } + }, + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["metadata"].isString()); + Json::Value metadata; + BOOST_CHECK(jsonParseStrict(contract["metadata"].asString(), metadata)); + + Json::Value const& optimizer = metadata["settings"]["optimizer"]; + BOOST_CHECK(!optimizer.isMember("enabled")); + BOOST_CHECK(optimizer.isMember("details")); + BOOST_CHECK(optimizer["details"]["constantOptimizer"].asBool() == true); + BOOST_CHECK(optimizer["details"]["cse"].asBool() == false); + BOOST_CHECK(optimizer["details"]["deduplicate"].asBool() == true); + BOOST_CHECK(optimizer["details"]["jumpdestRemover"].asBool() == true); + BOOST_CHECK(optimizer["details"]["orderLiterals"].asBool() == false); + BOOST_CHECK(optimizer["details"]["peephole"].asBool() == true); + BOOST_CHECK(optimizer["details"]["yulDetails"].isObject()); + BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 8); + BOOST_CHECK(optimizer["runs"].asUInt() == 600); +} BOOST_AUTO_TEST_SUITE_END()