diff --git a/Changelog.md b/Changelog.md index e84204f01..46be38d41 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,7 @@ Language Features: Compiler Features: * Function calls with named arguments now work with overloaded functions. * Inline Assembly: Issue error when using ``callvalue()`` inside nonpayable function (in the same way that ``msg.value`` already does). + * Standard JSON Interface: Support "Yul" as input language. * SMTChecker: Show callstack together with model if applicable. * SMTChecker: Support modifiers. * Yul Optimizer: Enable stack allocation optimization by default if yul optimizer is active (disable in yulDetails). diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index ca773fa72..4bf8fa73f 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -147,7 +147,7 @@ Input Description .. code-block:: none { - // Required: Source code language, such as "Solidity", "Vyper", "lll", "assembly", etc. + // Required: Source code language. Currently supported are "Solidity" and "Yul". "language": "Solidity", // Required "sources": @@ -263,8 +263,9 @@ Input Description // devdoc - Developer documentation (natspec) // userdoc - User documentation (natspec) // metadata - Metadata - // ir - New assembly format before desugaring - // evm.assembly - New assembly format after desugaring + // ir - Yul intermediate representation of the code before optimization + // irOptimized - Intermediate representation after optimization + // evm.assembly - New assembly format // evm.legacyAssembly - Old-style assembly format in JSON // evm.bytecode.object - Bytecode object // evm.bytecode.opcodes - Opcodes list @@ -273,8 +274,8 @@ Input Description // evm.deployedBytecode* - Deployed bytecode (has the same options as evm.bytecode) // evm.methodIdentifiers - The list of function hashes // evm.gasEstimates - Function gas estimates - // ewasm.wast - eWASM S-expressions format (not supported atm) - // ewasm.wasm - eWASM binary format (not supported atm) + // ewasm.wast - eWASM S-expressions format (not supported at the moment) + // ewasm.wasm - eWASM binary format (not supported at the moment) // // Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every // target part of that output. Additionally, `*` can be used as a wildcard to request everything. diff --git a/docs/yul.rst b/docs/yul.rst index 13a1e9b62..d13af263f 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -15,6 +15,23 @@ It can already be used for "inline assembly" inside Solidity and future versions of the Solidity compiler will even use Yul as intermediate language. It should also be easy to build high-level optimizer stages for Yul. +In its flavour of inline-assembly, Yul can be used as a language setting +for the :ref:`standard-json interface `: + +:: + + { + "language": "Yul", + "sources": { "input.yul": { "content": "{ sstore(0, 1) }" } }, + "settings": { + "outputSelection": { "*": { "*": ["*"], "": [ "*" ] } }, + "optimizer": { "enabled": true, "details": { "yul": true } } + } + } + +Furthermore, the commandline interface can be switched to Yul mode +using ``solc --strict-assembly``. + .. note:: Note that the flavour used for "inline assembly" does not have types diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 607f2d42e..c7fd2dbb9 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,7 @@ using namespace std; using namespace dev; using namespace langutil; using namespace dev::solidity; +using namespace yul; namespace { @@ -354,7 +356,6 @@ boost::optional checkOutputSelection(Json::Value const& _outputSele return boost::none; } - /// Validates the optimizer settings and returns them in a parsed object. /// On error returns the json-formatted error message. boost::variant parseOptimizerSettings(Json::Value const& _jsonInput) @@ -427,8 +428,7 @@ boost::variant StandardCompile if (auto result = checkRootKeys(_input)) return *result; - if (_input["language"] != "Solidity") - return formatFatalError("JSONError", "Only \"Solidity\" is supported as a language."); + ret.language = _input["language"].asString(); Json::Value const& sources = _input["sources"]; @@ -850,15 +850,86 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting return output; } + +Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) +{ + if (_inputsAndSettings.sources.size() != 1) + return formatFatalError("JSONError", "Yul mode only supports exactly one input file."); + if (!_inputsAndSettings.smtLib2Responses.empty()) + return formatFatalError("JSONError", "Yul mode does not support smtlib2responses."); + if (!_inputsAndSettings.remappings.empty()) + return formatFatalError("JSONError", "Field \"settings.remappings\" cannot be used for Yul."); + if (!_inputsAndSettings.libraries.empty()) + return formatFatalError("JSONError", "Field \"settings.libraries\" cannot be used for Yul."); + + Json::Value output = Json::objectValue; + + AssemblyStack stack(_inputsAndSettings.evmVersion, AssemblyStack::Language::StrictAssembly); + string const& sourceName = _inputsAndSettings.sources.begin()->first; + string const& sourceContents = _inputsAndSettings.sources.begin()->second; + if (!stack.parseAndAnalyze(sourceName, sourceContents)) + { + Json::Value errors = Json::arrayValue; + for (auto const& error: stack.errors()) + { + auto err = dynamic_pointer_cast(error); + + errors.append(formatErrorWithException( + *error, + err->type() == Error::Type::Warning, + err->typeName(), + "general", + "" + )); + } + output["errors"] = errors; + return output; + } + + string contractName = stack.parserResult()->name.str(); + + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir")) + output["contracts"][sourceName][contractName]["ir"] = stack.print(); + + if (_inputsAndSettings.optimiserSettings.runYulOptimiser) + stack.optimize(); + + MachineAssemblyObject object = stack.assemble( + AssemblyStack::Machine::EVM, + _inputsAndSettings.optimiserSettings.optimizeStackAllocation + ); + + if (isArtifactRequested( + _inputsAndSettings.outputSelection, + sourceName, + contractName, + { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } + )) + output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, nullptr); + + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized")) + output["contracts"][sourceName][contractName]["irOptimized"] = stack.print(); + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly")) + output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly; + + return output; +} + + Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept { try { auto parsed = parseInput(_input); - if (parsed.type() == typeid(InputsAndSettings)) - return compileSolidity(boost::get(std::move(parsed))); - else + if (parsed.type() == typeid(Json::Value)) return boost::get(std::move(parsed)); + InputsAndSettings settings = boost::get(std::move(parsed)); + if (settings.language == "Solidity") + return compileSolidity(std::move(settings)); + else if (settings.language == "Yul") + return compileYul(std::move(settings)); + else + return formatFatalError("JSONError", "Only \"Solidity\" or \"Yul\" is supported as a language."); } catch (Json::LogicError const& _exception) { diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index 63852fbbd..daae7797c 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -58,6 +58,7 @@ public: private: struct InputsAndSettings { + std::string language; Json::Value errors; std::map sources; std::map smtLib2Responses; @@ -74,6 +75,7 @@ private: boost::variant parseInput(Json::Value const& _input); Json::Value compileSolidity(InputsAndSettings _inputsAndSettings); + Json::Value compileYul(InputsAndSettings _inputsAndSettings); ReadCallback::Callback m_readFile; }; diff --git a/test/cmdlineTests/standard_yul/input.json b/test/cmdlineTests/standard_yul/input.json new file mode 100644 index 000000000..fb26c9751 --- /dev/null +++ b/test/cmdlineTests/standard_yul/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul/output.json b/test/cmdlineTests/standard_yul/output.json new file mode 100644 index 000000000..1724d333e --- /dev/null +++ b/test/cmdlineTests/standard_yul/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"object":{"evm":{"assembly":" /* \"A\":17:18 */\n 0x00\n /* \"A\":11:19 */\n mload\n /* \"A\":38:39 */\n 0x00\n /* \"A\":34:35 */\n 0x00\n /* \"A\":31:32 */\n dup3\n /* \"A\":27:36 */\n add\n /* \"A\":20:40 */\n sstore\n /* \"A\":0:42 */\n pop\n","bytecode":{"linkReferences":{},"object":"6000516000600082015550","opcodes":"PUSH1 0x0 MLOAD PUSH1 0x0 PUSH1 0x0 DUP3 ADD SSTORE POP ","sourceMap":""}},"ir":"object \"object\" {\n code {\n let x := mload(0)\n sstore(add(x, 0), 0)\n }\n}\n","irOptimized":"object \"object\" {\n code {\n let x := mload(0)\n sstore(add(x, 0), 0)\n }\n}\n"}}}} diff --git a/test/cmdlineTests/standard_yul_embedded_object_name/input.json b/test/cmdlineTests/standard_yul_embedded_object_name/input.json new file mode 100644 index 000000000..49761d253 --- /dev/null +++ b/test/cmdlineTests/standard_yul_embedded_object_name/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "object \"NamedObject\" { code { let x := dataoffset(\"DataName\") sstore(add(x, 0), 0) } data \"DataName\" \"abc\" object \"OtherObject\" { code { revert(0, 0) } } }" + } + }, + "settings": + { + "outputSelection": + { + "A": { "OtherObject": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_embedded_object_name/output.json b/test/cmdlineTests/standard_yul_embedded_object_name/output.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/cmdlineTests/standard_yul_embedded_object_name/output.json @@ -0,0 +1 @@ +{} diff --git a/test/cmdlineTests/standard_yul_invalid_object_name/input.json b/test/cmdlineTests/standard_yul_invalid_object_name/input.json new file mode 100644 index 000000000..715075310 --- /dev/null +++ b/test/cmdlineTests/standard_yul_invalid_object_name/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + } + }, + "settings": + { + "outputSelection": + { + "A": { "OtherObject": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_invalid_object_name/output.json b/test/cmdlineTests/standard_yul_invalid_object_name/output.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/cmdlineTests/standard_yul_invalid_object_name/output.json @@ -0,0 +1 @@ +{} diff --git a/test/cmdlineTests/standard_yul_multiple_files/input.json b/test/cmdlineTests/standard_yul_multiple_files/input.json new file mode 100644 index 000000000..d63a218f0 --- /dev/null +++ b/test/cmdlineTests/standard_yul_multiple_files/input.json @@ -0,0 +1,21 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + }, + "B": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_multiple_files/output.json b/test/cmdlineTests/standard_yul_multiple_files/output.json new file mode 100644 index 000000000..b748cd50f --- /dev/null +++ b/test/cmdlineTests/standard_yul_multiple_files/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"Yul mode only supports exactly one input file.","message":"Yul mode only supports exactly one input file.","severity":"error","type":"JSONError"}]} \ No newline at end of file diff --git a/test/cmdlineTests/standard_yul_multiple_files_selected/input.json b/test/cmdlineTests/standard_yul_multiple_files_selected/input.json new file mode 100644 index 000000000..faf1ead48 --- /dev/null +++ b/test/cmdlineTests/standard_yul_multiple_files_selected/input.json @@ -0,0 +1,21 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + }, + "B": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + } + }, + "settings": + { + "outputSelection": + { + "B": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_multiple_files_selected/output.json b/test/cmdlineTests/standard_yul_multiple_files_selected/output.json new file mode 100644 index 000000000..b748cd50f --- /dev/null +++ b/test/cmdlineTests/standard_yul_multiple_files_selected/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"Yul mode only supports exactly one input file.","message":"Yul mode only supports exactly one input file.","severity":"error","type":"JSONError"}]} \ No newline at end of file diff --git a/test/cmdlineTests/standard_yul_object/input.json b/test/cmdlineTests/standard_yul_object/input.json new file mode 100644 index 000000000..29391e84a --- /dev/null +++ b/test/cmdlineTests/standard_yul_object/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "object \"NamedObject\" { code { let x := dataoffset(\"DataName\") sstore(add(x, 0), 0) } data \"DataName\" \"abc\" }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_object/output.json b/test/cmdlineTests/standard_yul_object/output.json new file mode 100644 index 000000000..5e45df9b7 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"NamedObject":{"evm":{"assembly":" data_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45\n /* \"A\":80:81 */\n 0x00\n /* \"A\":76:77 */\n 0x00\n /* \"A\":73:74 */\n dup3\n /* \"A\":69:78 */\n add\n /* \"A\":62:82 */\n sstore\n /* \"A\":28:84 */\n pop\nstop\ndata_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 616263\n","bytecode":{"linkReferences":{},"object":"600b6000600082015550fe616263","opcodes":"PUSH1 0xB PUSH1 0x0 PUSH1 0x0 DUP3 ADD SSTORE POP INVALID PUSH2 0x6263 ","sourceMap":""}},"ir":"object \"NamedObject\" {\n code {\n let x := dataoffset(\"DataName\")\n sstore(add(x, 0), 0)\n }\n data \"DataName\" hex\"616263\"\n}\n","irOptimized":"object \"NamedObject\" {\n code {\n let x := dataoffset(\"DataName\")\n sstore(add(x, 0), 0)\n }\n data \"DataName\" hex\"616263\"\n}\n"}}}} diff --git a/test/cmdlineTests/standard_yul_object_name/input.json b/test/cmdlineTests/standard_yul_object_name/input.json new file mode 100644 index 000000000..ccc1db2d4 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object_name/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "object \"NamedObject\" { code { let x := dataoffset(\"DataName\") sstore(add(x, 0), 0) } data \"DataName\" \"abc\" object \"OtherObject\" { code { revert(0, 0) } } }" + } + }, + "settings": + { + "outputSelection": + { + "A": { "NamedObject": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_object_name/output.json b/test/cmdlineTests/standard_yul_object_name/output.json new file mode 100644 index 000000000..af29f34a1 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object_name/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"NamedObject":{"evm":{"assembly":" data_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45\n /* \"A\":80:81 */\n 0x00\n /* \"A\":76:77 */\n 0x00\n /* \"A\":73:74 */\n dup3\n /* \"A\":69:78 */\n add\n /* \"A\":62:82 */\n sstore\n /* \"A\":28:84 */\n pop\nstop\ndata_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 616263\n\nsub_0: assembly {\n /* \"A\":147:148 */\n 0x00\n /* \"A\":144:145 */\n 0x00\n /* \"A\":137:149 */\n revert\n}\n","bytecode":{"linkReferences":{},"object":"600b6000600082015550fe616263","opcodes":"PUSH1 0xB PUSH1 0x0 PUSH1 0x0 DUP3 ADD SSTORE POP INVALID PUSH2 0x6263 ","sourceMap":""}},"ir":"object \"NamedObject\" {\n code {\n let x := dataoffset(\"DataName\")\n sstore(add(x, 0), 0)\n }\n data \"DataName\" hex\"616263\"\n object \"OtherObject\" {\n code {\n revert(0, 0)\n }\n }\n}\n","irOptimized":"object \"NamedObject\" {\n code {\n let x := dataoffset(\"DataName\")\n sstore(add(x, 0), 0)\n }\n data \"DataName\" hex\"616263\"\n object \"OtherObject\" {\n code {\n revert(0, 0)\n }\n }\n}\n"}}}} diff --git a/test/cmdlineTests/standard_yul_optimized/input.json b/test/cmdlineTests/standard_yul_optimized/input.json new file mode 100644 index 000000000..97611e5fb --- /dev/null +++ b/test/cmdlineTests/standard_yul_optimized/input.json @@ -0,0 +1,23 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "{ let x := mload(0) sstore(add(x, 0), 0) }" + } + }, + "settings": + { + "optimizer": { + "enabled": true, + "details": { + "yul": true + } + }, + "outputSelection": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} diff --git a/test/cmdlineTests/standard_yul_optimized/output.json b/test/cmdlineTests/standard_yul_optimized/output.json new file mode 100644 index 000000000..5eea1cb16 --- /dev/null +++ b/test/cmdlineTests/standard_yul_optimized/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"object":{"evm":{"assembly":" /* \"A\":17:18 */\n 0x00\n 0x00\n /* \"A\":11:19 */\n mload\n /* \"A\":20:40 */\n sstore\n","bytecode":{"linkReferences":{},"object":"600060005155","opcodes":"PUSH1 0x0 PUSH1 0x0 MLOAD SSTORE ","sourceMap":""}},"ir":"object \"object\" {\n code {\n let x := mload(0)\n sstore(add(x, 0), 0)\n }\n}\n","irOptimized":"object \"object\" {\n code {\n sstore(mload(0), 0)\n }\n}\n"}}}} diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 1a539bc8f..707ed65e6 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -124,11 +124,12 @@ BOOST_AUTO_TEST_CASE(invalid_language) { char const* input = R"( { - "language": "INVALID" + "language": "INVALID", + "sources": { "name": { "content": "abc" } } } )"; Json::Value result = compile(input); - BOOST_CHECK(containsError(result, "JSONError", "Only \"Solidity\" is supported as a language.")); + BOOST_CHECK(containsError(result, "JSONError", "Only \"Solidity\" or \"Yul\" is supported as a language.")); } BOOST_AUTO_TEST_CASE(valid_language) @@ -139,7 +140,7 @@ BOOST_AUTO_TEST_CASE(valid_language) } )"; Json::Value result = compile(input); - BOOST_CHECK(!containsError(result, "JSONError", "Only \"Solidity\" is supported as a language.")); + BOOST_CHECK(!containsError(result, "JSONError", "Only \"Solidity\" or \"Yul\" is supported as a language.")); } BOOST_AUTO_TEST_CASE(no_sources)