Merge pull request #6716 from ethereum/wasmTranslation

[Yul] EVM to Wasm translation
This commit is contained in:
chriseth
2019-06-27 12:31:35 +02:00
committed by GitHub
20 changed files with 627 additions and 40 deletions
+49 -1
View File
@@ -49,6 +49,12 @@
#include <libsolidity/codegen/ir/IRGenerator.h>
#include <libyul/YulString.h>
#include <libyul/AsmPrinter.h>
#include <libyul/backends/wasm/EVMToEWasmTranslator.h>
#include <libyul/backends/wasm/EWasmObjectCompiler.h>
#include <libyul/backends/wasm/WasmDialect.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AssemblyStack.h>
#include <liblangutil/Scanner.h>
#include <liblangutil/SemVerHandler.h>
@@ -73,6 +79,7 @@ static int g_compilerStackCounts = 0;
CompilerStack::CompilerStack(ReadCallback::Callback const& _readFile):
m_readFile{_readFile},
m_generateIR{false},
m_generateEWasm{false},
m_errorList{},
m_errorReporter{m_errorList}
{
@@ -171,6 +178,7 @@ void CompilerStack::reset(bool _keepSettings)
m_libraries.clear();
m_evmVersion = langutil::EVMVersion();
m_generateIR = false;
m_generateEWasm = false;
m_optimiserSettings = OptimiserSettings::minimal();
m_metadataLiteralSources = false;
}
@@ -413,8 +421,10 @@ bool CompilerStack::compile()
if (isRequestedContract(*contract))
{
compileContract(*contract, otherCompilers);
if (m_generateIR)
if (m_generateIR || m_generateEWasm)
generateIR(*contract);
if (m_generateEWasm)
generateEWasm(*contract);
}
m_stackState = CompilationSuccessful;
this->link();
@@ -540,6 +550,14 @@ string const& CompilerStack::yulIROptimized(string const& _contractName) const
return contract(_contractName).yulIROptimized;
}
string const& CompilerStack::eWasm(string const& _contractName) const
{
if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
return contract(_contractName).eWasm;
}
eth::LinkerObject const& CompilerStack::object(string const& _contractName) const
{
if (m_stackState != CompilationSuccessful)
@@ -971,6 +989,36 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract);
}
void CompilerStack::generateEWasm(ContractDefinition const& _contract)
{
solAssert(m_stackState >= AnalysisSuccessful, "");
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
solAssert(!compiledContract.yulIROptimized.empty(), "");
if (!compiledContract.eWasm.empty())
return;
// Re-parse the Yul IR in EVM dialect
yul::AssemblyStack evmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
evmStack.parseAndAnalyze("", compiledContract.yulIROptimized);
// Turn into eWasm dialect
yul::Object ewasmObject = yul::EVMToEWasmTranslator(
yul::EVMDialect::strictAssemblyForEVMObjects(m_evmVersion)
).run(*evmStack.parserResult());
// Re-inject into an assembly stack for the eWasm dialect
yul::AssemblyStack ewasmStack(m_evmVersion, yul::AssemblyStack::Language::EWasm, m_optimiserSettings);
// TODO this is a hack for now - provide as structured AST!
ewasmStack.parseAndAnalyze("", "{}");
*ewasmStack.parserResult() = move(ewasmObject);
ewasmStack.optimize();
//cout << yul::AsmPrinter{}(*ewasmStack.parserResult()->code) << endl;
// Turn into eWasm text representation.
compiledContract.eWasm = ewasmStack.assemble(yul::AssemblyStack::Machine::eWasm).assembly;
}
CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const
{
solAssert(m_stackState >= AnalysisSuccessful, "");
+11
View File
@@ -154,6 +154,9 @@ public:
/// Enable experimental generation of Yul IR code.
void enableIRGeneration(bool _enable = true) { m_generateIR = _enable; }
/// Enable experimental generation of eWasm code. If enabled, IR is also generated.
void enableEWasmGeneration(bool _enable = true) { m_generateEWasm = _enable; }
/// @arg _metadataLiteralSources When true, store sources as literals in the contract metadata.
/// Must be set before parsing.
void useMetadataLiteralSources(bool _metadataLiteralSources);
@@ -219,6 +222,9 @@ public:
/// @returns the optimized IR representation of a contract.
std::string const& yulIROptimized(std::string const& _contractName) const;
/// @returns the eWasm (text) representation of a contract.
std::string const& eWasm(std::string const& _contractName) const;
/// @returns the assembled object for a contract.
eth::LinkerObject const& object(std::string const& _contractName) const;
@@ -296,6 +302,7 @@ private:
eth::LinkerObject runtimeObject; ///< Runtime object.
std::string yulIR; ///< Experimental Yul IR code.
std::string yulIROptimized; ///< Optimized experimental Yul IR code.
std::string eWasm; ///< Experimental eWasm code (text representation).
mutable std::unique_ptr<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
mutable std::unique_ptr<Json::Value const> abi;
mutable std::unique_ptr<Json::Value const> userDocumentation;
@@ -326,6 +333,9 @@ private:
/// The IR is stored but otherwise unused.
void generateIR(ContractDefinition const& _contract);
/// Generate eWasm text representation for a single contract.
void generateEWasm(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();
@@ -379,6 +389,7 @@ private:
langutil::EVMVersion m_evmVersion;
std::set<std::string> m_requestedContractNames;
bool m_generateIR;
bool m_generateEWasm;
std::map<std::string, h160> m_libraries;
/// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum
/// "context:prefix=target"
+54 -30
View File
@@ -129,16 +129,17 @@ bool hashMatchesContent(string const& _hash, string const& _content)
}
}
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact, bool _wildcardMatchesIR)
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact, bool _wildcardMatchesExperimental)
{
static set<string> experimental{"ir", "irOptimized", "wast", "ewasm", "ewasm.wast"};
for (auto const& artifact: _outputSelection)
/// @TODO support sub-matching, e.g "evm" matches "evm.assembly"
if (artifact == _artifact)
return true;
else if (artifact == "*")
{
// "ir" and "irOptimized" can only be matched by "*" if activated.
if ((_artifact != "ir" && _artifact != "irOptimized") || _wildcardMatchesIR)
// "ir", "irOptimized", "wast" and "ewasm.wast" can only be matched by "*" if activated.
if (experimental.count(_artifact) == 0 || _wildcardMatchesExperimental)
return true;
}
return false;
@@ -157,7 +158,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 _wildcardMatchesIR)
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact, bool _wildcardMatchesExperimental)
{
if (!_outputSelection.isObject())
return false;
@@ -174,7 +175,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil
if (
_outputSelection[file].isMember(contract) &&
_outputSelection[file][contract].isArray() &&
isArtifactRequested(_outputSelection[file][contract], _artifact, _wildcardMatchesIR)
isArtifactRequested(_outputSelection[file][contract], _artifact, _wildcardMatchesExperimental)
)
return true;
}
@@ -182,10 +183,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<string> const& _artifacts, bool _wildcardMatchesIR)
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector<string> const& _artifacts, bool _wildcardMatchesExperimental)
{
for (auto const& artifact: _artifacts)
if (isArtifactRequested(_outputSelection, _file, _contract, artifact, _wildcardMatchesIR))
if (isArtifactRequested(_outputSelection, _file, _contract, artifact, _wildcardMatchesExperimental))
return true;
return false;
}
@@ -200,6 +201,7 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
static vector<string> const outputsThatRequireBinaries{
"*",
"ir", "irOptimized",
"wast", "wasm", "ewasm.wast", "ewasm.wasm",
"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",
@@ -215,10 +217,29 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
return false;
}
/// @returns true if any eWasm code was requested. Note that as an exception, '*' does not
/// yet match "ewasm.wast" or "ewasm"
bool isEWasmRequested(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 == "ewasm" || request == "ewasm.wast")
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 (isEWasmRequested(_outputSelection))
return true;
if (!_outputSelection.isObject())
return false;
@@ -231,7 +252,6 @@ bool isIRRequested(Json::Value const& _outputSelection)
return false;
}
Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
{
Json::Value ret(Json::objectValue);
@@ -689,9 +709,9 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources);
compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection));
bool const irRequested = isIRRequested(_inputsAndSettings.outputSelection);
compilerStack.enableIRGeneration(isIRRequested(_inputsAndSettings.outputSelection));
compilerStack.enableIRGeneration(irRequested);
compilerStack.enableEWasmGeneration(isEWasmRequested(_inputsAndSettings.outputSelection));
Json::Value errors = std::move(_inputsAndSettings.errors);
@@ -812,7 +832,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
for (string const& query: compilerStack.unhandledSMTLib2Queries())
output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + keccak256(query).hex()] = query;
bool const wildcardMatchesIR = false;
bool const wildcardMatchesExperimental = false;
output["sources"] = Json::objectValue;
unsigned sourceIndex = 0;
@@ -820,9 +840,9 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
{
Json::Value sourceResult = Json::objectValue;
sourceResult["id"] = sourceIndex++;
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast", wildcardMatchesIR))
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast", wildcardMatchesExperimental))
sourceResult["ast"] = ASTJsonConverter(false, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName));
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST", wildcardMatchesIR))
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST", wildcardMatchesExperimental))
sourceResult["legacyAST"] = ASTJsonConverter(true, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName));
output["sources"][sourceName] = sourceResult;
}
@@ -837,30 +857,34 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
// ABI, documentation and metadata
Json::Value contractData(Json::objectValue);
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi", wildcardMatchesIR))
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi", wildcardMatchesExperimental))
contractData["abi"] = compilerStack.contractABI(contractName);
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata", wildcardMatchesIR))
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata", wildcardMatchesExperimental))
contractData["metadata"] = compilerStack.metadata(contractName);
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc", wildcardMatchesIR))
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc", wildcardMatchesExperimental))
contractData["userdoc"] = compilerStack.natspecUser(contractName);
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesIR))
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesExperimental))
contractData["devdoc"] = compilerStack.natspecDev(contractName);
// IR
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesIR))
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesExperimental))
contractData["ir"] = compilerStack.yulIR(contractName);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesIR))
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesExperimental))
contractData["irOptimized"] = compilerStack.yulIROptimized(contractName);
// eWasm
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ewasm.wast", wildcardMatchesExperimental))
contractData["ewasm"]["wast"] = compilerStack.eWasm(contractName);
// EVM
Json::Value evmData(Json::objectValue);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesIR))
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesExperimental))
evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesIR))
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesExperimental))
evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName, sourceList);
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesIR))
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesExperimental))
evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesIR))
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental))
evmData["gasEstimates"] = compilerStack.gasEstimates(contractName);
if (compilationSuccess && isArtifactRequested(
@@ -868,7 +892,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
file,
name,
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
wildcardMatchesIR
wildcardMatchesExperimental
))
evmData["bytecode"] = collectEVMObject(
compilerStack.object(contractName),
@@ -880,7 +904,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
file,
name,
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" },
wildcardMatchesIR
wildcardMatchesExperimental
))
evmData["deployedBytecode"] = collectEVMObject(
compilerStack.runtimeObject(contractName),
@@ -954,8 +978,8 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
string contractName = stack.parserResult()->name.str();
bool const wildcardMatchesIR = true;
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesIR))
bool const wildcardMatchesExperimental = true;
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["ir"] = stack.print();
stack.optimize();
@@ -967,13 +991,13 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
sourceName,
contractName,
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
wildcardMatchesIR
wildcardMatchesExperimental
))
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, nullptr);
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesIR))
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesIR))
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly;
return output;