diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 39ea69350..5bfc30b65 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -67,6 +67,10 @@ set(sources codegen/MultiUseYulFunctionCollector.cpp codegen/YulUtilFunctions.h codegen/YulUtilFunctions.cpp + codegen/ir/IRGenerator.cpp + codegen/ir/IRGenerator.h + codegen/ir/IRGenerationContext.cpp + codegen/ir/IRGenerationContext.h formal/SMTChecker.cpp formal/SMTChecker.h formal/SMTLib2Interface.cpp diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp new file mode 100644 index 000000000..0b5e39cf2 --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -0,0 +1,37 @@ +/* + 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 . +*/ +/** + * Class that contains contextual information during IR generation. + */ + +#include + +#include + +using namespace dev; +using namespace dev::solidity; +using namespace std; + +string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl) +{ + solUnimplementedAssert( + _varDecl.annotation().type->sizeOnStack() == 1, + "Multi-slot types not yet implemented." + ); + + return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id()); +} diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h new file mode 100644 index 000000000..f6a71e578 --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -0,0 +1,63 @@ +/* + 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 . +*/ +/** + * Class that contains contextual information during IR generation. + */ + +#pragma once + +#include + +#include + +#include + +#include +#include + +namespace dev +{ +namespace solidity +{ + +class VariableDeclaration; + +/** + * Class that contains contextual information during IR generation. + */ +class IRGenerationContext +{ +public: + IRGenerationContext(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + m_evmVersion(_evmVersion), + m_optimiserSettings(std::move(_optimiserSettings)), + m_functions(std::make_shared()) + {} + + std::shared_ptr functionCollector() const { return m_functions; } + + std::string addLocalVariable(VariableDeclaration const& _varDecl); + +private: + langutil::EVMVersion m_evmVersion; + OptimiserSettings m_optimiserSettings; + std::map m_localVariables; + std::shared_ptr m_functions; +}; + +} +} diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp new file mode 100644 index 000000000..1674e7cdd --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -0,0 +1,243 @@ +/* + 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 . +*/ +/** + * @author Alex Beregszaszi + * @date 2017 + * Component that translates Solidity code into Yul. + */ + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +pair IRGenerator::run(ContractDefinition const& _contract) +{ + // TODO Would be nice to pretty-print this while retaining comments. + string ir = generateIR(_contract); + + yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); + if (!asmStack.parseAndAnalyze("", ir)) + { + string errorMessage; + for (auto const& error: asmStack.errors()) + errorMessage += langutil::SourceReferenceFormatter::formatExceptionInformation( + *error, + (error->type() == langutil::Error::Type::Warning) ? "Warning" : "Error" + ); + solAssert(false, "Invalid IR generated:\n" + errorMessage + "\n" + ir); + } + asmStack.optimize(); + + string warning = + "/*******************************************************\n" + " * WARNING *\n" + " * Solidity to Yul compilation is still EXPERIMENTAL *\n" + " * It can result in LOSS OF FUNDS or worse *\n" + " * !USE AT YOUR OWN RISK! *\n" + " *******************************************************/\n\n"; + + return {warning + ir, warning + asmStack.print()}; +} + +string IRGenerator::generateIR(ContractDefinition const& _contract) +{ + Whiskers t(R"( + object "" { + code { + + + + + } + object "" { + code { + + + + } + } + } + )"); + + resetContext(); + t("CreationObject", creationObjectName(_contract)); + t("memoryInit", memoryInit()); + t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : ""); + t("deploy", deployCode(_contract)); + t("functions", m_context.functionCollector()->requestedFunctions()); + + resetContext(); + t("RuntimeObject", runtimeObjectName(_contract)); + t("dispatch", dispatchRoutine(_contract)); + t("runtimeFunctions", m_context.functionCollector()->requestedFunctions()); + return t.render(); +} + +string IRGenerator::generateIRFunction(FunctionDefinition const& _function) +{ + string functionName = "fun_" + to_string(_function.id()) + "_" + _function.name(); + return m_context.functionCollector()->createFunction(functionName, [&]() { + Whiskers t("function () {}"); + t("functionName", functionName); + string params; + for (auto const& varDecl: _function.parameters()) + params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl); + t("params", params); + string retParams; + for (auto const& varDecl: _function.returnParameters()) + retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl); + t("returns", retParams.empty() ? "" : " -> " + retParams); + return t.render(); + }); +} + +string IRGenerator::constructorCode(FunctionDefinition const& _constructor) +{ + string out; + if (!_constructor.isPayable()) + out = callValueCheck(); + + solUnimplemented("Constructors are not yet implemented."); + + return out; +} + +string IRGenerator::deployCode(ContractDefinition const& _contract) +{ + Whiskers t(R"X( + codecopy(0, dataoffset(""), datasize("")) + return(0, datasize("")) + )X"); + t("object", runtimeObjectName(_contract)); + return t.render(); +} + +string IRGenerator::callValueCheck() +{ + return "if callvalue() { revert(0, 0) }"; +} + +string IRGenerator::creationObjectName(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + to_string(_contract.id()); +} + +string IRGenerator::runtimeObjectName(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + to_string(_contract.id()) + "_deployed"; +} + +string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) +{ + Whiskers t(R"X( + if iszero(lt(calldatasize(), 4)) + { + let selector := (calldataload(0)) + switch selector + <#cases> + case + { + // + + (4, calldatasize()) + () + let memPos := (0) + let memEnd := (memPos ) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + + )X"); + t("shr224", m_utils.shiftRightFunction(224)); + vector> functions; + for (auto const& function: _contract.interfaceFunctions()) + { + functions.push_back({}); + map& templ = functions.back(); + templ["functionSelector"] = "0x" + function.first.hex(); + FunctionTypePointer const& type = function.second; + templ["functionName"] = type->externalSignature(); + templ["callValueCheck"] = type->isPayable() ? "" : callValueCheck(); + + unsigned paramVars = make_shared(type->parameterTypes())->sizeOnStack(); + unsigned retVars = make_shared(type->returnParameterTypes())->sizeOnStack(); + templ["assignToParams"] = paramVars == 0 ? "" : "let " + m_utils.suffixedVariableNameList("param_", 0, paramVars) + " := "; + templ["assignToRetParams"] = retVars == 0 ? "" : "let " + m_utils.suffixedVariableNameList("ret_", 0, retVars) + " := "; + + ABIFunctions abiFunctions(m_evmVersion, m_context.functionCollector()); + templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes()); + templ["params"] = m_utils.suffixedVariableNameList("param_", 0, paramVars); + templ["retParams"] = m_utils.suffixedVariableNameList("ret_", retVars, 0); + templ["function"] = generateIRFunction(dynamic_cast(type->declaration())); + templ["allocate"] = m_utils.allocationFunction(); + templ["abiEncode"] = abiFunctions.tupleEncoder(type->returnParameterTypes(), type->returnParameterTypes(), false); + templ["comma"] = retVars == 0 ? "" : ", "; + } + t("cases", functions); + if (FunctionDefinition const* fallback = _contract.fallbackFunction()) + { + string fallbackCode; + if (!fallback->isPayable()) + fallbackCode += callValueCheck(); + fallbackCode += generateIRFunction(*fallback) + "() stop()"; + + t("fallback", fallbackCode); + } + else + t("fallback", "revert(0, 0)"); + return t.render(); +} + +string IRGenerator::memoryInit() +{ + // This function should be called at the beginning of the EVM call frame + // and thus can assume all memory to be zero, including the contents of + // the "zero memory area" (the position CompilerUtils::zeroPointer points to). + return + Whiskers{"mstore(, )"} + ("memPtr", to_string(CompilerUtils::freeMemoryPointer)) + ("generalPurposeStart", to_string(CompilerUtils::generalPurposeMemoryStart)) + .render(); +} + +void IRGenerator::resetContext() +{ + solAssert( + m_context.functionCollector()->requestedFunctions().empty(), + "Reset context while it still had functions." + ); + m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings); + m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector()); +} diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h new file mode 100644 index 000000000..c5986f7e9 --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -0,0 +1,78 @@ +/* + 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 . +*/ +/** + * @author Alex Beregszaszi + * @date 2017 + * Component that translates Solidity code into Yul. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class SourceUnit; + +class IRGenerator +{ +public: + IRGenerator(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + m_evmVersion(_evmVersion), + m_optimiserSettings(_optimiserSettings), + m_context(_evmVersion, std::move(_optimiserSettings)), + m_utils(_evmVersion, m_context.functionCollector()) + {} + + /// Generates and returns the IR code, in unoptimized and optimized form + /// (or just pretty-printed, depending on the optimizer settings). + std::pair run(ContractDefinition const& _contract); + +private: + std::string generateIR(ContractDefinition const& _contract); + std::string generateIRFunction(FunctionDefinition const& _function); + + std::string constructorCode(FunctionDefinition const& _constructor); + std::string deployCode(ContractDefinition const& _contract); + std::string callValueCheck(); + + std::string creationObjectName(ContractDefinition const& _contract); + std::string runtimeObjectName(ContractDefinition const& _contract); + + std::string dispatchRoutine(ContractDefinition const& _contract); + + std::string memoryInit(); + + void resetContext(); + + langutil::EVMVersion const m_evmVersion; + OptimiserSettings const m_optimiserSettings; + + IRGenerationContext m_context; + YulUtilFunctions m_utils; +}; + +} +} diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 0d9c74fc4..3dfcc9267 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -46,6 +46,8 @@ #include #include +#include + #include #include @@ -146,6 +148,7 @@ void CompilerStack::reset(bool _keepSettings) m_remappings.clear(); m_libraries.clear(); m_evmVersion = langutil::EVMVersion(); + m_generateIR = false; m_optimiserSettings = OptimiserSettings::minimal(); m_metadataLiteralSources = false; } @@ -387,7 +390,11 @@ bool CompilerStack::compile() for (ASTPointer const& node: source->ast->nodes()) if (auto contract = dynamic_cast(node.get())) if (isRequestedContract(*contract)) + { compileContract(*contract, otherCompilers); + if (m_generateIR) + generateIR(*contract); + } m_stackState = CompilationSuccessful; this->link(); return true; @@ -496,6 +503,22 @@ std::string const CompilerStack::filesystemFriendlyName(string const& _contractN return matchContract.contract->name(); } +string const& CompilerStack::yulIR(string const& _contractName) const +{ + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + + return contract(_contractName).yulIR; +} + +string const& CompilerStack::yulIROptimized(string const& _contractName) const +{ + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + + return contract(_contractName).yulIROptimized; +} + eth::LinkerObject const& CompilerStack::object(string const& _contractName) const { if (m_stackState != CompilationSuccessful) @@ -902,6 +925,24 @@ void CompilerStack::compileContract( _otherCompilers[compiledContract.contract] = compiler; } +void CompilerStack::generateIR(ContractDefinition const& _contract) +{ + solAssert(m_stackState >= AnalysisSuccessful, ""); + + if (!_contract.canBeDeployed()) + return; + + Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); + if (!compiledContract.yulIR.empty()) + return; + + for (auto const* dependency: _contract.annotation().contractDependencies) + generateIR(*dependency); + + IRGenerator generator(m_evmVersion, m_optimiserSettings); + tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract); +} + CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const { solAssert(m_stackState >= AnalysisSuccessful, ""); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 2bcb6f0f1..6678cb0c0 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -100,6 +100,7 @@ public: /// and must not emit exceptions. explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback()): m_readFile(_readFile), + m_generateIR(false), m_errorList(), m_errorReporter(m_errorList) {} @@ -143,6 +144,9 @@ public: m_requestedContractNames = _contractNames; } + /// Enable experimental generation of Yul IR code. + void enableIRGeneration(bool _enable = true) { m_generateIR = _enable; } + /// @arg _metadataLiteralSources When true, store sources as literals in the contract metadata. /// Must be set before parsing. void useMetadataLiteralSources(bool _metadataLiteralSources); @@ -202,6 +206,12 @@ public: /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use std::string const filesystemFriendlyName(std::string const& _contractName) const; + /// @returns the IR representation of a contract. + std::string const& yulIR(std::string const& _contractName) const; + + /// @returns the optimized IR representation of a contract. + std::string const& yulIROptimized(std::string const& _contractName) const; + /// @returns the assembled object for a contract. eth::LinkerObject const& object(std::string const& _contractName) const; @@ -273,6 +283,8 @@ private: std::shared_ptr compiler; eth::LinkerObject object; ///< Deployment object (includes the runtime sub-object). eth::LinkerObject runtimeObject; ///< Runtime object. + std::string yulIR; ///< Experimental Yul IR code. + std::string yulIROptimized; ///< Optimized experimental Yul IR code. mutable std::unique_ptr metadata; ///< The metadata json that will be hashed into the chain. mutable std::unique_ptr abi; mutable std::unique_ptr userDocumentation; @@ -299,6 +311,10 @@ private: std::map>& _otherCompilers ); + /// Generate Yul IR for a single contract. + /// The IR is stored but otherwise unused. + void generateIR(ContractDefinition const& _contract); + /// Links all the known library addresses in the available objects. Any unknown /// library will still be kept as an unlinked placeholder in the objects. void link(); @@ -351,6 +367,7 @@ private: OptimiserSettings m_optimiserSettings; langutil::EVMVersion m_evmVersion; std::set m_requestedContractNames; + bool m_generateIR; std::map m_libraries; /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum /// "context:prefix=target" diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 1902880a7..5b033dfce 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -129,12 +129,18 @@ bool hashMatchesContent(string const& _hash, string const& _content) } } -bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact, bool _wildcardMatchesIR) { for (auto const& artifact: _outputSelection) /// @TODO support sub-matching, e.g "evm" matches "evm.assembly" - if (artifact == "*" || artifact == _artifact) + if (artifact == _artifact) return true; + else if (artifact == "*") + { + // "ir" and "irOptimized" can only be matched by "*" if activated. + if ((_artifact != "ir" && _artifact != "irOptimized") || _wildcardMatchesIR) + return true; + } return false; } @@ -151,7 +157,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _art /// /// @TODO optimise this. Perhaps flatten the structure upfront. /// -bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact, bool _wildcardMatchesIR) { if (!_outputSelection.isObject()) return false; @@ -168,7 +174,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil if ( _outputSelection[file].isMember(contract) && _outputSelection[file][contract].isArray() && - isArtifactRequested(_outputSelection[file][contract], _artifact) + isArtifactRequested(_outputSelection[file][contract], _artifact, _wildcardMatchesIR) ) return true; } @@ -176,10 +182,10 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil return false; } -bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector const& _artifacts) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector const& _artifacts, bool _wildcardMatchesIR) { for (auto const& artifact: _artifacts) - if (isArtifactRequested(_outputSelection, _file, _contract, artifact)) + if (isArtifactRequested(_outputSelection, _file, _contract, artifact, _wildcardMatchesIR)) return true; return false; } @@ -193,6 +199,7 @@ bool isBinaryRequested(Json::Value const& _outputSelection) // This does not inculde "evm.methodIdentifiers" on purpose! static vector const outputsThatRequireBinaries{ "*", + "ir", "irOptimized", "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences", "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", @@ -203,11 +210,28 @@ bool isBinaryRequested(Json::Value const& _outputSelection) for (auto const& fileRequests: _outputSelection) for (auto const& requests: fileRequests) for (auto const& output: outputsThatRequireBinaries) - if (isArtifactRequested(requests, output)) + if (isArtifactRequested(requests, output, false)) return true; return false; } +/// @returns true if any Yul IR was requested. Note that as an exception, '*' does not +/// yet match "ir" or "irOptimized" +bool isIRRequested(Json::Value const& _outputSelection) +{ + if (!_outputSelection.isObject()) + return false; + + for (auto const& fileRequests: _outputSelection) + for (auto const& requests: fileRequests) + for (auto const& request: requests) + if (request == "ir" || request == "irOptimized") + return true; + + return false; +} + + Json::Value formatLinkReferences(std::map const& linkReferences) { Json::Value ret(Json::objectValue); @@ -657,6 +681,10 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources); compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection)); + bool const irRequested = isIRRequested(_inputsAndSettings.outputSelection); + + compilerStack.enableIRGeneration(irRequested); + Json::Value errors = std::move(_inputsAndSettings.errors); bool const binariesRequested = isBinaryRequested(_inputsAndSettings.outputSelection); @@ -767,15 +795,17 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting for (string const& query: compilerStack.unhandledSMTLib2Queries()) output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + keccak256(query).hex()] = query; + bool const wildcardMatchesIR = false; + output["sources"] = Json::objectValue; unsigned sourceIndex = 0; for (string const& sourceName: analysisSuccess ? compilerStack.sourceNames() : vector()) { Json::Value sourceResult = Json::objectValue; sourceResult["id"] = sourceIndex++; - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast", wildcardMatchesIR)) sourceResult["ast"] = ASTJsonConverter(false, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST", wildcardMatchesIR)) sourceResult["legacyAST"] = ASTJsonConverter(true, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); output["sources"][sourceName] = sourceResult; } @@ -790,32 +820,38 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting // ABI, documentation and metadata Json::Value contractData(Json::objectValue); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi", wildcardMatchesIR)) contractData["abi"] = compilerStack.contractABI(contractName); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata", wildcardMatchesIR)) contractData["metadata"] = compilerStack.metadata(contractName); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc", wildcardMatchesIR)) contractData["userdoc"] = compilerStack.natspecUser(contractName); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesIR)) contractData["devdoc"] = compilerStack.natspecDev(contractName); + // IR + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesIR)) + contractData["ir"] = compilerStack.yulIR(contractName); + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesIR)) + contractData["irOptimized"] = compilerStack.yulIROptimized(contractName); + // EVM Json::Value evmData(Json::objectValue); - // @TODO: add ir - if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly")) + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesIR)) evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList); - if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly")) + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesIR)) evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName, sourceList); - if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesIR)) evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName); - if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates")) + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesIR)) evmData["gasEstimates"] = compilerStack.gasEstimates(contractName); if (compilationSuccess && isArtifactRequested( _inputsAndSettings.outputSelection, file, name, - { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } + { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }, + wildcardMatchesIR )) evmData["bytecode"] = collectEVMObject( compilerStack.object(contractName), @@ -826,7 +862,8 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting _inputsAndSettings.outputSelection, file, name, - { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" } + { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" }, + wildcardMatchesIR )) evmData["deployedBytecode"] = collectEVMObject( compilerStack.runtimeObject(contractName), @@ -900,7 +937,8 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) string contractName = stack.parserResult()->name.str(); - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir")) + bool const wildcardMatchesIR = true; + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesIR)) output["contracts"][sourceName][contractName]["ir"] = stack.print(); stack.optimize(); @@ -911,13 +949,14 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) _inputsAndSettings.outputSelection, sourceName, contractName, - { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } + { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }, + wildcardMatchesIR )) output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, nullptr); - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesIR)) output["contracts"][sourceName][contractName]["irOptimized"] = stack.print(); - if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly")) + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesIR)) output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly; return output; diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 9de8b70b8..f2d82f274 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -121,6 +121,7 @@ static string const g_strHelp = "help"; static string const g_strInputFile = "input-file"; static string const g_strInterface = "interface"; static string const g_strYul = "yul"; +static string const g_strIR = "ir"; static string const g_strLicense = "license"; static string const g_strLibraries = "libraries"; static string const g_strLink = "link"; @@ -166,6 +167,7 @@ static string const g_argGas = g_strGas; static string const g_argHelp = g_strHelp; static string const g_argInputFile = g_strInputFile; static string const g_argYul = g_strYul; +static string const g_argIR = g_strIR; static string const g_argLibraries = g_strLibraries; static string const g_argLink = g_strLink; static string const g_argMachine = g_strMachine; @@ -293,6 +295,20 @@ void CommandLineInterface::handleOpcode(string const& _contract) } } +void CommandLineInterface::handleIR(string const& _contractName) +{ + if (m_args.count(g_argIR)) + { + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName)); + else + { + sout() << "IR: " << endl; + sout() << m_compiler->yulIR(_contractName) << endl; + } + } +} + void CommandLineInterface::handleBytecode(string const& _contract) { if (m_args.count(g_argOpcodes)) @@ -685,6 +701,7 @@ Allowed options)", (g_argBinary.c_str(), "Binary of the contracts in hex.") (g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.") (g_argAbi.c_str(), "ABI specification of the contracts.") + (g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).") (g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.") (g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.") (g_argNatspecDev.c_str(), "Natspec developer documentation of all contracts.") @@ -907,6 +924,8 @@ bool CommandLineInterface::processInput() m_compiler->setEVMVersion(m_evmVersion); // TODO: Perhaps we should not compile unless requested + m_compiler->enableIRGeneration(m_args.count(g_argIR)); + OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal(); settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as(); settings.runYulOptimiser = m_args.count(g_strOptimizeYul); @@ -1369,6 +1388,7 @@ void CommandLineInterface::outputCompilationResults() handleGasEstimation(contract); handleBytecode(contract); + handleIR(contract); handleSignatureHashes(contract); handleMetadata(contract); handleABI(contract); diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 0d3c656f5..e9c293d17 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -65,6 +65,7 @@ private: void handleAst(std::string const& _argStr); void handleBinary(std::string const& _contract); void handleOpcode(std::string const& _contract); + void handleIR(std::string const& _contract); void handleBytecode(std::string const& _contract); void handleSignatureHashes(std::string const& _contract); void handleMetadata(std::string const& _contract); diff --git a/test/cmdlineTests/standard_irOptimized_requested/input.json b/test/cmdlineTests/standard_irOptimized_requested/input.json new file mode 100644 index 000000000..96ea078bd --- /dev/null +++ b/test/cmdlineTests/standard_irOptimized_requested/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["irOptimized"] } + } + } +} diff --git a/test/cmdlineTests/standard_irOptimized_requested/output.json b/test/cmdlineTests/standard_irOptimized_requested/output.json new file mode 100644 index 000000000..b24d3e826 --- /dev/null +++ b/test/cmdlineTests/standard_irOptimized_requested/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue()\n {\n revert(0, 0)\n }\n abi_decode_tuple_(4, calldatasize())\n fun_5_f()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default {\n }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0)\n {\n revert(0, 0)\n }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n {\n tail := add(headStart, 0)\n }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr))\n {\n revert(0, 0)\n }\n mstore(64, newFreePtr)\n }\n function fun_5_f()\n {\n }\n function shift_right_224_unsigned(value) -> newValue\n {\n newValue := shr(224, value)\n }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_ir_requested/input.json b/test/cmdlineTests/standard_ir_requested/input.json new file mode 100644 index 000000000..37404ddba --- /dev/null +++ b/test/cmdlineTests/standard_ir_requested/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json new file mode 100644 index 000000000..fe4267eeb --- /dev/null +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -0,0 +1 @@ +{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_5_f()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\tfunction fun_5_f() {}\n\t\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\t\tnewValue := shr(224, value)\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}}