Code generation for access to contract code.

This commit is contained in:
chriseth 2019-01-10 16:44:31 +01:00
parent 2fcfb216b5
commit e6fee257e6
14 changed files with 101 additions and 45 deletions

View File

@ -3455,3 +3455,10 @@ string MagicType::toString(bool _short) const
solAssert(false, "Unknown kind of magic."); solAssert(false, "Unknown kind of magic.");
return {}; return {};
} }
TypePointer MagicType::typeArgument() const
{
solAssert(m_kind == Kind::MetaType, "");
solAssert(m_typeArgument, "");
return m_typeArgument;
}

View File

@ -1335,6 +1335,8 @@ public:
Kind kind() const { return m_kind; } Kind kind() const { return m_kind; }
TypePointer typeArgument() const;
private: private:
Kind m_kind; Kind m_kind;
/// Contract type used for contract metadata magic. /// Contract type used for contract metadata magic.

View File

@ -31,18 +31,18 @@ using namespace dev::solidity;
void Compiler::compileContract( void Compiler::compileContract(
ContractDefinition const& _contract, ContractDefinition const& _contract,
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts, std::map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers,
bytes const& _metadata bytes const& _metadata
) )
{ {
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns); ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns);
runtimeCompiler.compileContract(_contract, _contracts); runtimeCompiler.compileContract(_contract, _otherCompilers);
m_runtimeContext.appendAuxiliaryData(_metadata); m_runtimeContext.appendAuxiliaryData(_metadata);
// This might modify m_runtimeContext because it can access runtime functions at // This might modify m_runtimeContext because it can access runtime functions at
// creation time. // creation time.
ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize, 1); ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize, 1);
m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
m_context.optimise(m_optimize, m_optimizeRuns); m_context.optimise(m_optimize, m_optimizeRuns);
} }

View File

@ -45,11 +45,13 @@ public:
/// @arg _metadata contains the to be injected metadata CBOR /// @arg _metadata contains the to be injected metadata CBOR
void compileContract( void compileContract(
ContractDefinition const& _contract, ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts, std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers,
bytes const& _metadata bytes const& _metadata
); );
/// @returns Entire assembly. /// @returns Entire assembly.
eth::Assembly const& assembly() const { return m_context.assembly(); } eth::Assembly const& assembly() const { return m_context.assembly(); }
/// @returns Runtime assembly.
eth::Assembly const& runtimeAssembly() const { return m_context.assembly().sub(m_runtimeSub); }
/// @returns The entire assembled object (with constructor). /// @returns The entire assembled object (with constructor).
eth::LinkerObject assembledObject() const { return m_context.assembledObject(); } eth::LinkerObject assembledObject() const { return m_context.assembledObject(); }
/// @returns Only the runtime object (without constructor). /// @returns Only the runtime object (without constructor).

View File

@ -167,11 +167,18 @@ unsigned CompilerContext::numberOfLocalVariables() const
return m_localVariables.size(); return m_localVariables.size();
} }
eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const eth::Assembly const& CompilerContext::compiledContract(ContractDefinition const& _contract) const
{ {
auto ret = m_compiledContracts.find(&_contract); auto ret = m_otherCompilers.find(&_contract);
solAssert(ret != m_compiledContracts.end(), "Compiled contract not found."); solAssert(ret != m_otherCompilers.end(), "Compiled contract not found.");
return *ret->second; return ret->second->assembly();
}
eth::Assembly const& CompilerContext::compiledContractRuntime(ContractDefinition const& _contract) const
{
auto ret = m_otherCompilers.find(&_contract);
solAssert(ret != m_otherCompilers.end(), "Compiled contract not found.");
return ret->second->runtimeAssembly();
} }
bool CompilerContext::isLocalVariable(Declaration const* _declaration) const bool CompilerContext::isLocalVariable(Declaration const* _declaration) const

View File

