Enable EWasm output.

This commit is contained in:
chriseth 2019-06-18 18:11:04 +02:00
parent 67f11104c1
commit c6f7f5b2b0
7 changed files with 157 additions and 31 deletions

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, "");

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"

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;

View File

@ -123,6 +123,7 @@ 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_strEWasm = "ewasm";
static string const g_strLicense = "license";
static string const g_strLibraries = "libraries";
static string const g_strLink = "link";
@ -170,6 +171,7 @@ 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_argEWasm = g_strEWasm;
static string const g_argLibraries = g_strLibraries;
static string const g_argLink = g_strLink;
static string const g_argMachine = g_strMachine;
@ -311,6 +313,20 @@ void CommandLineInterface::handleIR(string const& _contractName)
}
}
void CommandLineInterface::handleEWasm(string const& _contractName)
{
if (m_args.count(g_argEWasm))
{
if (m_args.count(g_argOutputDir))
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->eWasm(_contractName));
else
{
sout() << "eWasm: " << endl;
sout() << m_compiler->eWasm(_contractName) << endl;
}
}
}
void CommandLineInterface::handleBytecode(string const& _contract)
{
if (m_args.count(g_argOpcodes))
@ -705,6 +721,7 @@ Allowed options)",
(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_argEWasm.c_str(), "EWasm text representation 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.")
@ -932,6 +949,7 @@ bool CommandLineInterface::processInput()
// TODO: Perhaps we should not compile unless requested
m_compiler->enableIRGeneration(m_args.count(g_argIR));
m_compiler->enableEWasmGeneration(m_args.count(g_argEWasm));
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as<unsigned>();
@ -1409,6 +1427,7 @@ void CommandLineInterface::outputCompilationResults()
handleBytecode(contract);
handleIR(contract);
handleEWasm(contract);
handleSignatureHashes(contract);
handleMetadata(contract);
handleABI(contract);

View File

@ -66,6 +66,7 @@ private:
void handleBinary(std::string const& _contract);
void handleOpcode(std::string const& _contract);
void handleIR(std::string const& _contract);
void handleEWasm(std::string const& _contract);
void handleBytecode(std::string const& _contract);
void handleSignatureHashes(std::string const& _contract);
void handleMetadata(std::string const& _contract);

View File

@ -0,0 +1,22 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { }"
}
},
"settings":
{
"optimizer":
{
"enabled": true,
"details": {"yul": true}
},
"outputSelection":
{
"*": { "*": ["ewasm.wast"] }
}
}
}

View File

@ -0,0 +1 @@
{"contracts":{"A":{"C":{"ewasm":{"wast":"(module\n (memory $memory (export \"memory\") 1)\n (export \"main\" (func $main))\n\n(func $main\n)\n\n)\n(module\n (memory $memory (export \"memory\") 1)\n (export \"main\" (func $main))\n (global $global_ (mut i64) (i64.const 0))\n (global $global__1 (mut i64) (i64.const 0))\n (global $global__2 (mut i64) (i64.const 0))\n\n(func $main\n (local $_1 i64)\n (local $_2 i64)\n (local $_3 i64)\n (local $_4 i64)\n (local $_5 i64)\n (local $_6 i64)\n (local $_7 i64)\n (local $_8 i64)\n (block\n (set_local $_1 (datasize \"C_2_deployed\"))\n (set_local $_2 (get_global $global_))\n (set_local $_3 (get_global $global__1))\n (set_local $_4 (get_global $global__2))\n \n )\n (block\n (set_local $_5 (dataoffset \"C_2_deployed\"))\n (set_local $_6 (get_global $global_))\n (set_local $_7 (get_global $global__1))\n (set_local $_8 (get_global $global__2))\n \n )\n)\n\n)\n"}}}},"errors":[{"component":"general","formattedMessage":"Warning: The Yul optimiser is still experimental. Do not use it in production unless correctness of generated code is verified with extensive tests.\n","message":"The Yul optimiser is still experimental. Do not use it in production unless correctness of generated code is verified with extensive tests.","severity":"warning","type":"Warning"}],"sources":{"A":{"id":0}}}