/* This file is part of cpp-ethereum. cpp-ethereum is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. cpp-ethereum is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ /** * @author Christian * @date 2014 * Routines used by both the compiler and the expression compiler. */ #include #include #include #include #include #include using namespace std; namespace dev { namespace solidity { const unsigned CompilerUtils::dataStartOffset = 4; const size_t CompilerUtils::freeMemoryPointer = 64; const unsigned CompilerUtils::identityContractAddress = 4; void CompilerUtils::initialiseFreeMemoryPointer() { m_context << u256(freeMemoryPointer + 32); storeFreeMemoryPointer(); } void CompilerUtils::fetchFreeMemoryPointer() { m_context << u256(freeMemoryPointer) << eth::Instruction::MLOAD; } void CompilerUtils::storeFreeMemoryPointer() { m_context << u256(freeMemoryPointer) << eth::Instruction::MSTORE; } void CompilerUtils::allocateMemory() { fetchFreeMemoryPointer(); m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; storeFreeMemoryPointer(); } void CompilerUtils::toSizeAfterFreeMemoryPointer() { fetchFreeMemoryPointer(); m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::SUB; m_context << eth::Instruction::SWAP1; } unsigned CompilerUtils::loadFromMemory( unsigned _offset, Type const& _type, bool _fromCalldata, bool _padToWordBoundaries ) { solAssert(_type.category() != Type::Category::Array, "Unable to statically load dynamic type."); m_context << u256(_offset); return loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); } void CompilerUtils::loadFromMemoryDynamic( Type const& _type, bool _fromCalldata, bool _padToWordBoundaries, bool _keepUpdatedMemoryOffset ) { solAssert(_type.category() != Type::Category::Array, "Arrays not yet implemented."); if (_keepUpdatedMemoryOffset) m_context << eth::Instruction::DUP1; unsigned numBytes = loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); if (_keepUpdatedMemoryOffset) { // update memory counter moveToStackTop(_type.sizeOnStack()); m_context << u256(numBytes) << eth::Instruction::ADD; } } void CompilerUtils::storeInMemory(unsigned _offset) { unsigned numBytes = prepareMemoryStore(IntegerType(256), true); if (numBytes > 0) m_context << u256(_offset) << eth::Instruction::MSTORE; } void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries) { if (auto ref = dynamic_cast(&_type)) { solAssert(ref->location() == DataLocation::Memory, ""); storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries); } else if (auto str = dynamic_cast(&_type)) { m_context << eth::Instruction::DUP1; storeStringData(bytesConstRef(str->value())); if (_padToWordBoundaries) m_context << u256(((str->value().size() + 31) / 32) * 32); else m_context << u256(str->value().size()); m_context << eth::Instruction::ADD; } else { unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); if (numBytes > 0) { solAssert( _type.sizeOnStack() == 1, "Memory store of types with stack size != 1 not implemented." ); m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; m_context << u256(numBytes) << eth::Instruction::ADD; } } } 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::sizeOnStack(_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]->sizeOnStack()); solAssert(!!targetType, "Externalable type expected."); TypePointer type = targetType; if ( _givenTypes[i]->dataStoredIn(DataLocation::Storage) || _givenTypes[i]->dataStoredIn(DataLocation::CallData) || _givenTypes[i]->category() == Type::Category::StringLiteral ) type = _givenTypes[i]; // delay conversion else convertType(*_givenTypes[i], *targetType, true); if (auto arrayType = dynamic_cast(type.get())) ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries); else storeInMemoryDynamic(*type, _padToWordBoundaries); } stackPos += _givenTypes[i]->sizeOnStack(); } // 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) { // 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; // stack: ... if (_givenTypes[i]->category() == Type::Category::StringLiteral) { auto const& strType = dynamic_cast(*_givenTypes[i]); m_context << u256(strType.value().size()); storeInMemoryDynamic(IntegerType(256), true); // stack: ... storeInMemoryDynamic(strType, _padToWordBoundaries); } else { solAssert(_givenTypes[i]->category() == Type::Category::Array, "Unknown dynamic type."); auto const& arrayType = dynamic_cast(*_givenTypes[i]); // now copy the array copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.sizeOnStack()); // stack: ... // copy length to memory m_context << eth::dupInstruction(1 + arrayType.sizeOnStack()); if (arrayType.location() == DataLocation::CallData) m_context << eth::Instruction::DUP2; // length is on stack else if (arrayType.location() == DataLocation::Storage) m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; else { solAssert(arrayType.location() == DataLocation::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.sizeOnStack() + 1) << eth::Instruction::POP; // stack: ... // copy data part ArrayUtils(m_context).copyArrayToMemory(arrayType, _padToWordBoundaries); // stack: ... } thisDynPointer++; } stackPos += _givenTypes[i]->sizeOnStack(); } // remove unneeded stack elements (and retain memory pointer) m_context << eth::swapInstruction(argSize + dynPointers + 1); popStackSlots(argSize + dynPointers + 1); } void CompilerUtils::memoryCopy() { // Stack here: size target source // stack for call: outsize target size source value contract gas //@TODO do not use ::CALL if less than 32 bytes? m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1; m_context << u256(0) << u256(identityContractAddress); // compute gas costs m_context << u256(32) << eth::Instruction::DUP5 << u256(31) << eth::Instruction::ADD; m_context << eth::Instruction::DIV << u256(eth::c_identityWordGas) << eth::Instruction::MUL; m_context << u256(eth::c_identityGas) << eth::Instruction::ADD; m_context << eth::Instruction::CALL; m_context << eth::Instruction::POP; // ignore return value } 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.category(); Type::Category targetTypeCategory = _targetType.category(); 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.numBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8) convertType(IntegerType(typeOnStack.numBytes() * 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.numBytes() < typeOnStack.numBytes()) { if (targetType.numBytes() == 0) m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; else { m_context << (u256(1) << (256 - targetType.numBytes() * 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.numBytes() * 8 > typeOnStack->numBits()) cleanHigherOrderBits(*typeOnStack); m_context << (u256(1) << (256 - targetBytesType.numBytes() * 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.numBits() < constType.integerType()->numBits() && _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.numBits() > typeOnStack.numBits()) cleanHigherOrderBits(typeOnStack); else if (_cleanupNeeded) cleanHigherOrderBits(targetType); } } break; case Type::Category::StringLiteral: { auto const& literalType = dynamic_cast(_typeOnStack); string const& value = literalType.value(); bytesConstRef data(value); if (targetTypeCategory == Type::Category::FixedBytes) { solAssert(data.size() <= 32, ""); m_context << h256::Arith(h256(data, h256::AlignLeft)); } else if (targetTypeCategory == Type::Category::Array) { auto const& arrayType = dynamic_cast(_targetType); solAssert(arrayType.isByteArray(), ""); u256 storageSize(32 + ((data.size() + 31) / 32) * 32); m_context << storageSize; allocateMemory(); // stack: mempos m_context << eth::Instruction::DUP1 << u256(data.size()); storeInMemoryDynamic(IntegerType(256)); // stack: mempos datapos storeStringData(data); break; } else solAssert( false, "Invalid conversion from string literal to " + _targetType.toString(false) + " requested." ); break; } case Type::Category::Array: { solAssert(targetTypeCategory == stackTypeCategory, ""); ArrayType const& typeOnStack = dynamic_cast(_typeOnStack); ArrayType const& targetType = dynamic_cast(_targetType); switch (targetType.location()) { case DataLocation::Storage: // Other cases are done explicitly in LValue::storeValue, and only possible by assignment. solAssert( (targetType.isPointer() || (typeOnStack.isByteArray() && targetType.isByteArray())) && typeOnStack.location() == DataLocation::Storage, "Invalid conversion to storage type." ); break; case DataLocation::Memory: { // Copy the array to a free position in memory, unless it is already in memory. if (typeOnStack.location() != DataLocation::Memory) { // stack: (variably sized) unsigned stackSize = typeOnStack.sizeOnStack(); ArrayUtils(m_context).retrieveLength(typeOnStack); // allocate memory // stack: (variably sized) m_context << eth::Instruction::DUP1; ArrayUtils(m_context).convertLengthToSize(targetType, true); // stack: (variably sized) if (targetType.isDynamicallySized()) m_context << u256(0x20) << eth::Instruction::ADD; allocateMemory(); // stack: (variably sized) m_context << eth::Instruction::DUP1; moveIntoStack(2 + stackSize); if (targetType.isDynamicallySized()) { m_context << eth::Instruction::DUP2; storeInMemoryDynamic(IntegerType(256)); } // stack: (variably sized) if (targetType.baseType()->isValueType()) { solAssert(typeOnStack.baseType()->isValueType(), ""); copyToStackTop(2 + stackSize, stackSize); ArrayUtils(m_context).copyArrayToMemory(typeOnStack); } else { m_context << u256(0) << eth::Instruction::SWAP1; // stack: (variably sized) auto repeat = m_context.newTag(); m_context << repeat; m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; m_context << eth::Instruction::LT << eth::Instruction::ISZERO; auto loopEnd = m_context.appendConditionalJump(); copyToStackTop(3 + stackSize, stackSize); copyToStackTop(2 + stackSize, 1); ArrayUtils(m_context).accessIndex(typeOnStack, false); if (typeOnStack.location() == DataLocation::Storage) StorageItem(m_context, *typeOnStack.baseType()).retrieveValue(SourceLocation(), true); convertType(*typeOnStack.baseType(), *targetType.baseType(), _cleanupNeeded); storeInMemoryDynamic(*targetType.baseType(), true); m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD; m_context << eth::Instruction::SWAP1; m_context.appendJumpTo(repeat); m_context << loopEnd; m_context << eth::Instruction::POP; } // stack: (variably sized) popStackSlots(2 + stackSize); // Stack: } break; } case DataLocation::CallData: solAssert( targetType.isByteArray() && typeOnStack.isByteArray() && typeOnStack.location() == DataLocation::CallData, "Invalid conversion to calldata type."); break; default: solAssert( false, "Invalid type conversion " + _typeOnStack.toString(false) + " to " + _targetType.toString(false) + " requested." ); } break; } case Type::Category::Struct: { solAssert(targetTypeCategory == stackTypeCategory, ""); auto& targetType = dynamic_cast(_targetType); auto& typeOnStack = dynamic_cast(_typeOnStack); solAssert( targetType.location() != DataLocation::CallData && typeOnStack.location() != DataLocation::CallData , ""); switch (targetType.location()) { case DataLocation::Storage: // Other cases are done explicitly in LValue::storeValue, and only possible by assignment. solAssert( targetType.isPointer() && typeOnStack.location() == DataLocation::Storage, "Invalid conversion to storage type." ); break; case DataLocation::Memory: // Copy the array to a free position in memory, unless it is already in memory. if (typeOnStack.location() != DataLocation::Memory) { solAssert(typeOnStack.location() == DataLocation::Storage, ""); // stack: m_context << typeOnStack.memorySize(); allocateMemory(); m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; // stack: for (auto const& member: typeOnStack.members()) { if (!member.type->canLiveOutsideStorage()) continue; pair const& offsets = typeOnStack.storageOffsetsOfMember(member.name); m_context << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD; m_context << u256(offsets.second); StorageItem(m_context, *member.type).retrieveValue(SourceLocation(), true); TypePointer targetMemberType = targetType.memberType(member.name); solAssert(!!targetMemberType, "Member not found in target type."); convertType(*member.type, *targetMemberType, true); storeInMemoryDynamic(*targetMemberType, true); } m_context << eth::Instruction::POP << eth::Instruction::POP; } break; case DataLocation::CallData: solAssert(false, "Invalid type conversion target location CallData."); break; } break; } default: // All other types should not be convertible to non-equal types. solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); break; } } void CompilerUtils::pushZeroValue(const Type& _type) { auto const* referenceType = dynamic_cast(&_type); if (!referenceType || referenceType->location() == DataLocation::Storage) { for (size_t i = 0; i < _type.sizeOnStack(); ++i) m_context << u256(0); return; } solAssert(referenceType->location() == DataLocation::Memory, ""); m_context << u256(max(32u, _type.calldataEncodedSize())); allocateMemory(); m_context << eth::Instruction::DUP1; if (auto structType = dynamic_cast(&_type)) for (auto const& member: structType->members()) { pushZeroValue(*member.type); storeInMemoryDynamic(*member.type); } else if (auto arrayType = dynamic_cast(&_type)) { if (arrayType->isDynamicallySized()) { // zero length m_context << u256(0); storeInMemoryDynamic(IntegerType(256)); } else if (arrayType->length() > 0) { m_context << arrayType->length() << eth::Instruction::SWAP1; // stack: items_to_do memory_pos auto repeat = m_context.newTag(); m_context << repeat; pushZeroValue(*arrayType->baseType()); storeInMemoryDynamic(*arrayType->baseType()); m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::SWAP1; m_context << eth::Instruction::SUB << eth::Instruction::SWAP1; m_context << eth::Instruction::DUP2; m_context.appendConditionalJumpTo(repeat); m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; } } else solAssert(false, "Requested initialisation for unknown type: " + _type.toString()); // remove the updated memory pointer m_context << eth::Instruction::POP; } void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) { unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.baseStackOffsetOfVariable(_variable)); unsigned const size = _variable.annotation().type->sizeOnStack(); solAssert(stackPosition >= size, "Variable size and position mismatch."); // move variable starting from its top end in the stack if (stackPosition - size + 1 > 16) BOOST_THROW_EXCEPTION( CompilerError() << errinfo_sourceLocation(_variable.location()) << errinfo_comment("Stack too deep, try removing local variables.") ); for (unsigned i = 0; i < size; ++i) m_context << eth::swapInstruction(stackPosition - size + 1) << eth::Instruction::POP; } void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) { solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); for (unsigned i = 0; i < _itemSize; ++i) m_context << eth::dupInstruction(_stackDepth); } void CompilerUtils::moveToStackTop(unsigned _stackDepth) { solAssert(_stackDepth <= 15, "Stack too deep, try removing local variables."); for (unsigned i = 0; i < _stackDepth; ++i) 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.sizeOnStack()); } void CompilerUtils::popStackSlots(size_t _amount) { for (size_t i = 0; i < _amount; ++i) m_context << eth::Instruction::POP; } unsigned CompilerUtils::sizeOnStack(vector> const& _variableTypes) { unsigned size = 0; for (shared_ptr const& type: _variableTypes) size += type->sizeOnStack(); return size; } void CompilerUtils::computeHashStatic() { storeInMemory(0); m_context << u256(32) << u256(0) << eth::Instruction::SHA3; } void CompilerUtils::storeStringData(bytesConstRef _data) { //@todo provide both alternatives to the optimiser // stack: mempos if (_data.size() <= 128) { for (unsigned i = 0; i < _data.size(); i += 32) { m_context << h256::Arith(h256(_data.cropped(i), h256::AlignLeft)); storeInMemoryDynamic(IntegerType(256)); } m_context << eth::Instruction::POP; } else { // stack: mempos mempos_data m_context.appendData(_data.toBytes()); m_context << u256(_data.size()) << eth::Instruction::SWAP2; m_context << eth::Instruction::CODECOPY; } } unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries); bool leftAligned = _type.category() == Type::Category::FixedBytes; if (numBytes == 0) m_context << eth::Instruction::POP << u256(0); else { solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); m_context << (_fromCalldata ? eth::Instruction::CALLDATALOAD : eth::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 << eth::Instruction::SWAP1 << eth::Instruction::DIV; if (leftAligned) m_context << shiftFactor << eth::Instruction::MUL; } } return numBytes; } void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack) { if (_typeOnStack.numBits() == 256) return; else if (_typeOnStack.isSigned()) m_context << u256(_typeOnStack.numBits() / 8 - 1) << eth::Instruction::SIGNEXTEND; else m_context << ((u256(1) << _typeOnStack.numBits()) - 1) << eth::Instruction::AND; } unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const { unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries); bool leftAligned = _type.category() == Type::Category::FixedBytes; if (numBytes == 0) m_context << eth::Instruction::POP; else { solAssert(numBytes <= 32, "Memory store of more than 32 bytes requested."); if (numBytes != 32 && !leftAligned && !_padToWordBoundaries) // shift the value accordingly before storing m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL; } return numBytes; } } }