From e543bd34c0b4884b5a27555f698f50af6a1c0b81 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 10 Nov 2016 18:16:21 +0100 Subject: Stored combined creation and runtime tags. Includes a change to Assembly to allow tags from sub-assemblies to be used. Sorry, this get a bit bigger than I thought. --- libsolidity/codegen/Compiler.cpp | 11 +++++--- libsolidity/codegen/Compiler.h | 8 +++--- libsolidity/codegen/CompilerContext.cpp | 16 +++++------ libsolidity/codegen/CompilerContext.h | 45 ++++++++++++++++-------------- libsolidity/codegen/CompilerUtils.cpp | 13 +++++++++ libsolidity/codegen/CompilerUtils.h | 4 +++ libsolidity/codegen/ContractCompiler.cpp | 43 ++++++++++++++++++++-------- libsolidity/codegen/ContractCompiler.h | 10 ++++--- libsolidity/codegen/ExpressionCompiler.cpp | 31 +++++++++++++++----- 9 files changed, 122 insertions(+), 59 deletions(-) (limited to 'libsolidity/codegen') diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index bb8211ad..eefa50c5 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -33,11 +33,13 @@ void Compiler::compileContract( std::map const& _contracts ) { - ContractCompiler runtimeCompiler(CompilationMode::Runtime, nullptr, m_runtimeContext, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); runtimeCompiler.compileContract(_contract, _contracts); - ContractCompiler creationCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); - m_runtimeSub = creationCompiler.compileConstructor(m_runtimeContext, _contract, _contracts); + // This might modify m_runtimeContext because it can access runtime functions at + // creation time. + ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize); + m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); if (m_optimize) m_context.optimise(m_optimizeRuns); @@ -54,7 +56,8 @@ void Compiler::compileClone( map const& _contracts ) { - ContractCompiler cloneCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); + ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize); m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts); if (m_optimize) diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 56849ea0..4a87de0e 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -36,8 +36,8 @@ public: explicit Compiler(bool _optimize = false, unsigned _runs = 200): m_optimize(_optimize), m_optimizeRuns(_runs), - m_context(CompilationMode::Creation, &m_runtimeContext), - m_runtimeContext(CompilationMode::Runtime) + m_runtimeContext(), + m_context(&m_runtimeContext) { } void compileContract( @@ -71,9 +71,9 @@ public: private: bool const m_optimize; unsigned const m_optimizeRuns; - CompilerContext m_context; - size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. CompilerContext m_runtimeContext; + size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. + CompilerContext m_context; }; } diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3ac5bd3c..f1d306ce 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -92,22 +92,22 @@ eth::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration const& return m_functionCompilationQueue.entryLabelIfExists(_declaration); } -eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel(FunctionDefinition const& _function) +FunctionDefinition const& CompilerContext::resolveVirtualFunction(FunctionDefinition const& _function) { // Libraries do not allow inheritance and their functions can be inlined, so we should not // search the inheritance hierarchy (which will be the wrong one in case the function // is inlined). if (auto scope = dynamic_cast(_function.scope())) if (scope->isLibrary()) - return functionEntryLabel(_function); + return _function; solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return virtualFunctionEntryLabel(_function, m_inheritanceHierarchy.begin()); + return resolveVirtualFunction(_function, m_inheritanceHierarchy.begin()); } -eth::AssemblyItem CompilerContext::superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base) +FunctionDefinition const& CompilerContext::superFunction(FunctionDefinition const& _function, ContractDefinition const& _base) { solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return virtualFunctionEntryLabel(_function, superContract(_base)); + return resolveVirtualFunction(_function, superContract(_base)); } FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const @@ -227,7 +227,7 @@ void CompilerContext::injectVersionStampIntoSub(size_t _subIndex) sub.injectStart(fromBigEndian(binaryVersion())); } -eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( +FunctionDefinition const& CompilerContext::resolveVirtualFunction( FunctionDefinition const& _function, vector::const_iterator _searchStart ) @@ -242,9 +242,9 @@ eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( !function->isConstructor() && FunctionType(*function).hasEqualArgumentTypes(functionType) ) - return functionEntryLabel(*function); + return *function; solAssert(false, "Super function " + name + " not found."); - return m_asm.newTag(); // not reached + return _function; // not reached } vector::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 8b95c9f5..c4724ee0 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -37,10 +37,6 @@ namespace dev { namespace solidity { -/// Depending on the compilation is on the runtime code or the creation code, -/// the interpretation of internal function values differ. -enum class CompilationMode { Runtime, Creation }; - /** * Context to be shared by all units that compile the same contract. * It stores the generated bytecode and the position of identifiers in memory and on the stack. @@ -48,15 +44,13 @@ enum class CompilationMode { Runtime, Creation }; class CompilerContext { public: - CompilerContext(CompilationMode _mode, CompilerContext* _runtimeContext = nullptr) : - m_mode(_mode), m_runtimeContext(_runtimeContext) + CompilerContext(CompilerContext* _runtimeContext = nullptr) : + m_runtimeContext(_runtimeContext) { - solAssert(m_mode != CompilationMode::Runtime || !m_runtimeContext, "runtime but another runtime context provided"); - solAssert(m_mode != CompilationMode::Creation || m_runtimeContext, "creation but no runtime context provided"); + if (m_runtimeContext) + m_runtimeSub = registerSubroutine(m_runtimeContext->assembly()); } - bool isCreationPhase() const { return m_mode == CompilationMode::Creation; } - void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); @@ -80,10 +74,10 @@ public: eth::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const; void setInheritanceHierarchy(std::vector const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } /// @returns the entry label of the given function and takes overrides into account. - eth::AssemblyItem virtualFunctionEntryLabel(FunctionDefinition const& _function); - /// @returns the entry label of a function that overrides the given declaration from the most derived class just + FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function); + /// @returns the function that overrides the given declaration from the most derived class just /// above _base in the current inheritance hierarchy. - eth::AssemblyItem superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base); + FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base); FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const; /// @returns the next function in the queue of functions that are still to be compiled @@ -123,11 +117,15 @@ public: eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } /// @returns a new tag without pushing any opcodes or data eth::AssemblyItem newTag() { return m_asm.newTag(); } + /// Adds a subroutine to the code (in the data section) + /// @returns the assembly item corresponding to the pushed subroutine, i.e. its offset in the list. + size_t registerSubroutine(eth::Assembly const& _assembly) { return size_t(m_asm.newSub(_assembly).data()); } /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) - /// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset. - eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); } + /// on the stack. @returns the pushsub assembly item. + eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { auto sub = m_asm.newSub(_assembly); m_asm.append(m_asm.newPushSubSize(size_t(sub.data()))); return sub; } + void appendSubroutineSize(size_t const& _subRoutine) { m_asm.append(m_asm.newPushSubSize(_subRoutine)); } /// Pushes the size of the final program - void appendProgramSize() { return m_asm.appendProgramSize(); } + void appendProgramSize() { m_asm.appendProgramSize(); } /// Adds data to the data section, pushes a reference to the stack eth::AssemblyItem appendData(bytes const& _data) { return m_asm.append(_data); } /// Appends the address (virtual, will be filled in by linker) of a library. @@ -159,6 +157,11 @@ public: void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } + /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. + CompilerContext* runtimeContext() { return m_runtimeContext; } + /// @returns the identifier of the runtime subroutine. + size_t runtimeSub() const { return m_runtimeSub; } + eth::Assembly const& assembly() const { return m_asm; } /// @returns non-const reference to the underlying assembly. Should be avoided in favour of /// wrappers in this class. @@ -185,9 +188,9 @@ public: }; private: - /// @returns the entry label of the given function - searches the inheritance hierarchy - /// startig from the given point towards the base. - eth::AssemblyItem virtualFunctionEntryLabel( + /// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns + /// the first function definition that is overwritten by _function. + FunctionDefinition const& resolveVirtualFunction( FunctionDefinition const& _function, std::vector::const_iterator _searchStart ); @@ -241,10 +244,10 @@ private: std::vector m_inheritanceHierarchy; /// Stack of current visited AST nodes, used for location attachment std::stack m_visitedNodes; - /// The current mode of the compilation - CompilationMode m_mode; /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. CompilerContext *m_runtimeContext; + /// The index of the runtime subroutine. + size_t m_runtimeSub = -1; }; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index dedb53e7..1e21c020 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -340,6 +340,19 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned) m_context << Instruction::OR; } +void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) +{ + m_context << m_context.functionEntryLabel(_function).pushTag(); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) + m_context << + (u256(1) << 32) << + Instruction::MUL << + rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) << + Instruction::OR; +} + void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) { // For a type extension, we need to remove all higher-order bits that we might have ignored in diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 690452f9..2ebec81a 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -120,6 +120,10 @@ public: void splitExternalFunctionType(bool _rightAligned); /// Performs the opposite operation of splitExternalFunctionType(_rightAligned) void combineExternalFunctionType(bool _rightAligned); + /// Appends code that combines the construction-time (if available) and runtime function + /// entry label of the given function into a single stack slot. + /// Note: This might cause the compilation queue of the runtime context to be extended. + void pushCombinedFunctionEntryLabel(Declaration const& _function); /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 9cd893e8..79987af6 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -60,14 +60,13 @@ void ContractCompiler::compileContract( } size_t ContractCompiler::compileConstructor( - CompilerContext const& _runtimeContext, ContractDefinition const& _contract, std::map const& _contracts ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); initializeContext(_contract, _contracts); - return packIntoContractCreator(_contract, _runtimeContext); + return packIntoContractCreator(_contract); } size_t ContractCompiler::compileClone( @@ -141,21 +140,31 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c appendBaseConstructor(*c); } -size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) +size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract) { + solAssert(!!m_runtimeCompiler, ""); + appendInitAndConstructorCode(_contract); - eth::AssemblyItem runtimeSub = m_context.addSubroutine(_runtimeContext.assembly()); + // We jump to the deploy routine because we first have to append all missing functions, + // which can cause further functions to be added to the runtime context. + eth::AssemblyItem deployRoutine = m_context.appendJumpToNew(); + + // We have to include copies of functions in the construction time and runtime context + // because of absolute jumps. + appendMissingFunctions(); + m_runtimeCompiler->appendMissingFunctions(); + + m_context << deployRoutine; + + solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); + m_context.appendSubroutineSize(m_context.runtimeSub()); // stack contains sub size - m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY; + m_context << Instruction::DUP1 << m_context.runtimeSub() << u256(0) << Instruction::CODECOPY; m_context << u256(0) << Instruction::RETURN; - // note that we have to include the functions again because of absolute jump labels - appendMissingFunctions(); - - solAssert(runtimeSub.data() < numeric_limits::max(), ""); - return size_t(runtimeSub.data()); + return m_context.runtimeSub(); } void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor) @@ -516,7 +525,19 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { solAssert(!!decl->type(), "Type of declaration required but not yet determined."); if (FunctionDefinition const* functionDef = dynamic_cast(decl)) - _assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag()); + { + functionDef = &m_context.resolveVirtualFunction(*functionDef); + _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) + { + _assembly.append(u256(1) << 32); + _assembly.append(Instruction::MUL); + _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); + _assembly.append(Instruction::OR); + } + } else if (auto variable = dynamic_cast(decl)) { solAssert(!variable->isConstant(), ""); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index fecf6f5a..9e24523c 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -38,11 +38,12 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(CompilationMode _mode, CompilerContext* _runtimeContext, CompilerContext& _context, bool _optimise): + explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise): m_optimise(_optimise), + m_runtimeCompiler(_runtimeCompiler), m_context(_context) { - m_context = CompilerContext(_mode, _runtimeContext); + m_context = CompilerContext(_runtimeCompiler ? &_runtimeCompiler->m_context : nullptr); } void compileContract( @@ -52,7 +53,6 @@ public: /// Compiles the constructor part of the contract. /// @returns the identifier of the runtime sub-assembly. size_t compileConstructor( - CompilerContext const& _runtimeContext, ContractDefinition const& _contract, std::map const& _contracts ); @@ -74,7 +74,7 @@ private: /// 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. /// @returns the identifier of the runtime sub assembly - size_t packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); + size_t packIntoContractCreator(ContractDefinition const& _contract); /// Appends state variable initialisation and constructor code. void appendInitAndConstructorCode(ContractDefinition const& _contract); void appendBaseConstructor(FunctionDefinition const& _constructor); @@ -117,6 +117,8 @@ private: static eth::Assembly cloneRuntime(); bool const m_optimise; + /// Pointer to the runtime compiler in case this is a creation compiler. + ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; std::vector m_breakTags; ///< tag to jump to for a "break" statement std::vector m_continueTags; ///< tag to jump to for a "continue" statement diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index e3f05c21..83c3a2c4 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -488,6 +488,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) parameterSize += function.selfType()->sizeOnStack(); } + if (m_context.runtimeContext()) + // We have a runtime context, so we need the creation part. + m_context << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + else + // Extract the runtime part. + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction); m_context << returnLabel; @@ -845,9 +852,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ); if (funType->location() == FunctionType::Location::Internal) { - m_context << m_context.functionEntryLabel( - dynamic_cast(funType->declaration()) - ).pushTag(); + FunctionDefinition const& funDef = dynamic_cast(funType->declaration()); + utils().pushCombinedFunctionEntryLabel(funDef); utils().moveIntoStack(funType->selfType()->sizeOnStack(), 1); } else @@ -883,7 +889,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) // us to link against it although we actually do not need it. auto const* function = dynamic_cast(_memberAccess.annotation().referencedDeclaration); solAssert(!!function, "Function not found in member access"); - m_context << m_context.functionEntryLabel(*function).pushTag(); + utils().pushCombinedFunctionEntryLabel(*function); } } else if (dynamic_cast(_memberAccess.annotation().type.get())) @@ -915,10 +921,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) if (type.isSuper()) { solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved."); - m_context << m_context.superFunctionEntryLabel( + utils().pushCombinedFunctionEntryLabel(m_context.superFunction( dynamic_cast(*_memberAccess.annotation().referencedDeclaration), type.contractDefinition() - ).pushTag(); + )); } else { @@ -1203,7 +1209,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) } } else if (FunctionDefinition const* functionDef = dynamic_cast(declaration)) - m_context << m_context.virtualFunctionEntryLabel(*functionDef).pushTag(); + utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef)); else if (auto variable = dynamic_cast(declaration)) appendVariable(*variable, static_cast(_identifier)); else if (auto contract = dynamic_cast(declaration)) @@ -1266,6 +1272,17 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type { if (_operator == Token::Equal || _operator == Token::NotEqual) { + if (FunctionType const* funType = dynamic_cast(&_type)) + { + if (funType->location() == FunctionType::Location::Internal) + { + // We have to remove the upper bits (construction time value) because they might + // be "unknown" in one of the operands and not in the other. + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context << Instruction::SWAP1; + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + } + } m_context << Instruction::EQ; if (_operator == Token::NotEqual) m_context << Instruction::ISZERO; -- cgit