Merge pull request #10866 from ethereum/ir-stack

Expose libevmasm Assembly in libyul for CompilerStack
This commit is contained in:
chriseth 2021-06-17 16:16:05 +02:00 committed by GitHub
commit a695089fec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 122 additions and 93 deletions

View File

@ -56,6 +56,7 @@
#include <libsolidity/interface/Version.h>
#include <libsolidity/parsing/Parser.h>
#include <libsolidity/codegen/ir/Common.h>
#include <libsolidity/codegen/ir/IRGenerator.h>
#include <libyul/YulString.h>
@ -1227,6 +1228,59 @@ bool onlySafeExperimentalFeaturesActivated(set<ExperimentalFeature> const& featu
}
}
void CompilerStack::assemble(
ContractDefinition const& _contract,
std::shared_ptr<evmasm::Assembly> _assembly,
std::shared_ptr<evmasm::Assembly> _runtimeAssembly
)
{
solAssert(m_stackState >= AnalysisPerformed, "");
solAssert(!m_hasError, "");
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
compiledContract.evmAssembly = _assembly;
solAssert(compiledContract.evmAssembly, "");
try
{
// Assemble deployment (incl. runtime) object.
compiledContract.object = compiledContract.evmAssembly->assemble();
}
catch (evmasm::AssemblyException const&)
{
solAssert(false, "Assembly exception for bytecode");
}
solAssert(compiledContract.object.immutableReferences.empty(), "Leftover immutables.");
compiledContract.evmRuntimeAssembly = _runtimeAssembly;
solAssert(compiledContract.evmRuntimeAssembly, "");
try
{
// Assemble runtime object.
compiledContract.runtimeObject = compiledContract.evmRuntimeAssembly->assemble();
}
catch (evmasm::AssemblyException const&)
{
solAssert(false, "Assembly exception for deployed bytecode");
}
// Throw a warning if EIP-170 limits are exceeded:
// If contract creation returns data with length greater than 0x6000 (214 + 213) bytes,
// contract creation fails with an out of gas error.
if (
m_evmVersion >= langutil::EVMVersion::spuriousDragon() &&
compiledContract.runtimeObject.bytecode.size() > 0x6000
)
m_errorReporter.warning(
5574_error,
_contract.location(),
"Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). "
"This contract may not be deployable on mainnet. "
"Consider enabling the optimizer (with a low \"runs\" value!), "
"turning off revert strings, or using libraries."
);
}
void CompilerStack::compileContract(
ContractDefinition const& _contract,
map<ContractDefinition const*, shared_ptr<Compiler const>>& _otherCompilers
@ -1262,48 +1316,9 @@ void CompilerStack::compileContract(
solAssert(false, "Optimizer exception during compilation");
}
compiledContract.evmAssembly = compiler->assemblyPtr();
solAssert(compiledContract.evmAssembly, "");
try
{
// Assemble deployment (incl. runtime) object.
compiledContract.object = compiledContract.evmAssembly->assemble();
}
catch(evmasm::AssemblyException const&)
{
solAssert(false, "Assembly exception for bytecode");
}
solAssert(compiledContract.object.immutableReferences.empty(), "Leftover immutables.");
compiledContract.evmRuntimeAssembly = compiler->runtimeAssemblyPtr();
solAssert(compiledContract.evmRuntimeAssembly, "");
try
{
// Assemble runtime object.
compiledContract.runtimeObject = compiledContract.evmRuntimeAssembly->assemble();
}
catch(evmasm::AssemblyException const&)
{
solAssert(false, "Assembly exception for deployed bytecode");
}
// Throw a warning if EIP-170 limits are exceeded:
// If contract creation returns data with length greater than 0x6000 (214 + 213) bytes,
// contract creation fails with an out of gas error.
if (
m_evmVersion >= langutil::EVMVersion::spuriousDragon() &&
compiledContract.runtimeObject.bytecode.size() > 0x6000
)
m_errorReporter.warning(
5574_error,
_contract.location(),
"Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). "
"This contract may not be deployable on mainnet. "
"Consider enabling the optimizer (with a low \"runs\" value!), "
"turning off revert strings, or using libraries."
);
_otherCompilers[compiledContract.contract] = compiler;
assemble(_contract, compiler->assemblyPtr(), compiler->runtimeAssemblyPtr());
}
void CompilerStack::generateIR(ContractDefinition const& _contract)
@ -1361,30 +1376,11 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
//cout << yul::AsmPrinter{}(*stack.parserResult()->code) << endl;
// TODO: support passing metadata
// TODO: use stack.assemble here!
yul::MachineAssemblyObject init;
yul::MachineAssemblyObject runtime;
std::tie(init, runtime) = stack.assembleWithDeployed(IRNames::deployedObject(_contract));
compiledContract.object = std::move(*init.bytecode);
compiledContract.runtimeObject = std::move(*runtime.bytecode);
// TODO: refactor assemblyItems, runtimeAssemblyItems, generatedSources,
// assemblyString, assemblyJSON, and functionEntryPoints to work with this code path
// Throw a warning if EIP-170 limits are exceeded:
// If contract creation returns data with length greater than 0x6000 (214 + 213) bytes,
// contract creation fails with an out of gas error.
if (
m_evmVersion >= langutil::EVMVersion::spuriousDragon() &&
compiledContract.runtimeObject.bytecode.size() > 0x6000
)
m_errorReporter.warning(
9609_error,
_contract.location(),
"Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). "
"This contract may not be deployable on mainnet. "
"Consider enabling the optimizer (with a low \"runs\" value!), "
"turning off revert strings, or using libraries."
);
string deployedName = IRNames::deployedObject(_contract);
solAssert(!deployedName.empty(), "");
tie(compiledContract.evmAssembly, compiledContract.evmRuntimeAssembly) = stack.assembleEVMWithDeployed(deployedName);
assemble(_contract, compiledContract.evmAssembly, compiledContract.evmRuntimeAssembly);
}
void CompilerStack::generateEwasm(ContractDefinition const& _contract)