@ -41,6 +41,7 @@
namespace dev { namespace dev {
namespace solidity { namespace solidity {
class Compiler;
/** /**
* Context to be shared by all units that compile the same contract. * Context to be shared by all units that compile the same contract.
@ -74,8 +75,9 @@ public:
/// Returns the number of currently allocated local variables. /// Returns the number of currently allocated local variables.
unsigned numberOfLocalVariables() const; unsigned numberOfLocalVariables() const;
void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; } void setOtherCompilers(std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers) { m_otherCompilers = _otherCompilers; }
eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; eth::Assembly const& compiledContract(ContractDefinition const& _contract) const;
eth::Assembly const& compiledContractRuntime(ContractDefinition const& _contract) const;
void setStackOffset(int _offset) { m_asm->setDeposit(_offset); } void setStackOffset(int _offset) { m_asm->setDeposit(_offset); }
void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); }
@ -307,7 +309,7 @@ private:
/// Activated experimental features. /// Activated experimental features.
std::set<ExperimentalFeature> m_experimentalFeatures; std::set<ExperimentalFeature> m_experimentalFeatures;
/// Other already compiled contracts to be used in contract creation calls. /// Other already compiled contracts to be used in contract creation calls.
std::map<ContractDefinition const*, eth::Assembly const*> m_compiledContracts; std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers;
/// Storage offsets of state variables /// Storage offsets of state variables
std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables; std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables;
/// Offsets of local variables on the stack (relative to stack base). /// Offsets of local variables on the stack (relative to stack base).

View File

@ -1199,6 +1199,29 @@ void CompilerUtils::computeHashStatic()
m_context << u256(32) << u256(0) << Instruction::KECCAK256; m_context << u256(32) << u256(0) << Instruction::KECCAK256;
} }
void CompilerUtils::copyContractCodeToMemory(ContractDefinition const& contract, bool _creation)
{
string which = _creation ? "Creation" : "Runtime";
m_context.callLowLevelFunction(
"$copyContract" + which + "CodeToMemory_" + contract.type()->identifier(),
1,
1,
[&contract, _creation](CompilerContext& _context)
{
// copy the contract's code into memory
eth::Assembly const& assembly =
_creation ?
_context.compiledContract(contract) :
_context.compiledContractRuntime(contract);
// pushes size
auto subroutine = _context.addSubroutine(make_shared<eth::Assembly>(assembly));
_context << Instruction::DUP1 << subroutine;
_context << Instruction::DUP4 << Instruction::CODECOPY;
_context << Instruction::ADD;
}
);
}
void CompilerUtils::storeStringData(bytesConstRef _data) void CompilerUtils::storeStringData(bytesConstRef _data)
{ {
//@todo provide both alternatives to the optimiser //@todo provide both alternatives to the optimiser

View File

@ -263,6 +263,13 @@ public:
/// Appends code that computes the Keccak-256 hash of the topmost stack element of 32 byte type. /// Appends code that computes the Keccak-256 hash of the topmost stack element of 32 byte type.
void computeHashStatic(); void computeHashStatic();
/// Apppends code that copies the code of the given contract to memory.
/// Stack pre: Memory position
/// Stack post: Updated memory position
/// @param creation if true, copies creation code, if false copies runtime code.
/// @note the contract has to be compiled already, so beware of cyclic dependencies!
void copyContractCodeToMemory(ContractDefinition const& contract, bool _creationCode);
/// Bytes we need to the start of call data. /// Bytes we need to the start of call data.
/// - The size in bytes of the function (hash) identifier. /// - The size in bytes of the function (hash) identifier.
static const unsigned dataStartOffset; static const unsigned dataStartOffset;

View File

@ -60,7 +60,7 @@ private:
void ContractCompiler::compileContract( void ContractCompiler::compileContract(
ContractDefinition const& _contract, ContractDefinition const& _contract,
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers
) )
{ {
CompilerContext::LocationSetter locationSetter(m_context, _contract); CompilerContext::LocationSetter locationSetter(m_context, _contract);
@ -70,7 +70,7 @@ void ContractCompiler::compileContract(
// This has to be the first code in the contract. // This has to be the first code in the contract.
appendDelegatecallCheck(); appendDelegatecallCheck();
initializeContext(_contract, _contracts); initializeContext(_contract, _otherCompilers);
// This generates the dispatch function for externally visible functions // This generates the dispatch function for externally visible functions
// and adds the function to the compilation queue. Additionally internal functions, // and adds the function to the compilation queue. Additionally internal functions,
// which are referenced directly or indirectly will be added. // which are referenced directly or indirectly will be added.
@ -81,7 +81,7 @@ void ContractCompiler::compileContract(
size_t ContractCompiler::compileConstructor( size_t ContractCompiler::compileConstructor(
ContractDefinition const& _contract, ContractDefinition const& _contract,
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts std::map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers
) )
{ {
CompilerContext::LocationSetter locationSetter(m_context, _contract); CompilerContext::LocationSetter locationSetter(m_context, _contract);
@ -89,18 +89,18 @@ size_t ContractCompiler::compileConstructor(
return deployLibrary(_contract); return deployLibrary(_contract);
else else
{ {
initializeContext(_contract, _contracts); initializeContext(_contract, _otherCompilers);
return packIntoContractCreator(_contract); return packIntoContractCreator(_contract);
} }
} }
void ContractCompiler::initializeContext( void ContractCompiler::initializeContext(
ContractDefinition const& _contract, ContractDefinition const& _contract,
map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers
) )
{ {
m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures); m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures);
m_context.setCompiledContracts(_compiledContracts); m_context.setOtherCompilers(_otherCompilers);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
CompilerUtils(m_context).initialiseFreeMemoryPointer(); CompilerUtils(m_context).initialiseFreeMemoryPointer();
registerStateVariables(_contract); registerStateVariables(_contract);

View File

@ -49,13 +49,13 @@ public:
void compileContract( void compileContract(
ContractDefinition const& _contract, ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers
); );
/// Compiles the constructor part of the contract. /// Compiles the constructor part of the contract.
/// @returns the identifier of the runtime sub-assembly. /// @returns the identifier of the runtime sub-assembly.
size_t compileConstructor( size_t compileConstructor(
ContractDefinition const& _contract, ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers
); );
private: private:
@ -63,7 +63,7 @@ private:
/// information about the contract like the AST annotations. /// information about the contract like the AST annotations.
void initializeContext( void initializeContext(
ContractDefinition const& _contract, ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers
); );
/// Adds the code that is run at creation time. Should be run after exchanging the run-time context /// Adds the code that is run at creation time. Should be run after exchanging the run-time context
/// with a new and initialized context. Adds the constructor code. /// with a new and initialized context. Adds the constructor code.

View File

@ -594,22 +594,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
} }
ContractDefinition const* contract = ContractDefinition const* contract =
&dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition(); &dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
m_context.callLowLevelFunction( utils().fetchFreeMemoryPointer();
"$copyContractCreationCodeToMemory_" + contract->type()->identifier(), utils().copyContractCodeToMemory(*contract, true);
0,
1,
[contract](CompilerContext& _context)
{
// copy the contract's code into memory
eth::Assembly const& assembly = _context.compiledContract(*contract);
CompilerUtils(_context).fetchFreeMemoryPointer();
// pushes size
auto subroutine = _context.addSubroutine(make_shared<eth::Assembly>(assembly));
_context << Instruction::DUP1 << subroutine;
_context << Instruction::DUP4 << Instruction::CODECOPY;
_context << Instruction::ADD;
}
);
utils().abiEncode(argumentTypes, function.parameterTypes()); utils().abiEncode(argumentTypes, function.parameterTypes());
// now on stack: memory_end_ptr // now on stack: memory_end_ptr
// need: size, offset, endowment // need: size, offset, endowment
@ -1351,6 +1337,23 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(false, "Gas has been removed."); solAssert(false, "Gas has been removed.");
else if (member == "blockhash") else if (member == "blockhash")
solAssert(false, "Blockhash has been removed."); solAssert(false, "Blockhash has been removed.");
else if (member == "creationCode" || member == "runtimeCode")
{
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
utils().fetchFreeMemoryPointer();
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
utils().copyContractCodeToMemory(contract, member == "creationCode");
// Stack: start end
m_context.appendInlineAssembly(
Whiskers(R"({
mstore(start, sub(end, add(start, 0x20)))
mstore(<free>, end)
})")("free", to_string(CompilerUtils::freeMemoryPointer)).render(),
{"start", "end"}
);
m_context << Instruction::POP;
}
else else
solAssert(false, "Unknown magic member."); solAssert(false, "Unknown magic member.");
break; break;

View File

@ -343,12 +343,12 @@ bool CompilerStack::compile()
return false; return false;
// Only compile contracts individually which have been requested. // Only compile contracts individually which have been requested.
map<ContractDefinition const*, eth::Assembly const*> compiledContracts; map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers;
for (Source const* source: m_sourceOrder) for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes()) for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
if (isRequestedContract(*contract)) if (isRequestedContract(*contract))
compileContract(*contract, compiledContracts); compileContract(*contract, otherCompilers);
m_stackState = CompilationSuccessful; m_stackState = CompilationSuccessful;
this->link(); this->link();
return true; return true;
@ -795,19 +795,19 @@ bool onlySafeExperimentalFeaturesActivated(set<ExperimentalFeature> const& featu
void CompilerStack::compileContract( void CompilerStack::compileContract(
ContractDefinition const& _contract, ContractDefinition const& _contract,
map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts map<ContractDefinition const*, shared_ptr<Compiler const>>& _otherCompilers
) )
{ {
solAssert(m_stackState >= AnalysisSuccessful, ""); solAssert(m_stackState >= AnalysisSuccessful, "");
if ( if (
_compiledContracts.count(&_contract) || _otherCompilers.count(&_contract) ||
!_contract.annotation().unimplementedFunctions.empty() || !_contract.annotation().unimplementedFunctions.empty() ||
!_contract.constructorIsPublic() !_contract.constructorIsPublic()
) )
return; return;
for (auto const* dependency: _contract.annotation().contractDependencies) for (auto const* dependency: _contract.annotation().contractDependencies)
compileContract(*dependency, _compiledContracts); compileContract(*dependency, _otherCompilers);
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
@ -825,7 +825,7 @@ void CompilerStack::compileContract(
try try
{ {
// Run optimiser and compile the contract. // Run optimiser and compile the contract.
compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); compiler->compileContract(_contract, _otherCompilers, cborEncodedMetadata);
} }
catch(eth::OptimizerException const&) catch(eth::OptimizerException const&)
{ {
@ -852,7 +852,7 @@ void CompilerStack::compileContract(
solAssert(false, "Assembly exception for deployed bytecode"); solAssert(false, "Assembly exception for deployed bytecode");
} }
_compiledContracts[compiledContract.contract] = &compiler->assembly(); _otherCompilers[compiledContract.contract] = compiler;
} }
CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const

View File

@ -293,10 +293,12 @@ private:
/// @returns true if the contract is requested to be compiled. /// @returns true if the contract is requested to be compiled.
bool isRequestedContract(ContractDefinition const& _contract) const; bool isRequestedContract(ContractDefinition const& _contract) const;
/// Compile a single contract and put the result in @a _compiledContracts. /// 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.
void compileContract( void compileContract(
ContractDefinition const& _contract, ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts std::map<ContractDefinition const*, std::shared_ptr<Compiler const>>& _otherCompilers
); );
/// Links all the known library addresses in the available objects. Any unknown /// Links all the known library addresses in the available objects. Any unknown

View File

@ -46,6 +46,7 @@ namespace dev
{ {
namespace solidity namespace solidity
{ {
class Contract;
namespace test namespace test
{ {
@ -84,7 +85,7 @@ eth::AssemblyItems compileContract(std::shared_ptr<CharStream> _sourceCode)
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{ {
Compiler compiler(dev::test::Options::get().evmVersion()); Compiler compiler(dev::test::Options::get().evmVersion());
compiler.compileContract(*contract, map<ContractDefinition const*, Assembly const*>{}, bytes()); compiler.compileContract(*contract, map<ContractDefinition const*, shared_ptr<Compiler const>>{}, bytes());
return compiler.runtimeAssemblyItems(); return compiler.runtimeAssemblyItems();
} }