diff options
author | chriseth <c@ethdev.com> | 2016-11-21 18:42:38 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2016-11-21 18:42:38 +0800 |
commit | b318366e6f16ed6a4274247d09badac4affff8d5 (patch) | |
tree | ef8dac7fe9285c1bc0b24ad042a3cda5565a661d /libsolidity/codegen | |
parent | 4633f3def897db0f91237f98cf46e5d84fb05e61 (diff) | |
parent | 5ebd31ce2d7447917740088eaa22c8c62c453a94 (diff) | |
download | dexon-solidity-b318366e6f16ed6a4274247d09badac4affff8d5.tar.gz dexon-solidity-b318366e6f16ed6a4274247d09badac4affff8d5.tar.zst dexon-solidity-b318366e6f16ed6a4274247d09badac4affff8d5.zip |
Merge remote-tracking branch 'origin/develop' into release
Diffstat (limited to 'libsolidity/codegen')
-rw-r--r-- | libsolidity/codegen/ArrayUtils.cpp | 2 | ||||
-rw-r--r-- | libsolidity/codegen/Compiler.cpp | 17 | ||||
-rw-r--r-- | libsolidity/codegen/Compiler.h | 8 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerContext.cpp | 30 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerContext.h | 84 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.cpp | 124 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.h | 11 | ||||
-rw-r--r-- | libsolidity/codegen/ContractCompiler.cpp | 101 | ||||
-rw-r--r-- | libsolidity/codegen/ContractCompiler.h | 13 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 50 | ||||
-rw-r--r-- | libsolidity/codegen/LValue.cpp | 52 |
11 files changed, 353 insertions, 139 deletions
diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index e7e8a98e..b5f5cd8e 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -270,7 +270,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const { - solAssert( + solUnimplementedAssert( !_sourceType.baseType()->isDynamicallySized(), "Nested dynamic arrays not implemented here." ); diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 1ae5dda9..54639515 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -33,14 +33,15 @@ void Compiler::compileContract( std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts ) { - ContractCompiler runtimeCompiler(m_runtimeContext, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); runtimeCompiler.compileContract(_contract, _contracts); - ContractCompiler creationCompiler(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); + m_context.optimise(m_optimize, m_optimizeRuns); if (_contract.isLibrary()) { @@ -54,11 +55,11 @@ void Compiler::compileClone( map<ContractDefinition const*, eth::Assembly const*> const& _contracts ) { - ContractCompiler cloneCompiler(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) - m_context.optimise(m_optimizeRuns); + m_context.optimise(m_optimize, m_optimizeRuns); } eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index fccb68a9..4a87de0e 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -35,7 +35,9 @@ class Compiler public: explicit Compiler(bool _optimize = false, unsigned _runs = 200): m_optimize(_optimize), - m_optimizeRuns(_runs) + m_optimizeRuns(_runs), + m_runtimeContext(), + m_context(&m_runtimeContext) { } void compileContract( @@ -69,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..c76db2a6 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -60,8 +60,8 @@ void CompilerContext::startFunction(Declaration const& _function) void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { - solAssert(m_asm.deposit() >= 0 && unsigned(m_asm.deposit()) >= _offsetToCurrent, ""); - m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent; + solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); + m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent; } void CompilerContext::removeVariable(VariableDeclaration const& _declaration) @@ -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<ContractDefinition const*>(_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 @@ -145,12 +145,12 @@ unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declarat unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const { - return m_asm.deposit() - _baseOffset - 1; + return m_asm->deposit() - _baseOffset - 1; } unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const { - return m_asm.deposit() - _offset - 1; + return m_asm->deposit() - _offset - 1; } pair<u256, unsigned> CompilerContext::storageLocationOfVariable(const Declaration& _declaration) const @@ -217,17 +217,17 @@ void CompilerContext::appendInlineAssembly( return true; }; - solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, m_asm, identifierAccess), ""); + solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), ""); } void CompilerContext::injectVersionStampIntoSub(size_t _subIndex) { - eth::Assembly& sub = m_asm.sub(_subIndex); + eth::Assembly& sub = m_asm->sub(_subIndex); sub.injectStart(Instruction::POP); sub.injectStart(fromBigEndian<u256>(binaryVersion())); } -eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( +FunctionDefinition const& CompilerContext::resolveVirtualFunction( FunctionDefinition const& _function, vector<ContractDefinition const*>::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<ContractDefinition const*>::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const @@ -257,7 +257,7 @@ vector<ContractDefinition const*>::const_iterator CompilerContext::superContract void CompilerContext::updateSourceLocation() { - m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); + m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); } eth::AssemblyItem CompilerContext::FunctionCompilationQueue::entryLabel( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 0c1500b0..8ccbddfd 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -44,6 +44,14 @@ namespace solidity { class CompilerContext { public: + CompilerContext(CompilerContext* _runtimeContext = nullptr): + m_asm(std::make_shared<eth::Assembly>()), + m_runtimeContext(_runtimeContext) + { + if (m_runtimeContext) + m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); + } + void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); @@ -52,9 +60,9 @@ public: void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; } eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; - void setStackOffset(int _offset) { m_asm.setDeposit(_offset); } - void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } - unsigned stackHeight() const { solAssert(m_asm.deposit() >= 0, ""); return unsigned(m_asm.deposit()); } + void setStackOffset(int _offset) { m_asm->setDeposit(_offset); } + void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } + unsigned stackHeight() const { solAssert(m_asm->deposit() >= 0, ""); return unsigned(m_asm->deposit()); } bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; } bool isLocalVariable(Declaration const* _declaration) const; @@ -67,10 +75,10 @@ public: eth::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const; void setInheritanceHierarchy(std::vector<ContractDefinition const*> 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 @@ -95,30 +103,33 @@ public: std::pair<u256, unsigned> storageLocationOfVariable(Declaration const& _declaration) const; /// Appends a JUMPI instruction to a new tag and @returns the tag - eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } + eth::AssemblyItem appendConditionalJump() { return m_asm->appendJumpI().tag(); } /// Appends a JUMPI instruction to @a _tag - CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; } + CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJumpI(_tag); return *this; } /// Appends a JUMP to a new tag and @returns the tag - eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); } + eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } /// Appends a JUMP to a tag already on the stack CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); /// Returns an "ErrorTag" - eth::AssemblyItem errorTag() { return m_asm.errorTag(); } + eth::AssemblyItem errorTag() { return m_asm->errorTag(); } /// Appends a JUMP to a specific tag - CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } + CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } /// Appends pushing of a new tag and @returns the new tag. - eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } + 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(); } + eth::AssemblyItem newTag() { return m_asm->newTag(); } /// 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::AssemblyPointer const& _assembly) { auto sub = m_asm->newSub(_assembly); m_asm->append(m_asm->newPushSubSize(size_t(sub.data()))); return sub; } + void pushSubroutineSize(size_t _subRoutine) { m_asm->append(m_asm->newPushSubSize(_subRoutine)); } + /// Pushes the offset of the subroutine. + void pushSubroutineOffset(size_t _subRoutine) { m_asm->append(eth::AssemblyItem(eth::PushSub, _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); } + eth::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); } /// Appends the address (virtual, will be filled in by linker) of a library. - void appendLibraryAddress(std::string const& _identifier) { m_asm.appendLibraryAddress(_identifier); } + void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); } /// Resets the stack of visited nodes with a new stack having only @c _node void resetVisitedNodes(ASTNode const* _node); /// Pops the stack of visited nodes @@ -127,10 +138,10 @@ public: void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); } /// Append elements to the current instruction list and adjust @a m_stackOffset. - CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } - CompilerContext& operator<<(Instruction _instruction) { m_asm.append(_instruction); return *this; } - CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } - CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } + CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm->append(_item); return *this; } + CompilerContext& operator<<(Instruction _instruction) { m_asm->append(_instruction); return *this; } + CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } + CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } /// Appends inline assembly. @a _replacements are string-matching replacements that are performed /// prior to parsing the inline assembly. @@ -144,22 +155,27 @@ public: /// Prepends "PUSH <compiler version number> POP" void injectVersionStampIntoSub(size_t _subIndex); - void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } + void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, 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; } + 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. - eth::Assembly& nonConstAssembly() { return m_asm; } + eth::Assembly& nonConstAssembly() { return *m_asm; } /// @arg _sourceCodes is the map of input files to source code strings /// @arg _inJsonFormat shows whether the out should be in Json format Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const { - return m_asm.stream(_stream, "", _sourceCodes, _inJsonFormat); + return m_asm->stream(_stream, "", _sourceCodes, _inJsonFormat); } - eth::LinkerObject const& assembledObject() { return m_asm.assemble(); } - eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm.sub(_subIndex).assemble(); } + eth::LinkerObject const& assembledObject() { return m_asm->assemble(); } + eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm->sub(_subIndex).assemble(); } /** * Helper class to pop the visited nodes stack when a scope closes @@ -172,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<ContractDefinition const*>::const_iterator _searchStart ); @@ -215,7 +231,7 @@ private: mutable std::queue<Declaration const*> m_functionsToCompile; } m_functionCompilationQueue; - eth::Assembly m_asm; + eth::AssemblyPointer m_asm; /// Magic global variables like msg, tx or this, distinguished by type. std::set<Declaration const*> m_magicGlobals; /// Other already compiled contracts to be used in contract creation calls. @@ -228,6 +244,10 @@ private: std::vector<ContractDefinition const*> m_inheritanceHierarchy; /// Stack of current visited AST nodes, used for location attachment std::stack<ASTNode const*> m_visitedNodes; + /// 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 e064c1a6..bfe5386b 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -133,12 +133,22 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound m_context << u256(str->value().size()); m_context << Instruction::ADD; } + else if ( + _type.category() == Type::Category::Function && + dynamic_cast<FunctionType const&>(_type).location() == FunctionType::Location::External + ) + { + solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); + combineExternalFunctionType(true); + m_context << Instruction::DUP2 << Instruction::MSTORE; + m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; + } else { unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); if (numBytes > 0) { - solAssert( + solUnimplementedAssert( _type.sizeOnStack() == 1, "Memory store of types with stack size != 1 not implemented." ); @@ -161,7 +171,7 @@ void CompilerUtils::encodeToMemory( solAssert(targetTypes.size() == _givenTypes.size(), ""); for (TypePointer& t: targetTypes) { - solAssert( + solUnimplementedAssert( t->mobileType() && t->mobileType()->interfaceType(_encodeAsLibraryTypes) && t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(), @@ -206,7 +216,8 @@ void CompilerUtils::encodeToMemory( else if ( _givenTypes[i]->dataStoredIn(DataLocation::Storage) || _givenTypes[i]->dataStoredIn(DataLocation::CallData) || - _givenTypes[i]->category() == Type::Category::StringLiteral + _givenTypes[i]->category() == Type::Category::StringLiteral || + _givenTypes[i]->category() == Type::Category::Function ) type = _givenTypes[i]; // delay conversion else @@ -304,6 +315,49 @@ void CompilerUtils::memoryCopy() m_context << Instruction::POP; // ignore return value } +void CompilerUtils::splitExternalFunctionType(bool _leftAligned) +{ + // We have to split the left-aligned <address><function identifier> into two stack slots: + // address (right aligned), function identifier (right aligned) + if (_leftAligned) + { + m_context << Instruction::DUP1 << (u256(1) << (64 + 32)) << Instruction::SWAP1 << Instruction::DIV; + // <input> <address> + m_context << Instruction::SWAP1 << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV; + } + else + { + m_context << Instruction::DUP1 << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; + } + m_context << u256(0xffffffffUL) << Instruction::AND; +} + +void CompilerUtils::combineExternalFunctionType(bool _leftAligned) +{ + // <address> <function_id> + m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1; + if (!_leftAligned) + m_context << ((u256(1) << 160) - 1) << Instruction::AND; + m_context << (u256(1) << 32) << Instruction::MUL; + m_context << Instruction::OR; + if (_leftAligned) + m_context << (u256(1) << 64) << Instruction::MUL; +} + +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 @@ -315,6 +369,8 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp Type::Category stackTypeCategory = _typeOnStack.category(); Type::Category targetTypeCategory = _targetType.category(); + bool enumOverflowCheckPending = (targetTypeCategory == Type::Category::Enum || stackTypeCategory == Type::Category::Enum); + switch (stackTypeCategory) { case Type::Category::FixedBytes: @@ -348,10 +404,18 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp } break; case Type::Category::Enum: - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); + solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, ""); + if (enumOverflowCheckPending) + { + EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack); + solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); + m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; + m_context.appendConditionalJumpTo(m_context.errorTag()); + enumOverflowCheckPending = false; + } break; case Type::Category::FixedPoint: - solAssert(false, "Not yet implemented - FixedPointType."); + solUnimplemented("Not yet implemented - FixedPointType."); case Type::Category::Integer: case Type::Category::Contract: case Type::Category::RationalNumber: @@ -372,6 +436,11 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp solAssert(_typeOnStack.mobileType(), ""); // just clean convertType(_typeOnStack, *_typeOnStack.mobileType(), true); + EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType); + solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); + m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; + m_context.appendConditionalJumpTo(m_context.errorTag()); + enumOverflowCheckPending = false; } else if (targetTypeCategory == Type::Category::FixedPoint) { @@ -386,7 +455,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack)) if (targetFixedPointType.integerBits() > typeOnStack->numBits()) cleanHigherOrderBits(*typeOnStack); - solAssert(false, "Not yet implemented - FixedPointType."); + solUnimplemented("Not yet implemented - FixedPointType."); } else { @@ -399,7 +468,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp RationalNumberType const& constType = dynamic_cast<RationalNumberType const&>(_typeOnStack); // We know that the stack is clean, we only have to clean for a narrowing conversion // where cleanup is forced. - solAssert(!constType.isFractional(), "Not yet implemented - FixedPointType."); + solUnimplementedAssert(!constType.isFractional(), "Not yet implemented - FixedPointType."); if (targetType.numBits() < constType.integerType()->numBits() && _cleanupNeeded) cleanHigherOrderBits(targetType); } @@ -651,15 +720,26 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp solAssert(_targetType == _typeOnStack, "Invalid conversion for bool."); if (_cleanupNeeded) m_context << Instruction::ISZERO << Instruction::ISZERO; + break; default: // All other types should not be convertible to non-equal types. solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); break; } + + solAssert(!enumOverflowCheckPending, "enum overflow checking missing."); } void CompilerUtils::pushZeroValue(Type const& _type) { + if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) + { + if (funType->location() == FunctionType::Location::Internal) + { + m_context << m_context.errorTag(); + return; + } + } auto const* referenceType = dynamic_cast<ReferenceType const*>(&_type); if (!referenceType || referenceType->location() == DataLocation::Storage) { @@ -804,21 +884,27 @@ void CompilerUtils::storeStringData(bytesConstRef _data) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries); - bool leftAligned = _type.category() == Type::Category::FixedBytes; + bool isExternalFunctionType = false; + if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) + if (funType->location() == FunctionType::Location::External) + isExternalFunctionType = true; if (numBytes == 0) + { m_context << Instruction::POP << u256(0); - else + return numBytes; + } + solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); + m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); + if (isExternalFunctionType) + splitExternalFunctionType(true); + else if (numBytes != 32) { - solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); - m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); - if (numBytes != 32) - { - // add leading or trailing zeros by dividing/multiplying depending on alignment - u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); - m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV; - if (leftAligned) - m_context << shiftFactor << Instruction::MUL; - } + bool leftAligned = _type.category() == Type::Category::FixedBytes; + // add leading or trailing zeros by dividing/multiplying depending on alignment + u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); + m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV; + if (leftAligned) + m_context << shiftFactor << Instruction::MUL; } return numBytes; diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index da74dc90..8e4033d6 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -114,6 +114,17 @@ public: /// Stack post: void memoryCopy(); + /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) + /// external function type <address><function identifier> into two stack slots: + /// address (right aligned), function identifier (right aligned) + 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 /// if a reference type is converted from calldata or storage to memory. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index ebb84784..8d60d6b3 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 ContractDefinition*, eth::Assembly const*> const& _contracts ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); initializeContext(_contract, _contracts); - return packIntoContractCreator(_contract, _runtimeContext); + return packIntoContractCreator(_contract); } size_t ContractCompiler::compileClone( @@ -80,7 +79,7 @@ size_t ContractCompiler::compileClone( appendInitAndConstructorCode(_contract); //@todo determine largest return size of all runtime functions - eth::AssemblyItem runtimeSub = m_context.addSubroutine(cloneRuntime()); + auto runtimeSub = m_context.addSubroutine(cloneRuntime()); // stack contains sub size m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY; @@ -88,7 +87,6 @@ size_t ContractCompiler::compileClone( appendMissingFunctions(); - solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), ""); return size_t(runtimeSub.data()); } @@ -104,6 +102,13 @@ void ContractCompiler::initializeContext( m_context.resetVisitedNodes(&_contract); } +void ContractCompiler::appendCallValueCheck() +{ + // Throw if function is not payable but call contained ether. + m_context << Instruction::CALLVALUE; + m_context.appendConditionalJumpTo(m_context.errorTag()); +} + void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) { // Determine the arguments that are used for the base constructors. @@ -139,23 +144,35 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c appendConstructor(*constructor); else if (auto c = m_context.nextConstructor(_contract)) appendBaseConstructor(*c); + else + appendCallValueCheck(); } -size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) +size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract) { - appendInitAndConstructorCode(_contract); + solAssert(!!m_runtimeCompiler, ""); - eth::AssemblyItem runtimeSub = m_context.addSubroutine(_runtimeContext.assembly()); + appendInitAndConstructorCode(_contract); - // stack contains sub size - m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY; - m_context << u256(0) << Instruction::RETURN; + // 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(); - // note that we have to include the functions again because of absolute jump labels + // We have to include copies of functions in the construction time and runtime context + // because of absolute jumps. appendMissingFunctions(); + m_runtimeCompiler->appendMissingFunctions(); - solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), ""); - return size_t(runtimeSub.data()); + m_context << deployRoutine; + + solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); + m_context.pushSubroutineSize(m_context.runtimeSub()); + m_context << Instruction::DUP1; + m_context.pushSubroutineOffset(m_context.runtimeSub()); + m_context << u256(0) << Instruction::CODECOPY; + m_context << u256(0) << Instruction::RETURN; + + return m_context.runtimeSub(); } void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor) @@ -176,6 +193,9 @@ void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _construc void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor) { CompilerContext::LocationSetter locationSetter(m_context, _constructor); + if (!_constructor.isPayable()) + appendCallValueCheck(); + // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program if (!_constructor.parameters().empty()) { @@ -243,11 +263,8 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac if (fallback) { if (!fallback->isPayable()) - { - // Throw if function is not payable but call contained ether. - m_context << Instruction::CALLVALUE; - m_context.appendConditionalJumpTo(m_context.errorTag()); - } + appendCallValueCheck(); + eth::AssemblyItem returnTag = m_context.pushNewTag(); fallback->accept(*this); m_context << returnTag; @@ -266,11 +283,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac // We have to allow this for libraries, because value of the previous // call is still visible in the delegatecall. if (!functionType->isPayable() && !_contract.isLibrary()) - { - // Throw if function is not payable but call contained ether. - m_context << Instruction::CALLVALUE; - m_context.appendConditionalJumpTo(m_context.errorTag()); - } + appendCallValueCheck(); eth::AssemblyItem returnTag = m_context.pushNewTag(); m_context << CompilerUtils::dataStartOffset; @@ -293,13 +306,14 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter { // stack: v1 v2 ... v(k-1) base_offset current_offset TypePointer type = parameterType->decodingType(); + solAssert(type, "No decoding type found."); if (type->category() == Type::Category::Array) { auto const& arrayType = dynamic_cast<ArrayType const&>(*type); - solAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); + solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); if (_fromMemory) { - solAssert( + solUnimplementedAssert( arrayType.baseType()->isValueType(), "Nested memory arrays not yet implemented here." ); @@ -515,7 +529,19 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { solAssert(!!decl->type(), "Type of declaration required but not yet determined."); if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(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<VariableDeclaration const*>(decl)) { solAssert(!variable->isConstant(), ""); @@ -611,12 +637,25 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement) m_breakTags.push_back(loopEnd); m_context << loopStart; - compileExpression(_whileStatement.condition()); - m_context << Instruction::ISZERO; - m_context.appendConditionalJumpTo(loopEnd); + + // While loops have the condition prepended + if (!_whileStatement.isDoWhile()) + { + compileExpression(_whileStatement.condition()); + m_context << Instruction::ISZERO; + m_context.appendConditionalJumpTo(loopEnd); + } _whileStatement.body().accept(*this); + // Do-while loops have the condition appended + if (_whileStatement.isDoWhile()) + { + compileExpression(_whileStatement.condition()); + m_context << Instruction::ISZERO; + m_context.appendConditionalJumpTo(loopEnd); + } + m_context.appendJumpTo(loopStart); m_context << loopEnd; @@ -858,7 +897,7 @@ void ContractCompiler::compileExpression(Expression const& _expression, TypePoin CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); } -eth::Assembly ContractCompiler::cloneRuntime() +eth::AssemblyPointer ContractCompiler::cloneRuntime() { eth::Assembly a; a << Instruction::CALLDATASIZE; @@ -876,5 +915,5 @@ eth::Assembly ContractCompiler::cloneRuntime() a.appendJumpI(a.errorTag()); //@todo adjust for larger return values, make this dynamic. a << u256(0x20) << u256(0) << Instruction::RETURN; - return a; + return make_shared<eth::Assembly>(a); } diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 0799a543..10febcf7 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -38,11 +38,12 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(CompilerContext& _context, bool _optimise): + explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise): m_optimise(_optimise), + m_runtimeCompiler(_runtimeCompiler), m_context(_context) { - m_context = CompilerContext(); + 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<ContractDefinition const*, eth::Assembly const*> const& _contracts ); @@ -74,12 +74,13 @@ 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); void appendConstructor(FunctionDefinition const& _constructor); void appendFunctionSelector(ContractDefinition const& _contract); + void appendCallValueCheck(); /// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers. /// From memory if @a _fromMemory is true, otherwise from call data. /// Expects source offset on the stack, which is removed. @@ -114,9 +115,11 @@ private: void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); /// @returns the runtime assembly for clone contracts. - static eth::Assembly cloneRuntime(); + static eth::AssemblyPointer 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<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 9a096e2d..7a328528 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -99,7 +99,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& if (auto mappingType = dynamic_cast<MappingType const*>(returnType.get())) { solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - solAssert( + solUnimplementedAssert( !paramTypes[i]->isDynamicallySized(), "Accessors for mapping with dynamically-sized keys not yet implemented." ); @@ -211,7 +211,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) Token::Value op = _assignment.assignmentOperator(); if (op != Token::Assign) // compound assignment { - solAssert(_assignment.annotation().type->isValueType(), "Compound operators not implemented for non-value types."); + solUnimplementedAssert(_assignment.annotation().type->isValueType(), "Compound operators not implemented for non-value types."); unsigned lvalueSize = m_currentLValue->sizeOnStack(); unsigned itemSize = _assignment.annotation().type->sizeOnStack(); if (lvalueSize > 0) @@ -312,7 +312,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) if (!_unaryOperation.isPrefixOperation()) { // store value for later - solAssert(_unaryOperation.annotation().type->sizeOnStack() == 1, "Stack size != 1 not implemented."); + solUnimplementedAssert(_unaryOperation.annotation().type->sizeOnStack() == 1, "Stack size != 1 not implemented."); m_context << Instruction::DUP1; if (m_currentLValue->sizeOnStack() > 0) for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) @@ -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; @@ -521,8 +528,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // copy the contract's code into memory eth::Assembly const& assembly = m_context.compiledContract(contract); utils().fetchFreeMemoryPointer(); + // TODO we create a copy here, which is actually what we want. + // This should be revisited at the point where we fix + // https://github.com/ethereum/solidity/issues/1092 // pushes size - eth::AssemblyItem subroutine = m_context.addSubroutine(assembly); + auto subroutine = m_context.addSubroutine(make_shared<eth::Assembly>(assembly)); m_context << Instruction::DUP1 << subroutine; m_context << Instruction::DUP4 << Instruction::CODECOPY; @@ -845,9 +855,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ); if (funType->location() == FunctionType::Location::Internal) { - m_context << m_context.functionEntryLabel( - dynamic_cast<FunctionDefinition const&>(funType->declaration()) - ).pushTag(); + FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration()); + utils().pushCombinedFunctionEntryLabel(funDef); utils().moveIntoStack(funType->selfType()->sizeOnStack(), 1); } else @@ -883,7 +892,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) // us to link against it although we actually do not need it. auto const* function = dynamic_cast<FunctionDefinition const*>(_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<TypeType const*>(_memberAccess.annotation().type.get())) @@ -915,10 +924,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<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration), type.contractDefinition() - ).pushTag(); + )); } else { @@ -1141,7 +1150,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) break; case DataLocation::CallData: //@todo if we implement this, the value in calldata has to be added to the base offset - solAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); + solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); if (arrayType.baseType()->isValueType()) CompilerUtils(m_context).loadFromMemoryDynamic( *arrayType.baseType(), @@ -1203,7 +1212,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) } } else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) - m_context << m_context.virtualFunctionEntryLabel(*functionDef).pushTag(); + utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef)); else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration)) appendVariable(*variable, static_cast<Expression const&>(_identifier)); else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) @@ -1266,6 +1275,17 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type { if (_operator == Token::Equal || _operator == Token::NotEqual) { + if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_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; @@ -1318,7 +1338,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty bool const c_isSigned = type.isSigned(); if (_type.category() == Type::Category::FixedPoint) - solAssert(false, "Not yet implemented - FixedPointType."); + solUnimplemented("Not yet implemented - FixedPointType."); switch (_operator) { @@ -1372,7 +1392,7 @@ void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Shift operators not yet implemented.")); + BOOST_THROW_EXCEPTION(UnimplementedFeatureError() << errinfo_comment("Shift operators not yet implemented.")); switch (_operator) { case Token::SHL: @@ -1634,7 +1654,7 @@ void ExpressionCompiler::appendExternalFunctionCall( void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) { - solAssert(_expectedType.isValueType(), "Not implemented for non-value types."); + solUnimplementedAssert(_expectedType.isValueType(), "Not implemented for non-value types."); _expression.accept(*this); utils().convertType(*_expression.annotation().type, _expectedType, true); utils().storeInMemoryDynamic(_expectedType); diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index c1e05792..2ec7f800 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -120,7 +120,7 @@ void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool } else { - solAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory."); + solUnimplementedAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory."); solAssert(m_dataType->sizeOnStack() == 1, ""); if (!_move) @@ -153,7 +153,8 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): { if (m_dataType->isValueType()) { - solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); + if (m_dataType->category() != Type::Category::Function) + solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); solAssert(m_dataType->storageSize() == 1, "Invalid storage size."); } } @@ -176,21 +177,39 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const m_context << Instruction::POP << Instruction::SLOAD; else { + bool cleaned = false; m_context << Instruction::SWAP1 << Instruction::SLOAD << Instruction::SWAP1 << u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV; if (m_dataType->category() == Type::Category::FixedPoint) // implementation should be very similar to the integer case. - solAssert(false, "Not yet implemented - FixedPointType."); + solUnimplemented("Not yet implemented - FixedPointType."); if (m_dataType->category() == Type::Category::FixedBytes) + { m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL; + cleaned = true; + } else if ( m_dataType->category() == Type::Category::Integer && dynamic_cast<IntegerType const&>(*m_dataType).isSigned() ) + { m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND; - else + cleaned = true; + } + else if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) + { + if (fun->location() == FunctionType::Location::External) + { + CompilerUtils(m_context).splitExternalFunctionType(false); + cleaned = true; + } + } + if (!cleaned) + { + solAssert(m_dataType->sizeOnStack() == 1, ""); m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::AND; + } } } @@ -204,6 +223,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size."); if (m_dataType->storageBytes() == 32) { + solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size."); // offset should be zero m_context << Instruction::POP; if (!_move) @@ -222,16 +242,27 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc m_context << Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::MUL; - m_context << Instruction::NOT << Instruction::AND; - // stack: value storage_ref multiplier cleared_value - m_context - << Instruction::SWAP1 << Instruction::DUP4; + m_context << Instruction::NOT << Instruction::AND << Instruction::SWAP1; + // stack: value storage_ref cleared_value multiplier + utils.copyToStackTop(3 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack()); // stack: value storage_ref cleared_value multiplier value - if (m_dataType->category() == Type::Category::FixedBytes) + if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) + { + if (fun->location() == FunctionType::Location::External) + // Combine the two-item function type into a single stack slot. + utils.combineExternalFunctionType(false); + else + m_context << + ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << + Instruction::AND; + } + else if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes())) << Instruction::SWAP1 << Instruction::DIV; else + { + solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size for opaque type."); // remove the higher order bits m_context << (u256(1) << (8 * (32 - m_dataType->storageBytes()))) @@ -239,11 +270,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc << Instruction::DUP2 << Instruction::MUL << Instruction::DIV; + } m_context << Instruction::MUL << Instruction::OR; // stack: value storage_ref updated_value m_context << Instruction::SWAP1 << Instruction::SSTORE; if (_move) - m_context << Instruction::POP; + utils.popStackElement(*m_dataType); } } else |