diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index b55ae7d36..82e98dfff 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -30,9 +30,8 @@ #include using namespace std; - -namespace dev { -namespace solidity { +using namespace dev; +using namespace dev::solidity; /** * Simple helper class to ensure that the stack height is the same at certain places in the code. @@ -301,24 +300,18 @@ void Compiler::appendCalldataUnpacker( void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) { - unsigned dataOffset = 0; - unsigned stackDepth = 0; - for (TypePointer const& type: _typeParameters) - stackDepth += type->getSizeOnStack(); - - for (TypePointer const& type: _typeParameters) - { - CompilerUtils(m_context).copyToStackTop(stackDepth, type->getSizeOnStack()); - ExpressionCompiler(m_context, m_optimize).appendTypeConversion(*type, *type, true); - bool const c_padToWords = true; - dataOffset += CompilerUtils(m_context).storeInMemory(dataOffset, *type, c_padToWords); - stackDepth -= type->getSizeOnStack(); - } - // note that the stack is not cleaned up here - if (dataOffset == 0) + CompilerUtils utils(m_context); + if (_typeParameters.empty()) m_context << eth::Instruction::STOP; else - m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN; + { + utils.fetchFreeMemoryPointer(); + //@todo optimization: if we return a single memory array, there should be enough space before + // its data to add the needed parts and we avoid a memory copy. + utils.encodeToMemory(_typeParameters, _typeParameters); + utils.toSizeAfterFreeMemoryPointer(); + m_context << eth::Instruction::RETURN; + } } void Compiler::registerStateVariables(ContractDefinition const& _contract) @@ -634,8 +627,5 @@ void Compiler::compileExpression(Expression const& _expression, TypePointer cons ExpressionCompiler expressionCompiler(m_context, m_optimize); expressionCompiler.compile(_expression); if (_targetType) - expressionCompiler.appendTypeConversion(*_expression.getType(), *_targetType); -} - -} + CompilerUtils(m_context).convertType(*_expression.getType(), *_targetType); } diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 7a96db928..349877a29 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include using namespace std; @@ -33,6 +35,7 @@ namespace solidity const unsigned CompilerUtils::dataStartOffset = 4; const size_t CompilerUtils::freeMemoryPointer = 64; +const unsigned CompilerUtils::identityContractAddress = 4; void CompilerUtils::initialiseFreeMemoryPointer() { @@ -83,8 +86,7 @@ void CompilerUtils::loadFromMemoryDynamic( if (_keepUpdatedMemoryOffset) { // update memory counter - for (unsigned i = 0; i < _type.getSizeOnStack(); ++i) - m_context << eth::swapInstruction(1 + i); + moveToStackTop(_type.getSizeOnStack()); m_context << u256(numBytes) << eth::Instruction::ADD; } } @@ -108,15 +110,80 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound if (type.location() == ReferenceType::Location::CallData) { // stack: target source_offset source_len - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5 + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5; // stack: target source_offset source_len source_len source_offset target - << eth::Instruction::CALLDATACOPY - << eth::Instruction::DUP3 << eth::Instruction::ADD - << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; + m_context << eth::Instruction::CALLDATACOPY; + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; + } + else if (type.location() == ReferenceType::Location::Memory) + { + // memcpy using the built-in contract + ArrayUtils(m_context).retrieveLength(type); + if (type.isDynamicallySized()) + { + // change pointer to data part + m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP1; + } + // stack: + // stack for call: outsize target size source value contract gas + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4; + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP5; + m_context << u256(0) << u256(identityContractAddress); + //@TODO do not use ::CALL if less than 32 bytes? + //@todo in production, we should not have to pair c_callNewAccountGas. + m_context << u256(eth::c_callGas + 10 + eth::c_callNewAccountGas) << eth::Instruction::GAS; + m_context << eth::Instruction::SUB << eth::Instruction::CALL; + m_context << eth::Instruction::POP; // ignore return value + + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + // stack: + + if (_padToWordBoundaries && (type.isDynamicallySized() || (type.getLength()) % 32 != 0)) + { + // stack: + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; + // stack: + m_context << eth::Instruction::SWAP1 << u256(31) << eth::Instruction::AND; + // stack: + eth::AssemblyItem skip = m_context.newTag(); + if (type.isDynamicallySized()) + { + m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(skip); + } + // round off, load from there. + // stack + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3; + m_context << eth::Instruction::SUB; + // stack: target+length remainder + m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD; + // Now we AND it with ~(2**(8 * (32 - remainder)) - 1) + m_context << u256(1); + m_context << eth::Instruction::DUP4 << u256(32) << eth::Instruction::SUB; + // stack: ... 1 <32 - remainder> + m_context << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SUB; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: target+length remainder target+length-remainder + m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; + // stack: target+length remainder target+length-remainder + m_context << u256(32) << eth::Instruction::ADD; + // stack: target+length remainder + m_context << eth::Instruction::SWAP2 << eth::Instruction::POP; + + if (type.isDynamicallySized()) + m_context << skip.tag(); + // stack + m_context << eth::Instruction::POP; + } + else + // stack: + m_context << eth::Instruction::ADD; } else { - solAssert(type.location() == ReferenceType::Location::Storage, "Memory arrays not yet implemented."); + solAssert(type.location() == ReferenceType::Location::Storage, ""); m_context << eth::Instruction::POP; // remove offset, arrays always start new slot m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // stack here: memory_offset storage_offset length_bytes @@ -133,17 +200,28 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound // stack here: memory_end_offset storage_data_offset memory_offset eth::AssemblyItem loopStart = m_context.newTag(); - m_context << loopStart - // load and store - << eth::Instruction::DUP2 << eth::Instruction::SLOAD - << eth::Instruction::DUP2 << eth::Instruction::MSTORE - // increment storage_data_offset by 1 - << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD - // increment memory offset by 32 - << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD - // check for loop condition - << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::GT; + m_context << loopStart; + // load and store + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; + // increment storage_data_offset by 1 + m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD; + // increment memory offset by 32 + m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::GT; m_context.appendConditionalJumpTo(loopStart); + // stack here: memory_end_offset storage_data_offset memory_offset + if (_padToWordBoundaries) + { + // memory_end_offset - start is the actual length (we want to compute the ceil of). + // memory_offset - start is its next multiple of 32, but it might be off by 32. + // so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31 + m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1 << eth::Instruction::SUB; + m_context << u256(31) << eth::Instruction::AND; + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP2; + } m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP; } } @@ -159,6 +237,290 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound } } +void CompilerUtils::encodeToMemory( + TypePointers const& _givenTypes, + TypePointers const& _targetTypes, + bool _padToWordBoundaries, + bool _copyDynamicDataInPlace +) +{ + // stack: ... + TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; + solAssert(targetTypes.size() == _givenTypes.size(), ""); + for (TypePointer& t: targetTypes) + t = t->mobileType()->externalType(); + + // Stack during operation: + // ... ... + // The values dyn_head_i are added during the first loop and they point to the head part + // of the ith dynamic parameter, which is filled once the dynamic parts are processed. + + // store memory start pointer + m_context << eth::Instruction::DUP1; + + unsigned argSize = CompilerUtils::getSizeOnStack(_givenTypes); + unsigned stackPos = 0; // advances through the argument values + unsigned dynPointers = 0; // number of dynamic head pointers on the stack + for (size_t i = 0; i < _givenTypes.size(); ++i) + { + TypePointer targetType = targetTypes[i]; + solAssert(!!targetType, "Externalable type expected."); + if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) + { + // leave end_of_mem as dyn head pointer + m_context << eth::Instruction::DUP1 << u256(32) << eth::Instruction::ADD; + dynPointers++; + } + else + { + copyToStackTop(argSize - stackPos + dynPointers + 2, _givenTypes[i]->getSizeOnStack()); + if (targetType->isValueType()) + convertType(*_givenTypes[i], *targetType, true); + solAssert(!!targetType, "Externalable type expected."); + storeInMemoryDynamic(*targetType, _padToWordBoundaries); + } + stackPos += _givenTypes[i]->getSizeOnStack(); + } + + // now copy the dynamic part + // Stack: ... ... + stackPos = 0; + unsigned thisDynPointer = 0; + for (size_t i = 0; i < _givenTypes.size(); ++i) + { + TypePointer targetType = targetTypes[i]; + solAssert(!!targetType, "Externalable type expected."); + if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) + { + solAssert(_givenTypes[i]->getCategory() == Type::Category::Array, "Unknown dynamic type."); + auto const& arrayType = dynamic_cast(*_givenTypes[i]); + // copy tail pointer (=mem_end - mem_start) to memory + m_context << eth::dupInstruction(2 + dynPointers) << eth::Instruction::DUP2; + m_context << eth::Instruction::SUB; + m_context << eth::dupInstruction(2 + dynPointers - thisDynPointer); + m_context << eth::Instruction::MSTORE; + // now copy the array + copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.getSizeOnStack()); + // stack: ... + // copy length to memory + m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack()); + if (arrayType.location() == ReferenceType::Location::CallData) + m_context << eth::Instruction::DUP2; // length is on stack + else if (arrayType.location() == ReferenceType::Location::Storage) + m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD; + else + { + solAssert(arrayType.location() == ReferenceType::Location::Memory, ""); + m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD; + } + // stack: ... + storeInMemoryDynamic(IntegerType(256), true); + // stack: ... + // copy the new memory pointer + m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP; + // stack: ... + // copy data part + storeInMemoryDynamic(arrayType, true); + // stack: ... + + thisDynPointer++; + } + stackPos += _givenTypes[i]->getSizeOnStack(); + } + + // remove unneeded stack elements (and retain memory pointer) + m_context << eth::swapInstruction(argSize + dynPointers + 1); + popStackSlots(argSize + dynPointers + 1); +} + +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 + // previous operations. + // @todo: store in the AST whether the operand might have "dirty" higher order bits + + if (_typeOnStack == _targetType && !_cleanupNeeded) + return; + Type::Category stackTypeCategory = _typeOnStack.getCategory(); + Type::Category targetTypeCategory = _targetType.getCategory(); + + switch (stackTypeCategory) + { + case Type::Category::FixedBytes: + { + FixedBytesType const& typeOnStack = dynamic_cast(_typeOnStack); + if (targetTypeCategory == Type::Category::Integer) + { + // conversion from bytes to integer. no need to clean the high bit + // only to shift right because of opposite alignment + IntegerType const& targetIntegerType = dynamic_cast(_targetType); + m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8) + convertType(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded); + } + else + { + // clear lower-order bytes for conversion to shorter bytes - we always clean + solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); + FixedBytesType const& targetType = dynamic_cast(_targetType); + if (targetType.getNumBytes() < typeOnStack.getNumBytes()) + { + if (targetType.getNumBytes() == 0) + m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; + else + { + m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)); + m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2; + m_context << eth::Instruction::DIV << eth::Instruction::MUL; + } + } + } + } + break; + case Type::Category::Enum: + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); + break; + case Type::Category::Integer: + case Type::Category::Contract: + case Type::Category::IntegerConstant: + if (targetTypeCategory == Type::Category::FixedBytes) + { + solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant, + "Invalid conversion to FixedBytesType requested."); + // conversion from bytes to string. no need to clean the high bit + // only to shift left because of opposite alignment + FixedBytesType const& targetBytesType = dynamic_cast(_targetType); + if (auto typeOnStack = dynamic_cast(&_typeOnStack)) + if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits()) + cleanHigherOrderBits(*typeOnStack); + m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL; + } + else if (targetTypeCategory == Type::Category::Enum) + // just clean + convertType(_typeOnStack, *_typeOnStack.mobileType(), true); + else + { + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); + IntegerType addressType(0, IntegerType::Modifier::Address); + IntegerType const& targetType = targetTypeCategory == Type::Category::Integer + ? dynamic_cast(_targetType) : addressType; + if (stackTypeCategory == Type::Category::IntegerConstant) + { + IntegerConstantType const& constType = dynamic_cast(_typeOnStack); + // We know that the stack is clean, we only have to clean for a narrowing conversion + // where cleanup is forced. + if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) + cleanHigherOrderBits(targetType); + } + else + { + IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer + ? dynamic_cast(_typeOnStack) : addressType; + // Widening: clean up according to source type width + // Non-widening and force: clean up according to target type bits + if (targetType.getNumBits() > typeOnStack.getNumBits()) + cleanHigherOrderBits(typeOnStack); + else if (_cleanupNeeded) + cleanHigherOrderBits(targetType); + } + } + break; + case Type::Category::Array: + { + solAssert(targetTypeCategory == stackTypeCategory, ""); + ArrayType const& typeOnStack = dynamic_cast(_typeOnStack); + ArrayType const& targetType = dynamic_cast(_targetType); + switch (targetType.location()) + { + case ReferenceType::Location::Storage: + // Other cases are done explicitly in LValue::storeValue, and only possible by assignment. + solAssert( + targetType.isPointer() && + typeOnStack.location() == ReferenceType::Location::Storage, + "Invalid conversion to storage type." + ); + break; + case ReferenceType::Location::Memory: + { + // Copy the array to a free position in memory, unless it is already in memory. + if (typeOnStack.location() != ReferenceType::Location::Memory) + { + // stack: (variably sized) + unsigned stackSize = typeOnStack.getSizeOnStack(); + fetchFreeMemoryPointer(); + moveIntoStack(stackSize); + // stack: (variably sized) + if (targetType.isDynamicallySized()) + { + bool fromStorage = (typeOnStack.location() == ReferenceType::Location::Storage); + // store length + if (fromStorage) + { + stackSize--; + // remove storage offset, as requested by ArrayUtils::retrieveLength + m_context << eth::Instruction::POP; + } + ArrayUtils(m_context).retrieveLength(typeOnStack); + // Stack: + m_context << eth::dupInstruction(2 + stackSize) << eth::Instruction::MSTORE; + m_context << eth::dupInstruction(1 + stackSize) << u256(0x20); + m_context << eth::Instruction::ADD; + moveIntoStack(stackSize); + if (fromStorage) + { + m_context << u256(0); + stackSize++; + } + } + else + { + m_context << eth::dupInstruction(1 + stackSize); + moveIntoStack(stackSize); + } + // Stack: + // Store data part. + storeInMemoryDynamic(typeOnStack); + // Stack + storeFreeMemoryPointer(); + } + else if (typeOnStack.location() == ReferenceType::Location::CallData) + { + // Stack: + //@todo + solAssert(false, "Not yet implemented."); + } + // nothing to do for memory to memory + break; + } + default: + solAssert(false, "Invalid type conversion requested."); + } + break; + } + case Type::Category::Struct: + { + //@todo we can probably use some of the code for arrays here. + solAssert(targetTypeCategory == stackTypeCategory, ""); + auto& targetType = dynamic_cast(_targetType); + auto& stackType = dynamic_cast(_typeOnStack); + solAssert( + targetType.location() == ReferenceType::Location::Storage && + stackType.location() == ReferenceType::Location::Storage, + "Non-storage structs not yet implemented." + ); + solAssert( + targetType.isPointer(), + "Type conversion to non-pointer struct requested." + ); + break; + } + default: + // All other types should not be convertible to non-equal types. + solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); + break; + } +} + void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) { unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable)); @@ -189,6 +551,13 @@ void CompilerUtils::moveToStackTop(unsigned _stackDepth) m_context << eth::swapInstruction(1 + i); } +void CompilerUtils::moveIntoStack(unsigned _stackDepth) +{ + solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); + for (unsigned i = _stackDepth; i > 0; --i) + m_context << eth::swapInstruction(i); +} + void CompilerUtils::popStackElement(Type const& _type) { popStackSlots(_type.getSizeOnStack()); @@ -238,6 +607,16 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda return numBytes; } +void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack) +{ + if (_typeOnStack.getNumBits() == 256) + return; + else if (_typeOnStack.isSigned()) + m_context << u256(_typeOnStack.getNumBits() / 8 - 1) << eth::Instruction::SIGNEXTEND; + else + m_context << ((u256(1) << _typeOnStack.getNumBits()) - 1) << eth::Instruction::AND; +} + unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const { unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index 27c46ba11..32dc93a2c 100644 --- a/libsolidity/CompilerUtils.h +++ b/libsolidity/CompilerUtils.h @@ -81,6 +81,30 @@ public: /// Stack post: (memory_offset+length) void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); + /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given + /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes. + /// Removes the values from the stack and leaves the updated memory pointer. + /// Stack pre: ... + /// Stack post: + /// Does not touch the memory-free pointer. + /// @param _padToWordBoundaries if false, all values are concatenated without padding. + /// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length) + /// together with fixed-length data. + /// @note the locations of target reference types are ignored, because it will always be + /// memory. + void encodeToMemory( + TypePointers const& _givenTypes = {}, + TypePointers const& _targetTypes = {}, + bool _padToWordBoundaries = true, + bool _copyDynamicDataInPlace = false + ); + + /// Appends code for an implicit or explicit type conversion. For now this comprises only erasing + /// higher-order bits (@see appendHighBitCleanup) when widening integer. + /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be + /// necessary. + void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); + /// Moves the value that is at the top of the stack to a stack variable. void moveToStackVariable(VariableDeclaration const& _variable); /// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth @@ -88,6 +112,8 @@ public: void copyToStackTop(unsigned _stackDepth, unsigned _itemSize); /// Moves a single stack element (with _stackDepth items on top of it) to the top of the stack. void moveToStackTop(unsigned _stackDepth); + /// Moves a single stack element past @a _stackDepth other stack elements + void moveIntoStack(unsigned _stackDepth); /// Removes the current value from the top of the stack. void popStackElement(Type const& _type); /// Removes element from the top of the stack _amount times. @@ -110,6 +136,12 @@ public: static const size_t freeMemoryPointer; private: + /// Address of the precompiled identity contract. + static const unsigned identityContractAddress; + + //// Appends code that cleans higher-order bits for integer types. + void cleanHigherOrderBits(IntegerType const& _typeOnStack); + /// Prepares the given type for storing in memory by shifting it if necessary. unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const; /// Loads type from memory assuming memory offset is on stack top. diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 811ee60ec..12274c7ab 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -51,7 +51,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c solAssert(!!_varDecl.getValue()->getType(), "Type information not available."); CompilerContext::LocationSetter locationSetter(m_context, _varDecl); _varDecl.getValue()->accept(*this); - appendTypeConversion(*_varDecl.getValue()->getType(), *_varDecl.getType(), true); + utils().convertType(*_varDecl.getValue()->getType(), *_varDecl.getType(), true); StorageItem(m_context, _varDecl).storeValue(*_varDecl.getType(), _varDecl.getLocation(), true); } @@ -77,10 +77,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& // pop offset m_context << eth::Instruction::POP; // move storage offset to memory. - CompilerUtils(m_context).storeInMemory(32); + utils().storeInMemory(32); // move key to memory. - CompilerUtils(m_context).copyToStackTop(paramTypes.size() - i, 1); - CompilerUtils(m_context).storeInMemory(0); + utils().copyToStackTop(paramTypes.size() - i, 1); + utils().storeInMemory(0); m_context << u256(64) << u256(0) << eth::Instruction::SHA3; // push offset m_context << u256(0); @@ -90,7 +90,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { // pop offset m_context << eth::Instruction::POP; - CompilerUtils(m_context).copyToStackTop(paramTypes.size() - i + 1, 1); + utils().copyToStackTop(paramTypes.size() - i + 1, 1); ArrayUtils(m_context).accessIndex(*arrayType); returnType = arrayType->getBaseType(); } @@ -105,7 +105,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& m_context << eth::swapInstruction(paramTypes.size()); m_context << eth::Instruction::POP; m_context << eth::swapInstruction(paramTypes.size()); - CompilerUtils(m_context).popStackSlots(paramTypes.size() - 1); + utils().popStackSlots(paramTypes.size() - 1); } unsigned retSizeOnStack = 0; solAssert(accessorType.getReturnParameterTypes().size() >= 1, ""); @@ -142,128 +142,14 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); } -void ExpressionCompiler::appendTypeConversion(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 - // previous operations. - // @todo: store in the AST whether the operand might have "dirty" higher order bits - - if (_typeOnStack == _targetType && !_cleanupNeeded) - return; - Type::Category stackTypeCategory = _typeOnStack.getCategory(); - Type::Category targetTypeCategory = _targetType.getCategory(); - - switch (stackTypeCategory) - { - case Type::Category::FixedBytes: - { - FixedBytesType const& typeOnStack = dynamic_cast(_typeOnStack); - if (targetTypeCategory == Type::Category::Integer) - { - // conversion from bytes to integer. no need to clean the high bit - // only to shift right because of opposite alignment - IntegerType const& targetIntegerType = dynamic_cast(_targetType); - m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8) - appendTypeConversion(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded); - } - else - { - // clear lower-order bytes for conversion to shorter bytes - we always clean - solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); - FixedBytesType const& targetType = dynamic_cast(_targetType); - if (targetType.getNumBytes() < typeOnStack.getNumBytes()) - { - if (targetType.getNumBytes() == 0) - m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; - else - m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)) - << eth::Instruction::DUP1 << eth::Instruction::SWAP2 - << eth::Instruction::DIV << eth::Instruction::MUL; - } - } - } - break; - case Type::Category::Enum: - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); - break; - case Type::Category::Integer: - case Type::Category::Contract: - case Type::Category::IntegerConstant: - if (targetTypeCategory == Type::Category::FixedBytes) - { - solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant, - "Invalid conversion to FixedBytesType requested."); - // conversion from bytes to string. no need to clean the high bit - // only to shift left because of opposite alignment - FixedBytesType const& targetBytesType = dynamic_cast(_targetType); - if (auto typeOnStack = dynamic_cast(&_typeOnStack)) - if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits()) - appendHighBitsCleanup(*typeOnStack); - m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL; - } - else if (targetTypeCategory == Type::Category::Enum) - // just clean - appendTypeConversion(_typeOnStack, *_typeOnStack.mobileType(), true); - else - { - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); - IntegerType addressType(0, IntegerType::Modifier::Address); - IntegerType const& targetType = targetTypeCategory == Type::Category::Integer - ? dynamic_cast(_targetType) : addressType; - if (stackTypeCategory == Type::Category::IntegerConstant) - { - IntegerConstantType const& constType = dynamic_cast(_typeOnStack); - // We know that the stack is clean, we only have to clean for a narrowing conversion - // where cleanup is forced. - if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) - appendHighBitsCleanup(targetType); - } - else - { - IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer - ? dynamic_cast(_typeOnStack) : addressType; - // Widening: clean up according to source type width - // Non-widening and force: clean up according to target type bits - if (targetType.getNumBits() > typeOnStack.getNumBits()) - appendHighBitsCleanup(typeOnStack); - else if (_cleanupNeeded) - appendHighBitsCleanup(targetType); - } - } - break; - case Type::Category::Array: - //@TODO - break; - case Type::Category::Struct: - { - solAssert(targetTypeCategory == stackTypeCategory, ""); - auto& targetType = dynamic_cast(_targetType); - auto& stackType = dynamic_cast(_typeOnStack); - solAssert( - targetType.location() == ReferenceType::Location::Storage && - stackType.location() == ReferenceType::Location::Storage, - "Non-storage structs not yet implemented." - ); - solAssert( - targetType.isPointer(), - "Type conversion to non-pointer struct requested." - ); - break; - } - default: - // All other types should not be convertible to non-equal types. - solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); - break; - } -} - bool ExpressionCompiler::visit(Assignment const& _assignment) { CompilerContext::LocationSetter locationSetter(m_context, _assignment); _assignment.getRightHandSide().accept(*this); if (_assignment.getType()->isValueType()) - appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); + utils().convertType(*_assignment.getRightHandSide().getType(), *_assignment.getType()); + // We need this conversion mostly in the case of compound assignments. For non-value types + // the conversion is done in LValue::storeValue. _assignment.getLeftHandSide().accept(*this); solAssert(!!m_currentLValue, "LValue not retrieved."); @@ -275,8 +161,8 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) unsigned itemSize = _assignment.getType()->getSizeOnStack(); if (lvalueSize > 0) { - CompilerUtils(m_context).copyToStackTop(lvalueSize + itemSize, itemSize); - CompilerUtils(m_context).copyToStackTop(itemSize + lvalueSize, lvalueSize); + utils().copyToStackTop(lvalueSize + itemSize, itemSize); + utils().copyToStackTop(itemSize + lvalueSize, lvalueSize); // value lvalue_ref value lvalue_ref } m_currentLValue->retrieveValue(_assignment.getLocation(), true); @@ -391,16 +277,16 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) if (swap) { leftExpression.accept(*this); - appendTypeConversion(*leftExpression.getType(), commonType, cleanupNeeded); + utils().convertType(*leftExpression.getType(), commonType, cleanupNeeded); rightExpression.accept(*this); - appendTypeConversion(*rightExpression.getType(), commonType, cleanupNeeded); + utils().convertType(*rightExpression.getType(), commonType, cleanupNeeded); } else { rightExpression.accept(*this); - appendTypeConversion(*rightExpression.getType(), commonType, cleanupNeeded); + utils().convertType(*rightExpression.getType(), commonType, cleanupNeeded); leftExpression.accept(*this); - appendTypeConversion(*leftExpression.getType(), commonType, cleanupNeeded); + utils().convertType(*leftExpression.getType(), commonType, cleanupNeeded); } if (Token::isCompareOp(c_op)) appendCompareOperatorCode(c_op, commonType); @@ -423,7 +309,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(_functionCall.getNames().empty(), ""); Expression const& firstArgument = *_functionCall.getArguments().front(); firstArgument.accept(*this); - appendTypeConversion(*firstArgument.getType(), *_functionCall.getType()); + utils().convertType(*firstArgument.getType(), *_functionCall.getType()); } else { @@ -461,7 +347,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) for (unsigned i = 0; i < arguments.size(); ++i) { arguments[i]->accept(*this); - appendTypeConversion(*arguments[i]->getType(), *function.getParameterTypes()[i]); + utils().convertType(*arguments[i]->getType(), *function.getParameterTypes()[i]); } _functionCall.getExpression().accept(*this); @@ -475,7 +361,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // @todo for now, the return value of a function is its first return value, so remove // all others for (unsigned i = 1; i < function.getReturnParameterTypes().size(); ++i) - CompilerUtils(m_context).popStackElement(*function.getReturnParameterTypes()[i]); + utils().popStackElement(*function.getReturnParameterTypes()[i]); break; } case Location::External: @@ -500,7 +386,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) *function.getReturnParameterTypes().front()).getContractDefinition(); // copy the contract's code into memory bytes const& bytecode = m_context.getCompiledContract(contract); - CompilerUtils(m_context).fetchFreeMemoryPointer(); + utils().fetchFreeMemoryPointer(); m_context << u256(bytecode.size()) << eth::Instruction::DUP1; //@todo could be done by actually appending the Assembly, but then we probably need to compile // multiple times. Will revisit once external fuctions are inlined. @@ -508,10 +394,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << eth::Instruction::DUP4 << eth::Instruction::CODECOPY; m_context << eth::Instruction::ADD; - encodeToMemory(argumentTypes, function.getParameterTypes()); + utils().encodeToMemory(argumentTypes, function.getParameterTypes()); // now on stack: memory_end_ptr // need: size, offset, endowment - CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + utils().toSizeAfterFreeMemoryPointer(); if (function.valueSet()) m_context << eth::dupInstruction(3); else @@ -527,7 +413,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) _functionCall.getExpression().accept(*this); arguments.front()->accept(*this); - appendTypeConversion(*arguments.front()->getType(), IntegerType(256), true); + utils().convertType(*arguments.front()->getType(), IntegerType(256), true); // Note that function is not the original function, but the ".gas" function. // Its values of gasSet and valueSet is equal to the original function's though. unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0); @@ -550,8 +436,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) _functionCall.getExpression().accept(*this); m_context << u256(0); // do not send gas (there still is the stipend) arguments.front()->accept(*this); - appendTypeConversion(*arguments.front()->getType(), - *function.getParameterTypes().front(), true); + utils().convertType( + *arguments.front()->getType(), + *function.getParameterTypes().front(), true + ); appendExternalFunctionCall( FunctionType( TypePointers{}, @@ -568,7 +456,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case Location::Suicide: arguments.front()->accept(*this); - appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); + utils().convertType(*arguments.front()->getType(), *function.getParameterTypes().front(), true); m_context << eth::Instruction::SUICIDE; break; case Location::SHA3: @@ -579,9 +467,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arg->accept(*this); argumentTypes.push_back(arg->getType()); } - CompilerUtils(m_context).fetchFreeMemoryPointer(); - encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true); - CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + utils().fetchFreeMemoryPointer(); + utils().encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true); + utils().toSizeAfterFreeMemoryPointer(); m_context << eth::Instruction::SHA3; break; } @@ -595,16 +483,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) for (unsigned arg = logNumber; arg > 0; --arg) { arguments[arg]->accept(*this); - appendTypeConversion(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true); + utils().convertType(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true); } arguments.front()->accept(*this); - CompilerUtils(m_context).fetchFreeMemoryPointer(); - encodeToMemory( + utils().fetchFreeMemoryPointer(); + utils().encodeToMemory( {arguments.front()->getType()}, {function.getParameterTypes().front()}, false, true); - CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + utils().toSizeAfterFreeMemoryPointer(); m_context << eth::logInstruction(logNumber); break; } @@ -619,7 +507,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { ++numIndexed; arguments[arg - 1]->accept(*this); - appendTypeConversion( + utils().convertType( *arguments[arg - 1]->getType(), *function.getParameterTypes()[arg - 1], true @@ -642,17 +530,17 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) nonIndexedArgTypes.push_back(arguments[arg]->getType()); nonIndexedParamTypes.push_back(function.getParameterTypes()[arg]); } - CompilerUtils(m_context).fetchFreeMemoryPointer(); - encodeToMemory(nonIndexedArgTypes, nonIndexedParamTypes); + utils().fetchFreeMemoryPointer(); + utils().encodeToMemory(nonIndexedArgTypes, nonIndexedParamTypes); // need: topic1 ... topicn memsize memstart - CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + utils().toSizeAfterFreeMemoryPointer(); m_context << eth::logInstruction(numIndexed); break; } case Location::BlockHash: { arguments[0]->accept(*this); - appendTypeConversion(*arguments[0]->getType(), *function.getParameterTypes()[0], true); + utils().convertType(*arguments[0]->getType(), *function.getParameterTypes()[0], true); m_context << eth::Instruction::BLOCKHASH; break; } @@ -713,7 +601,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) identifier = FunctionType(*function).externalIdentifier(); else solAssert(false, "Contract member is neither variable nor function."); - appendTypeConversion(type, IntegerType(0, IntegerType::Modifier::Address), true); + utils().convertType(type, IntegerType(0, IntegerType::Modifier::Address), true); m_context << identifier; } else @@ -726,13 +614,19 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) case Type::Category::Integer: if (member == "balance") { - appendTypeConversion(*_memberAccess.getExpression().getType(), - IntegerType(0, IntegerType::Modifier::Address), true); + utils().convertType( + *_memberAccess.getExpression().getType(), + IntegerType(0, IntegerType::Modifier::Address), + true + ); m_context << eth::Instruction::BALANCE; } else if ((set{"send", "call", "callcode"}).count(member)) - appendTypeConversion(*_memberAccess.getExpression().getType(), - IntegerType(0, IntegerType::Modifier::Address), true); + utils().convertType( + *_memberAccess.getExpression().getType(), + IntegerType(0, IntegerType::Modifier::Address), + true + ); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); break; @@ -809,7 +703,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) auto const& type = dynamic_cast(*_memberAccess.getExpression().getType()); if (!type.isDynamicallySized()) { - CompilerUtils(m_context).popStackElement(type); + utils().popStackElement(type); m_context << type.getLength(); } else @@ -850,7 +744,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression()); m_context << eth::Instruction::SWAP1; solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - appendTypeMoveToMemory(IntegerType(256)); + utils().storeInMemoryDynamic(IntegerType(256)); m_context << u256(0) << eth::Instruction::SHA3; m_context << u256(0); setLValueToStorageItem(_indexAccess); @@ -1071,16 +965,6 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) } } -void ExpressionCompiler::appendHighBitsCleanup(IntegerType const& _typeOnStack) -{ - if (_typeOnStack.getNumBits() == 256) - return; - else if (_typeOnStack.isSigned()) - m_context << u256(_typeOnStack.getNumBits() / 8 - 1) << eth::Instruction::SIGNEXTEND; - else - m_context << ((u256(1) << _typeOnStack.getNumBits()) - 1) << eth::Instruction::AND; -} - void ExpressionCompiler::appendExternalFunctionCall( FunctionType const& _functionType, vector> const& _arguments @@ -1127,7 +1011,7 @@ void ExpressionCompiler::appendExternalFunctionCall( // If we have a BareCall or BareCallCode and the first type has exactly 4 bytes, use it as // function identifier. _arguments.front()->accept(*this); - appendTypeConversion( + utils().convertType( *_arguments.front()->getType(), IntegerType(8 * CompilerUtils::dataStartOffset), true @@ -1144,16 +1028,16 @@ void ExpressionCompiler::appendExternalFunctionCall( } // Copy function identifier to memory. - CompilerUtils(m_context).fetchFreeMemoryPointer(); + utils().fetchFreeMemoryPointer(); if (!_functionType.isBareCall() || manualFunctionId) { m_context << eth::dupInstruction(2 + gasValueSize + CompilerUtils::getSizeOnStack(argumentTypes)); - appendTypeMoveToMemory(IntegerType(8 * CompilerUtils::dataStartOffset), false); + utils().storeInMemoryDynamic(IntegerType(8 * CompilerUtils::dataStartOffset), false); } // If the function takes arbitrary parameters, copy dynamic length data in place. // Move argumenst to memory, will not update the free memory pointer (but will update the memory // pointer on the stack). - encodeToMemory( + utils().encodeToMemory( argumentTypes, _functionType.getParameterTypes(), _functionType.padArguments(), @@ -1171,7 +1055,7 @@ void ExpressionCompiler::appendExternalFunctionCall( // Output data will replace input data. // put on stack: m_context << u256(retSize); - CompilerUtils(m_context).fetchFreeMemoryPointer(); + utils().fetchFreeMemoryPointer(); m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SUB; m_context << eth::Instruction::DUP2; @@ -1212,7 +1096,7 @@ void ExpressionCompiler::appendExternalFunctionCall( m_context.appendConditionalJumpTo(m_context.errorTag()); } - CompilerUtils(m_context).popStackSlots(remainsSize); + utils().popStackSlots(remainsSize); if (returnSuccessCondition) { @@ -1221,130 +1105,28 @@ void ExpressionCompiler::appendExternalFunctionCall( else if (funKind == FunctionKind::RIPEMD160) { // fix: built-in contract returns right-aligned data - CompilerUtils(m_context).fetchFreeMemoryPointer(); - CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(160), false, true, false); - appendTypeConversion(IntegerType(160), FixedBytesType(20)); + utils().fetchFreeMemoryPointer(); + utils().loadFromMemoryDynamic(IntegerType(160), false, true, false); + utils().convertType(IntegerType(160), FixedBytesType(20)); } else if (firstReturnType) { //@todo manually update free memory pointer if we accept returning memory-stored objects - CompilerUtils(m_context).fetchFreeMemoryPointer(); - CompilerUtils(m_context).loadFromMemoryDynamic(*firstReturnType, false, true, false); + utils().fetchFreeMemoryPointer(); + utils().loadFromMemoryDynamic(*firstReturnType, false, true, false); } } -void ExpressionCompiler::encodeToMemory( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _padToWordBoundaries, - bool _copyDynamicDataInPlace -) -{ - // stack: ... - TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; - solAssert(targetTypes.size() == _givenTypes.size(), ""); - for (TypePointer& t: targetTypes) - t = t->mobileType()->externalType(); - - // Stack during operation: - // ... ... - // The values dyn_head_i are added during the first loop and they point to the head part - // of the ith dynamic parameter, which is filled once the dynamic parts are processed. - - // store memory start pointer - m_context << eth::Instruction::DUP1; - - unsigned argSize = CompilerUtils::getSizeOnStack(_givenTypes); - unsigned stackPos = 0; // advances through the argument values - unsigned dynPointers = 0; // number of dynamic head pointers on the stack - for (size_t i = 0; i < _givenTypes.size(); ++i) - { - TypePointer targetType = targetTypes[i]; - solAssert(!!targetType, "Externalable type expected."); - if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) - { - // leave end_of_mem as dyn head pointer - m_context << eth::Instruction::DUP1 << u256(32) << eth::Instruction::ADD; - dynPointers++; - } - else - { - CompilerUtils(m_context).copyToStackTop( - argSize - stackPos + dynPointers + 2, - _givenTypes[i]->getSizeOnStack() - ); - if (targetType->isValueType()) - appendTypeConversion(*_givenTypes[i], *targetType, true); - solAssert(!!targetType, "Externalable type expected."); - appendTypeMoveToMemory(*targetType, _padToWordBoundaries); - } - stackPos += _givenTypes[i]->getSizeOnStack(); - } - - // now copy the dynamic part - // Stack: ... ... - stackPos = 0; - unsigned thisDynPointer = 0; - for (size_t i = 0; i < _givenTypes.size(); ++i) - { - TypePointer targetType = targetTypes[i]; - solAssert(!!targetType, "Externalable type expected."); - if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) - { - solAssert(_givenTypes[i]->getCategory() == Type::Category::Array, "Unknown dynamic type."); - auto const& arrayType = dynamic_cast(*_givenTypes[i]); - // copy tail pointer (=mem_end - mem_start) to memory - m_context << eth::dupInstruction(2 + dynPointers) << eth::Instruction::DUP2; - m_context << eth::Instruction::SUB; - m_context << eth::dupInstruction(2 + dynPointers - thisDynPointer); - m_context << eth::Instruction::MSTORE; - // now copy the array - CompilerUtils(m_context).copyToStackTop( - argSize - stackPos + dynPointers + 2, - arrayType.getSizeOnStack() - ); - // copy length to memory - m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack()); - if (arrayType.location() == ReferenceType::Location::CallData) - m_context << eth::Instruction::DUP2; // length is on stack - else if (arrayType.location() == ReferenceType::Location::Storage) - m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD; - else - { - solAssert(arrayType.location() == ReferenceType::Location::Memory, ""); - m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD; - } - appendTypeMoveToMemory(IntegerType(256), true); - // copy the new memory pointer - m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP; - // copy data part - appendTypeMoveToMemory(arrayType, true); - - thisDynPointer++; - } - stackPos += _givenTypes[i]->getSizeOnStack(); - } - - // remove unneeded stack elements (and retain memory pointer) - m_context << eth::swapInstruction(argSize + dynPointers + 1); - CompilerUtils(m_context).popStackSlots(argSize + dynPointers + 1); -} - -void ExpressionCompiler::appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries) -{ - CompilerUtils(m_context).storeInMemoryDynamic(_type, _padToWordBoundaries); -} - void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) { _expression.accept(*this); if (_expectedType.isValueType()) { - appendTypeConversion(*_expression.getType(), _expectedType, true); - appendTypeMoveToMemory(_expectedType); + utils().convertType(*_expression.getType(), _expectedType, true); + utils().storeInMemoryDynamic(_expectedType); } else - appendTypeMoveToMemory(*_expression.getType()->mobileType()); + utils().storeInMemoryDynamic(*_expression.getType()->mobileType()); } void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression) @@ -1364,5 +1146,10 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) setLValue(_expression, *_expression.getType()); } +CompilerUtils ExpressionCompiler::utils() +{ + return CompilerUtils(m_context); +} + } } diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 90994dfdb..642560c64 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -26,9 +26,9 @@ #include #include #include -#include #include #include +#include namespace dev { namespace eth @@ -39,6 +39,7 @@ namespace solidity { // forward declarations class CompilerContext; +class CompilerUtils; class Type; class IntegerType; class ArrayType; @@ -66,12 +67,6 @@ public: /// Appends code for a State Variable accessor function void appendStateVariableAccessor(VariableDeclaration const& _varDecl); - /// Appends an implicit or explicit type conversion. For now this comprises only erasing - /// higher-order bits (@see appendHighBitCleanup) when widening integer. - /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be - /// necessary. - void appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); - private: virtual bool visit(Assignment const& _assignment) override; virtual bool visit(UnaryOperation const& _unaryOperation) override; @@ -94,33 +89,11 @@ private: void appendShiftOperatorCode(Token::Value _operator); /// @} - //// Appends code that cleans higher-order bits for integer types. - void appendHighBitsCleanup(IntegerType const& _typeOnStack); - /// Appends code to call a function of the given type with the given arguments. void appendExternalFunctionCall( FunctionType const& _functionType, std::vector> const& _arguments ); - /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given - /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes. - /// Removes the values from the stack and leaves the updated memory pointer. - /// Stack pre: ... - /// Stack post: - /// Does not touch the memory-free pointer. - /// @param _padToWordBoundaries if false, all values are concatenated without padding. - /// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length) - /// together with fixed-length data. - void encodeToMemory( - TypePointers const& _givenTypes = {}, - TypePointers const& _targetTypes = {}, - bool _padToWordBoundaries = true, - bool _copyDynamicDataInPlace = false - ); - /// Appends code that moves a stack element of the given type to memory. The memory offset is - /// expected below the stack element and is updated by this call. - /// For arrays, this only copies the data part. - void appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries = true); /// Appends code that evaluates a single expression and moves the result to memory. The memory offset is /// expected to be on the stack and is updated by this call. void appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression); @@ -137,9 +110,13 @@ private: template void setLValue(Expression const& _expression, _Arguments const&... _arguments); + /// @returns the CompilerUtils object containing the current context. + CompilerUtils utils(); + bool m_optimize; CompilerContext& m_context; std::unique_ptr m_currentLValue; + }; template diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index fd4bbcf6d..8d316a977 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -106,7 +106,7 @@ BOOST_AUTO_TEST_CASE(location_test) AssemblyItems items = compileContract(sourceCode); vector locations = vector(17, SourceLocation(2, 75, n)) + - vector(14, SourceLocation(20, 72, n)) + + vector(26, SourceLocation(20, 72, n)) + vector{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + vector(4, SourceLocation(58, 67, n)) + vector(3, SourceLocation(20, 72, n)); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index f12abd48e..f4d875e77 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2420,7 +2420,7 @@ BOOST_AUTO_TEST_CASE(event_really_lots_of_data_from_storage) callContractFunction("deposit()"); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(10, 0x60, 15, 3) + asBytes("ABC")); + BOOST_CHECK(m_logs[0].data == encodeArgs(10, 0x60, 15, 3, string("ABC"))); BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::sha3(string("Deposit(uint256,bytes,uint256)"))); } @@ -4232,6 +4232,31 @@ BOOST_AUTO_TEST_CASE(reusing_memory) BOOST_REQUIRE(callContractFunction("f(uint256)", 0x34) == encodeArgs(dev::sha3(dev::toBigEndian(u256(0x34))))); } +BOOST_AUTO_TEST_CASE(return_string) +{ + char const* sourceCode = R"( + contract Main { + string public s; + function set(string _s) external { + s = _s; + } + function get1() returns (string r) { + return s; + } +// function get2() returns (string r) { +// r = s; +// } + } + )"; + compileAndRun(sourceCode, 0, "Main"); + string s("Julia"); + bytes args = encodeArgs(u256(0x20), u256(s.length()), s); + BOOST_REQUIRE(callContractFunction("set(string)", asString(args)) == encodeArgs()); + BOOST_CHECK(callContractFunction("get1()") == args); +// BOOST_CHECK(callContractFunction("get2()") == args); +// BOOST_CHECK(callContractFunction("s()") == args); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/solidityExecutionFramework.h b/test/libsolidity/solidityExecutionFramework.h index 44590b1c8..4ba229815 100644 --- a/test/libsolidity/solidityExecutionFramework.h +++ b/test/libsolidity/solidityExecutionFramework.h @@ -174,11 +174,11 @@ protected: BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); BOOST_REQUIRE(!executive.call(m_contractAddress, m_sender, _value, m_gasPrice, &_data, m_gas)); } - BOOST_REQUIRE(executive.go()); + BOOST_REQUIRE(executive.go(/* DEBUG eth::Executive::simpleTrace() */)); m_state.noteSending(m_sender); executive.finalize(); - m_gasUsed = executive.gasUsed(); - m_output = std::move(res.output); // FIXME: Looks like Framework needs ExecutiveResult embedded + m_gasUsed = res.gasUsed; + m_output = std::move(res.output); m_logs = executive.logs(); }