View File

@ -403,6 +403,14 @@ private:
/// @returns true if the contract is requested to be compiled.
bool isRequestedContract(ContractDefinition const& _contract) const;
/// Assembles the contract.
/// This function should only be internally called by compileContract and generateEVMFromIR.
void assemble(
ContractDefinition const& _contract,
std::shared_ptr<evmasm::Assembly> _assembly,
std::shared_ptr<evmasm::Assembly> _runtimeAssembly
);
/// Compile a single contract.
/// @param _otherCompilers provides access to compilers of other contracts, to get
/// their bytecode if needed. Only filled after they have been compiled.

View File

@ -216,7 +216,41 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
return MachineAssemblyObject();
}
std::pair<MachineAssemblyObject, MachineAssemblyObject> AssemblyStack::assembleWithDeployed(optional<string_view> _deployName) const
std::pair<MachineAssemblyObject, MachineAssemblyObject>
AssemblyStack::assembleWithDeployed(optional<string_view> _deployName) const
{
auto [creationAssembly, deployedAssembly] = assembleEVMWithDeployed(_deployName);
yulAssert(creationAssembly, "");
MachineAssemblyObject creationObject;
creationObject.bytecode = make_shared<evmasm::LinkerObject>(creationAssembly->assemble());
yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables.");
creationObject.assembly = creationAssembly->assemblyString();
creationObject.sourceMappings = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping(
creationAssembly->items(),
{{scanner().charStream() ? scanner().charStream()->name() : "", 0}}
)
);
MachineAssemblyObject deployedObject;
if (deployedAssembly)
{
deployedObject.bytecode = make_shared<evmasm::LinkerObject>(deployedAssembly->assemble());
deployedObject.assembly = deployedAssembly->assemblyString();
deployedObject.sourceMappings = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping(
deployedAssembly->items(),
{{scanner().charStream() ? scanner().charStream()->name() : "", 0}}
)
);
}
return {std::move(creationObject), std::move(deployedObject)};
}
std::pair<std::shared_ptr<evmasm::Assembly>, std::shared_ptr<evmasm::Assembly>>
AssemblyStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
{
yulAssert(m_analysisSuccessful, "");
yulAssert(m_parserResult, "");
@ -227,18 +261,6 @@ std::pair<MachineAssemblyObject, MachineAssemblyObject> AssemblyStack::assembleW
EthAssemblyAdapter adapter(assembly);
compileEVM(adapter, m_optimiserSettings.optimizeStackAllocation);
MachineAssemblyObject creationObject;
creationObject.bytecode = make_shared<evmasm::LinkerObject>(assembly.assemble());
yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables.");
creationObject.assembly = assembly.assemblyString();
creationObject.sourceMappings = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping(
assembly.items(),
{{scanner().charStream() ? scanner().charStream()->name() : "", 0}}
)
);
MachineAssemblyObject deployedObject;
optional<size_t> subIndex;
// Pick matching assembly if name was given
@ -260,17 +282,10 @@ std::pair<MachineAssemblyObject, MachineAssemblyObject> AssemblyStack::assembleW
if (subIndex.has_value())
{
evmasm::Assembly& runtimeAssembly = assembly.sub(*subIndex);
deployedObject.bytecode = make_shared<evmasm::LinkerObject>(runtimeAssembly.assemble());
deployedObject.assembly = runtimeAssembly.assemblyString();
deployedObject.sourceMappings = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping(
runtimeAssembly.items(),
{{scanner().charStream() ? scanner().charStream()->name() : "", 0}}
)
);
return {make_shared<evmasm::Assembly>(assembly), make_shared<evmasm::Assembly>(runtimeAssembly)};
}
return {std::move(creationObject), std::move(deployedObject)};
return {make_shared<evmasm::Assembly>(assembly), {}};
}
string AssemblyStack::print() const

View File

@ -35,6 +35,11 @@
#include <memory>
#include <string>
namespace solidity::evmasm
{
class Assembly;
}
namespace solidity::langutil
{
class Scanner;
@ -89,17 +94,22 @@ public:
/// Run the assembly step (should only be called after parseAndAnalyze).
MachineAssemblyObject assemble(Machine _machine) const;
/// Run the assembly step (should only be called after parseAndAnalyze).
/// In addition to the value returned by @a assemble, returns
/// a second object that is guessed to be the runtime code.
/// Only available for EVM.
std::pair<MachineAssemblyObject, MachineAssemblyObject> assembleAndGuessRuntime() const;
/// Run the assembly step (should only be called after parseAndAnalyze).
/// In addition to the value returned by @a assemble, returns
/// a second object that is the runtime code.
/// Only available for EVM.
std::pair<MachineAssemblyObject, MachineAssemblyObject> assembleWithDeployed(std::optional<std::string_view> _deployeName = {}) const;
std::pair<MachineAssemblyObject, MachineAssemblyObject>
assembleWithDeployed(
std::optional<std::string_view> _deployName = {}
) const;
/// Run the assembly step (should only be called after parseAndAnalyze).
/// Similar to @a assemblyWithDeployed, but returns EVM assembly objects.
/// Only available for EVM.
std::pair<std::shared_ptr<evmasm::Assembly>, std::shared_ptr<evmasm::Assembly>>
assembleEVMWithDeployed(
std::optional<std::string_view> _deployName = {}
) const;
/// @returns the errors generated during parsing, analysis (and potentially assembly).
langutil::ErrorList const& errors() const { return m_errors; }