diff options
author | chriseth <c@ethdev.com> | 2015-10-21 06:21:52 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2015-10-21 06:46:01 +0800 |
commit | e3dffb611fe1736e3ffa170e6d8dc4dee17366bd (patch) | |
tree | b2df13e7c4c16c01b6cdc7cd5c15932031185d95 /libsolidity/codegen | |
parent | d41f8b7ce702c3b25c48d27e2e895ccdcd04e4e0 (diff) | |
download | dexon-solidity-e3dffb611fe1736e3ffa170e6d8dc4dee17366bd.tar.gz dexon-solidity-e3dffb611fe1736e3ffa170e6d8dc4dee17366bd.tar.zst dexon-solidity-e3dffb611fe1736e3ffa170e6d8dc4dee17366bd.zip |
File reorganisation.
Diffstat (limited to 'libsolidity/codegen')
-rw-r--r-- | libsolidity/codegen/ArrayUtils.cpp | 968 | ||||
-rw-r--r-- | libsolidity/codegen/ArrayUtils.h | 103 | ||||
-rw-r--r-- | libsolidity/codegen/Compiler.cpp | 778 | ||||
-rw-r--r-- | libsolidity/codegen/Compiler.h | 144 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerContext.cpp | 223 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerContext.h | 189 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.cpp | 802 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.h | 182 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 1370 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.h | 138 | ||||
-rw-r--r-- | libsolidity/codegen/LValue.cpp | 546 | ||||
-rw-r--r-- | libsolidity/codegen/LValue.h | 224 |
12 files changed, 5667 insertions, 0 deletions
diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp new file mode 100644 index 00000000..ba26caa6 --- /dev/null +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -0,0 +1,968 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * Code generation utils that handle arrays. + */ + +#include <libsolidity/codegen/ArrayUtils.h> +#include <libevmcore/Instruction.h> +#include <libsolidity/codegen/CompilerContext.h> +#include <libsolidity/codegen/CompilerUtils.h> +#include <libsolidity/ast/Types.h> +#include <libsolidity/interface/Utils.h> +#include <libsolidity/codegen/LValue.h> + +using namespace std; +using namespace dev; +using namespace solidity; + +void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const +{ + // this copies source to target and also clears target if it was larger + // need to leave "target_ref target_byte_off" on the stack at the end + + // stack layout: [source_ref] [source length] target_ref (top) + solAssert(_targetType.location() == DataLocation::Storage, ""); + + IntegerType uint256(256); + Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.baseType()); + Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.baseType()); + + // TODO unroll loop for small sizes + + bool sourceIsStorage = _sourceType.location() == DataLocation::Storage; + bool fromCalldata = _sourceType.location() == DataLocation::CallData; + bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType; + bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->storageBytes() <= 16; + bool haveByteOffsetTarget = !directCopy && targetBaseType->storageBytes() <= 16; + unsigned byteOffsetSize = (haveByteOffsetSource ? 1 : 0) + (haveByteOffsetTarget ? 1 : 0); + + // stack: source_ref [source_length] target_ref + // store target_ref + for (unsigned i = _sourceType.sizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); + // stack: target_ref source_ref [source_length] + // stack: target_ref source_ref [source_length] + // retrieve source length + if (_sourceType.location() != DataLocation::CallData || !_sourceType.isDynamicallySized()) + retrieveLength(_sourceType); // otherwise, length is already there + if (_sourceType.location() == DataLocation::Memory && _sourceType.isDynamicallySized()) + { + // increment source pointer to point to data + m_context << eth::Instruction::SWAP1 << u256(0x20); + m_context << eth::Instruction::ADD << eth::Instruction::SWAP1; + } + + // stack: target_ref source_ref source_length + m_context << eth::Instruction::DUP3; + // stack: target_ref source_ref source_length target_ref + retrieveLength(_targetType); + // stack: target_ref source_ref source_length target_ref target_length + if (_targetType.isDynamicallySized()) + // store new target length + if (!_targetType.isByteArray()) + // Otherwise, length will be stored below. + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3 << eth::Instruction::SSTORE; + if (sourceBaseType->category() == Type::Category::Mapping) + { + solAssert(targetBaseType->category() == Type::Category::Mapping, ""); + solAssert(_sourceType.location() == DataLocation::Storage, ""); + // nothing to copy + m_context + << eth::Instruction::POP << eth::Instruction::POP + << eth::Instruction::POP << eth::Instruction::POP; + return; + } + // stack: target_ref source_ref source_length target_ref target_length + // compute hashes (data positions) + m_context << eth::Instruction::SWAP1; + if (_targetType.isDynamicallySized()) + CompilerUtils(m_context).computeHashStatic(); + // stack: target_ref source_ref source_length target_length target_data_pos + m_context << eth::Instruction::SWAP1; + convertLengthToSize(_targetType); + m_context << eth::Instruction::DUP2 << eth::Instruction::ADD; + // stack: target_ref source_ref source_length target_data_pos target_data_end + m_context << eth::Instruction::SWAP3; + // stack: target_ref target_data_end source_length target_data_pos source_ref + + eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); + + // special case for short byte arrays: Store them together with their length. + if (_targetType.isByteArray()) + { + // stack: target_ref target_data_end source_length target_data_pos source_ref + m_context << eth::Instruction::DUP3 << u256(31) << eth::Instruction::LT; + eth::AssemblyItem longByteArray = m_context.appendConditionalJump(); + // store the short byte array + solAssert(_sourceType.isByteArray(), ""); + if (_sourceType.location() == DataLocation::Storage) + { + // just copy the slot, it contains length and data + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + m_context << eth::Instruction::DUP6 << eth::Instruction::SSTORE; + } + else + { + m_context << eth::Instruction::DUP1; + CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); + // stack: target_ref target_data_end source_length target_data_pos source_ref value + // clear the lower-order byte - which will hold the length + m_context << u256(0xff) << eth::Instruction::NOT << eth::Instruction::AND; + // fetch the length and shift it left by one + m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::ADD; + // combine value and length and store them + m_context << eth::Instruction::OR << eth::Instruction::DUP6 << eth::Instruction::SSTORE; + } + // end of special case, jump right into cleaning target data area + m_context.appendJumpTo(copyLoopEndWithoutByteOffset); + m_context << longByteArray; + // Store length (2*length+1) + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP1 << eth::Instruction::ADD; + m_context << u256(1) << eth::Instruction::ADD; + m_context << eth::Instruction::DUP6 << eth::Instruction::SSTORE; + } + + // skip copying if source length is zero + m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); + + if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) + CompilerUtils(m_context).computeHashStatic(); + // stack: target_ref target_data_end source_length target_data_pos source_data_pos + m_context << eth::Instruction::SWAP2; + convertLengthToSize(_sourceType); + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + if (haveByteOffsetTarget) + m_context << u256(0); + if (haveByteOffsetSource) + m_context << u256(0); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + eth::AssemblyItem copyLoopStart = m_context.newTag(); + m_context << copyLoopStart; + // check for loop condition + m_context + << eth::dupInstruction(3 + byteOffsetSize) << eth::dupInstruction(2 + byteOffsetSize) + << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.appendConditionalJump(); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + // copy + if (sourceBaseType->category() == Type::Category::Array) + { + solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); + auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType); + m_context << eth::Instruction::DUP3; + if (sourceBaseArrayType.location() == DataLocation::Memory) + m_context << eth::Instruction::MLOAD; + m_context << eth::Instruction::DUP3; + copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType); + m_context << eth::Instruction::POP; + } + else if (directCopy) + { + solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); + m_context + << eth::Instruction::DUP3 << eth::Instruction::SLOAD + << eth::Instruction::DUP3 << eth::Instruction::SSTORE; + } + else + { + // Note that we have to copy each element on its own in case conversion is involved. + // We might copy too much if there is padding at the last element, but this way end + // checking is easier. + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + m_context << eth::dupInstruction(3 + byteOffsetSize); + if (_sourceType.location() == DataLocation::Storage) + { + if (haveByteOffsetSource) + m_context << eth::Instruction::DUP2; + else + m_context << u256(0); + StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + } + else if (sourceBaseType->isValueType()) + CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); + else + solAssert(false, "Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>... + solAssert( + 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, + "Stack too deep, try removing local variables." + ); + // fetch target storage reference + m_context << eth::dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); + if (haveByteOffsetTarget) + m_context << eth::dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); + else + m_context << u256(0); + StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); + } + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + // increment source + if (haveByteOffsetSource) + incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); + else + { + m_context << eth::swapInstruction(2 + byteOffsetSize); + if (sourceIsStorage) + m_context << sourceBaseType->storageSize(); + else if (_sourceType.location() == DataLocation::Memory) + m_context << sourceBaseType->memoryHeadSize(); + else + m_context << sourceBaseType->calldataEncodedSize(true); + m_context + << eth::Instruction::ADD + << eth::swapInstruction(2 + byteOffsetSize); + } + // increment target + if (haveByteOffsetTarget) + incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); + else + m_context + << eth::swapInstruction(1 + byteOffsetSize) + << targetBaseType->storageSize() + << eth::Instruction::ADD + << eth::swapInstruction(1 + byteOffsetSize); + m_context.appendJumpTo(copyLoopStart); + m_context << copyLoopEnd; + if (haveByteOffsetTarget) + { + // clear elements that might be left over in the current slot in target + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] + m_context << eth::dupInstruction(byteOffsetSize) << eth::Instruction::ISZERO; + eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump(); + m_context << eth::dupInstruction(2 + byteOffsetSize) << eth::dupInstruction(1 + byteOffsetSize); + StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true); + incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); + m_context.appendJumpTo(copyLoopEnd); + + m_context << copyCleanupLoopEnd; + m_context << eth::Instruction::POP; // might pop the source, but then target is popped next + } + if (haveByteOffsetSource) + m_context << eth::Instruction::POP; + m_context << copyLoopEndWithoutByteOffset; + + // zero-out leftovers in target + // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end + m_context << eth::Instruction::POP << eth::Instruction::SWAP1 << eth::Instruction::POP; + // stack: target_ref target_data_end target_data_pos_updated + clearStorageLoop(*targetBaseType); + m_context << eth::Instruction::POP; +} + +void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const +{ + solAssert( + _sourceType.baseType()->calldataEncodedSize() > 0, + "Nested dynamic arrays not implemented here." + ); + CompilerUtils utils(m_context); + unsigned baseSize = 1; + if (!_sourceType.isByteArray()) + // We always pad the elements, regardless of _padToWordBoundaries. + baseSize = _sourceType.baseType()->calldataEncodedSize(); + + if (_sourceType.location() == DataLocation::CallData) + { + if (!_sourceType.isDynamicallySized()) + m_context << _sourceType.length(); + if (baseSize > 1) + m_context << u256(baseSize) << eth::Instruction::MUL; + // stack: target source_offset source_len + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5; + // stack: target source_offset source_len source_len source_offset target + 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 (_sourceType.location() == DataLocation::Memory) + { + retrieveLength(_sourceType); + // stack: target source length + if (!_sourceType.baseType()->isValueType()) + { + // copy using a loop + m_context << u256(0) << eth::Instruction::SWAP3; + // stack: counter source length target + auto repeat = m_context.newTag(); + m_context << repeat; + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP5; + m_context << eth::Instruction::LT << eth::Instruction::ISZERO; + auto loopEnd = m_context.appendConditionalJump(); + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP5; + accessIndex(_sourceType, false); + MemoryItem(m_context, *_sourceType.baseType(), true).retrieveValue(SourceLocation(), true); + if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType().get())) + copyArrayToMemory(*baseArray, _padToWordBoundaries); + else + utils.storeInMemoryDynamic(*_sourceType.baseType()); + m_context << eth::Instruction::SWAP3 << u256(1) << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP3; + m_context.appendJumpTo(repeat); + m_context << loopEnd; + m_context << eth::Instruction::SWAP3; + utils.popStackSlots(3); + // stack: updated_target_pos + return; + } + + // memcpy using the built-in contract + if (_sourceType.isDynamicallySized()) + { + // change pointer to data part + m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP1; + } + // convert length to size + if (baseSize > 1) + m_context << u256(baseSize) << eth::Instruction::MUL; + // stack: <target> <source> <size> + //@TODO do not use ::CALL if less than 32 bytes? + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::DUP4; + utils.memoryCopy(); + + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + // stack: <target> <size> + + bool paddingNeeded = false; + if (_sourceType.isDynamicallySized()) + paddingNeeded = _padToWordBoundaries && ((baseSize % 32) != 0); + else + paddingNeeded = _padToWordBoundaries && (((_sourceType.length() * baseSize) % 32) != 0); + if (paddingNeeded) + { + // stack: <target> <size> + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; + // stack: <length> <target + size> + m_context << eth::Instruction::SWAP1 << u256(31) << eth::Instruction::AND; + // stack: <target + size> <remainder = size % 32> + eth::AssemblyItem skip = m_context.newTag(); + if (_sourceType.isDynamicallySized()) + { + m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(skip); + } + // round off, load from there. + // stack <target + size> <remainder = size % 32> + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3; + m_context << eth::Instruction::SUB; + // stack: target+size remainder <target + size - 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: ...<v> 1 <32 - remainder> + m_context << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SUB; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: target+size remainder target+size-remainder <v & ...> + m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; + // stack: target+size remainder target+size-remainder + m_context << u256(32) << eth::Instruction::ADD; + // stack: target+size remainder <new_padded_end> + m_context << eth::Instruction::SWAP2 << eth::Instruction::POP; + + if (_sourceType.isDynamicallySized()) + m_context << skip.tag(); + // stack <target + "size"> <remainder = size % 32> + m_context << eth::Instruction::POP; + } + else + // stack: <target> <size> + m_context << eth::Instruction::ADD; + } + else + { + solAssert(_sourceType.location() == DataLocation::Storage, ""); + unsigned storageBytes = _sourceType.baseType()->storageBytes(); + u256 storageSize = _sourceType.baseType()->storageSize(); + solAssert(storageSize > 1 || (storageSize == 1 && storageBytes > 0), ""); + + retrieveLength(_sourceType); + // stack here: memory_offset storage_offset length + // jump to end if length is zero + m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; + eth::AssemblyItem loopEnd = m_context.appendConditionalJump(); + // Special case for tightly-stored byte arrays + if (_sourceType.isByteArray()) + { + // stack here: memory_offset storage_offset length + m_context << eth::Instruction::DUP1 << u256(31) << eth::Instruction::LT; + eth::AssemblyItem longByteArray = m_context.appendConditionalJump(); + // store the short byte array (discard lower-order byte) + m_context << u256(0x100) << eth::Instruction::DUP1; + m_context << eth::Instruction::DUP4 << eth::Instruction::SLOAD; + m_context << eth::Instruction::DIV << eth::Instruction::MUL; + m_context << eth::Instruction::DUP4 << eth::Instruction::MSTORE; + // stack here: memory_offset storage_offset length + // add 32 or length to memory offset + m_context << eth::Instruction::SWAP2; + if (_padToWordBoundaries) + m_context << u256(32); + else + m_context << eth::Instruction::DUP3; + m_context << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP2; + m_context.appendJumpTo(loopEnd); + m_context << longByteArray; + } + // compute memory end offset + if (baseSize > 1) + // convert length to memory size + m_context << u256(baseSize) << eth::Instruction::MUL; + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD << eth::Instruction::SWAP2; + if (_sourceType.isDynamicallySized()) + { + // actual array data is stored at SHA3(storage_offset) + m_context << eth::Instruction::SWAP1; + utils.computeHashStatic(); + m_context << eth::Instruction::SWAP1; + } + + // stack here: memory_end_offset storage_data_offset memory_offset + bool haveByteOffset = !_sourceType.isByteArray() && storageBytes <= 16; + if (haveByteOffset) + m_context << u256(0) << eth::Instruction::SWAP1; + // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset + eth::AssemblyItem loopStart = m_context.newTag(); + m_context << loopStart; + // load and store + if (_sourceType.isByteArray()) + { + // Packed both in storage and memory. + 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; + } + else + { + // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset + if (haveByteOffset) + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; + else + m_context << eth::Instruction::DUP2 << u256(0); + StorageItem(m_context, *_sourceType.baseType()).retrieveValue(SourceLocation(), true); + if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType().get())) + copyArrayToMemory(*baseArray, _padToWordBoundaries); + else + utils.storeInMemoryDynamic(*_sourceType.baseType()); + // increment storage_data_offset and byte offset + if (haveByteOffset) + incrementByteOffset(storageBytes, 2, 3); + else + { + m_context << eth::Instruction::SWAP1; + m_context << storageSize << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP1; + } + } + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::dupInstruction(haveByteOffset ? 5 : 4); + m_context << eth::Instruction::GT; + m_context.appendConditionalJumpTo(loopStart); + // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset + if (haveByteOffset) + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + if (_padToWordBoundaries && baseSize % 32 != 0) + { + // 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; + } +} + +void ArrayUtils::clearArray(ArrayType const& _type) const +{ + unsigned stackHeightStart = m_context.stackHeight(); + solAssert(_type.location() == DataLocation::Storage, ""); + if (_type.baseType()->storageBytes() < 32) + { + solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); + solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); + } + if (_type.baseType()->isValueType()) + solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); + + m_context << eth::Instruction::POP; // remove byte offset + if (_type.isDynamicallySized()) + clearDynamicArray(_type); + else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) + m_context << eth::Instruction::POP; + else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) + { + // unroll loop for small arrays @todo choose a good value + // Note that we loop over storage slots here, not elements. + for (unsigned i = 1; i < _type.storageSize(); ++i) + m_context + << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::ADD; + m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } + else if (!_type.baseType()->isValueType() && _type.length() <= 4) + { + // unroll loop for small arrays @todo choose a good value + solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); + for (unsigned i = 1; i < _type.length(); ++i) + { + m_context << u256(0); + StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false); + m_context + << eth::Instruction::POP + << u256(_type.baseType()->storageSize()) << eth::Instruction::ADD; + } + m_context << u256(0); + StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); + } + else + { + m_context << eth::Instruction::DUP1 << _type.length(); + convertLengthToSize(_type); + m_context << eth::Instruction::ADD << eth::Instruction::SWAP1; + if (_type.baseType()->storageBytes() < 32) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.baseType()); + m_context << eth::Instruction::POP; + } + solAssert(m_context.stackHeight() == stackHeightStart - 2, ""); +} + +void ArrayUtils::clearDynamicArray(ArrayType const& _type) const +{ + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + + // fetch length + retrieveLength(_type); + // set length to zero + m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE; + // Special case: short byte arrays are stored togeher with their length + eth::AssemblyItem endTag = m_context.newTag(); + if (_type.isByteArray()) + { + // stack: ref old_length + m_context << eth::Instruction::DUP1 << u256(31) << eth::Instruction::LT; + eth::AssemblyItem longByteArray = m_context.appendConditionalJump(); + m_context << eth::Instruction::POP; + m_context.appendJumpTo(endTag); + m_context.adjustStackOffset(1); // needed because of jump + m_context << longByteArray; + } + // stack: ref old_length + convertLengthToSize(_type); + // compute data positions + m_context << eth::Instruction::SWAP1; + CompilerUtils(m_context).computeHashStatic(); + // stack: len data_pos + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD + << eth::Instruction::SWAP1; + // stack: data_pos_end data_pos + if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.baseType()); + // cleanup + m_context << endTag; + m_context << eth::Instruction::POP; +} + +void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const +{ + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) + solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); + + unsigned stackHeightStart = m_context.stackHeight(); + eth::AssemblyItem resizeEnd = m_context.newTag(); + + // stack: ref new_length + // fetch old length + retrieveLength(_type, 1); + // stack: ref new_length old_length + solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "2"); + + // Special case for short byte arrays, they are stored together with their length + if (_type.isByteArray()) + { + eth::AssemblyItem regularPath = m_context.newTag(); + // We start by a large case-distinction about the old and new length of the byte array. + + m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD; + // stack: ref new_length current_length ref_value + + solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + m_context << eth::Instruction::DUP2 << u256(31) << eth::Instruction::LT; + eth::AssemblyItem currentIsLong = m_context.appendConditionalJump(); + m_context << eth::Instruction::DUP3 << u256(31) << eth::Instruction::LT; + eth::AssemblyItem newIsLong = m_context.appendConditionalJump(); + + // Here: short -> short + + // Compute 1 << (256 - 8 * new_size) + eth::AssemblyItem shortToShort = m_context.newTag(); + m_context << shortToShort; + m_context << eth::Instruction::DUP3 << u256(8) << eth::Instruction::MUL; + m_context << u256(0x100) << eth::Instruction::SUB; + m_context << u256(2) << eth::Instruction::EXP; + // Divide and multiply by that value, clearing bits. + m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2; + m_context << eth::Instruction::DIV << eth::Instruction::MUL; + // Insert 2*length. + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP1 << eth::Instruction::ADD; + m_context << eth::Instruction::OR; + // Store. + m_context << eth::Instruction::DUP4 << eth::Instruction::SSTORE; + solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); + m_context.appendJumpTo(resizeEnd); + + m_context.adjustStackOffset(1); // we have to do that because of the jumps + // Here: short -> long + + m_context << newIsLong; + // stack: ref new_length current_length ref_value + solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + // Zero out lower-order byte. + m_context << u256(0xff) << eth::Instruction::NOT << eth::Instruction::AND; + // Store at data location. + m_context << eth::Instruction::DUP4; + CompilerUtils(m_context).computeHashStatic(); + m_context << eth::Instruction::SSTORE; + // stack: ref new_length current_length + // Store new length: Compule 2*length + 1 and store it. + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP1 << eth::Instruction::ADD; + m_context << u256(1) << eth::Instruction::ADD; + // stack: ref new_length current_length 2*new_length+1 + m_context << eth::Instruction::DUP4 << eth::Instruction::SSTORE; + solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); + m_context.appendJumpTo(resizeEnd); + + m_context.adjustStackOffset(1); // we have to do that because of the jumps + + m_context << currentIsLong; + m_context << eth::Instruction::DUP3 << u256(31) << eth::Instruction::LT; + m_context.appendConditionalJumpTo(regularPath); + + // Here: long -> short + // Read the first word of the data and store it on the stack. Clear the data location and + // then jump to the short -> short case. + + // stack: ref new_length current_length ref_value + solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + m_context << eth::Instruction::POP << eth::Instruction::DUP3; + CompilerUtils(m_context).computeHashStatic(); + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; + // stack: ref new_length current_length first_word data_location + m_context << eth::Instruction::DUP3; + convertLengthToSize(_type); + m_context << eth::Instruction::DUP2 << eth::Instruction::ADD << eth::Instruction::SWAP1; + // stack: ref new_length current_length first_word data_location_end data_location + clearStorageLoop(IntegerType(256)); + m_context << eth::Instruction::POP; + // stack: ref new_length current_length first_word + solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + m_context.appendJumpTo(shortToShort); + + m_context << regularPath; + // stack: ref new_length current_length ref_value + m_context << eth::Instruction::POP; + } + + // Change of length for a regular array (i.e. length at location, data at sha3(location)). + // stack: ref new_length old_length + // store new length + m_context << eth::Instruction::DUP2; + if (_type.isByteArray()) + // For a "long" byte array, store length as 2*length+1 + m_context << eth::Instruction::DUP1 << eth::Instruction::ADD << u256(1) << eth::Instruction::ADD; + m_context<< eth::Instruction::DUP4 << eth::Instruction::SSTORE; + // skip if size is not reduced + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP2 + << eth::Instruction::ISZERO << eth::Instruction::GT; + m_context.appendConditionalJumpTo(resizeEnd); + + // size reduced, clear the end of the array + // stack: ref new_length old_length + convertLengthToSize(_type); + m_context << eth::Instruction::DUP2; + convertLengthToSize(_type); + // stack: ref new_length old_size new_size + // compute data positions + m_context << eth::Instruction::DUP4; + CompilerUtils(m_context).computeHashStatic(); + // stack: ref new_length old_size new_size data_pos + m_context << eth::Instruction::SWAP2 << eth::Instruction::DUP3 << eth::Instruction::ADD; + // stack: ref new_length data_pos new_size delete_end + m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD; + // stack: ref new_length delete_end delete_start + if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.baseType()); + + m_context << resizeEnd; + // cleanup + m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + solAssert(m_context.stackHeight() == stackHeightStart - 2, ""); +} + +void ArrayUtils::clearStorageLoop(Type const& _type) const +{ + unsigned stackHeightStart = m_context.stackHeight(); + if (_type.category() == Type::Category::Mapping) + { + m_context << eth::Instruction::POP; + return; + } + // stack: end_pos pos + + // jump to and return from the loop to allow for duplicate code removal + eth::AssemblyItem returnTag = m_context.pushNewTag(); + m_context << eth::Instruction::SWAP2 << eth::Instruction::SWAP1; + + // stack: <return tag> end_pos pos + eth::AssemblyItem loopStart = m_context.appendJumpToNew(); + m_context << loopStart; + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 + << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem zeroLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(zeroLoopEnd); + // delete + m_context << u256(0); + StorageItem(m_context, _type).setToZero(SourceLocation(), false); + m_context << eth::Instruction::POP; + // increment + m_context << u256(1) << eth::Instruction::ADD; + m_context.appendJumpTo(loopStart); + // cleanup + m_context << zeroLoopEnd; + m_context << eth::Instruction::POP << eth::Instruction::SWAP1; + // "return" + m_context << eth::Instruction::JUMP; + + m_context << returnTag; + solAssert(m_context.stackHeight() == stackHeightStart - 1, ""); +} + +void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const +{ + if (_arrayType.location() == DataLocation::Storage) + { + if (_arrayType.baseType()->storageSize() <= 1) + { + unsigned baseBytes = _arrayType.baseType()->storageBytes(); + if (baseBytes == 0) + m_context << eth::Instruction::POP << u256(1); + else if (baseBytes <= 16) + { + unsigned itemsPerSlot = 32 / baseBytes; + m_context + << u256(itemsPerSlot - 1) << eth::Instruction::ADD + << u256(itemsPerSlot) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + } + } + else + m_context << _arrayType.baseType()->storageSize() << eth::Instruction::MUL; + } + else + { + if (!_arrayType.isByteArray()) + { + if (_arrayType.location() == DataLocation::Memory) + m_context << _arrayType.baseType()->memoryHeadSize(); + else + m_context << _arrayType.baseType()->calldataEncodedSize(); + m_context << eth::Instruction::MUL; + } + else if (_pad) + m_context << u256(31) << eth::Instruction::ADD + << u256(32) << eth::Instruction::DUP1 + << eth::Instruction::SWAP2 << eth::Instruction::DIV << eth::Instruction::MUL; + } +} + +void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDepth) const +{ + if (!_arrayType.isDynamicallySized()) + m_context << _arrayType.length(); + else + { + m_context << eth::dupInstruction(1 + _stackDepth); + switch (_arrayType.location()) + { + case DataLocation::CallData: + // length is stored on the stack + break; + case DataLocation::Memory: + m_context << eth::Instruction::MLOAD; + break; + case DataLocation::Storage: + m_context << eth::Instruction::SLOAD; + if (_arrayType.isByteArray()) + { + // Retrieve length both for in-place strings and off-place strings: + // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 + // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it + // computes (x & (-1)) / 2, which is equivalent to just x / 2. + m_context << u256(1) << eth::Instruction::DUP2 << u256(1) << eth::Instruction::AND; + m_context << eth::Instruction::ISZERO << u256(0x100) << eth::Instruction::MUL; + m_context << eth::Instruction::SUB << eth::Instruction::AND; + m_context << u256(2) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + } + break; + } + } +} + +void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) const +{ + /// Stack: reference [length] index + DataLocation location = _arrayType.location(); + + if (_doBoundsCheck) + { + // retrieve length + ArrayUtils::retrieveLength(_arrayType, 1); + // Stack: ref [length] index length + // check out-of-bounds access + m_context << eth::Instruction::DUP2 << eth::Instruction::LT << eth::Instruction::ISZERO; + // out-of-bounds access throws exception + m_context.appendConditionalJumpTo(m_context.errorTag()); + } + if (location == DataLocation::CallData && _arrayType.isDynamicallySized()) + // remove length if present + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + + // stack: <base_ref> <index> + m_context << eth::Instruction::SWAP1; + // stack: <index> <base_ref> + switch (location) + { + case DataLocation::Memory: + if (_arrayType.isDynamicallySized()) + m_context << u256(32) << eth::Instruction::ADD; + // fall-through + case DataLocation::CallData: + if (!_arrayType.isByteArray()) + { + m_context << eth::Instruction::SWAP1; + if (location == DataLocation::CallData) + m_context << _arrayType.baseType()->calldataEncodedSize(); + else + m_context << u256(_arrayType.memoryHeadSize()); + m_context << eth::Instruction::MUL; + } + m_context << eth::Instruction::ADD; + break; + case DataLocation::Storage: + { + eth::AssemblyItem endTag = m_context.newTag(); + if (_arrayType.isByteArray()) + { + // Special case of short byte arrays. + m_context << eth::Instruction::SWAP1; + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + m_context << u256(1) << eth::Instruction::AND << eth::Instruction::ISZERO; + // No action needed for short byte arrays. + m_context.appendConditionalJumpTo(endTag); + m_context << eth::Instruction::SWAP1; + } + if (_arrayType.isDynamicallySized()) + CompilerUtils(m_context).computeHashStatic(); + m_context << eth::Instruction::SWAP1; + if (_arrayType.baseType()->storageBytes() <= 16) + { + // stack: <data_ref> <index> + // goal: + // <ref> <byte_number> = <base_ref + index / itemsPerSlot> <(index % itemsPerSlot) * byteSize> + unsigned byteSize = _arrayType.baseType()->storageBytes(); + solAssert(byteSize != 0, ""); + unsigned itemsPerSlot = 32 / byteSize; + m_context << u256(itemsPerSlot) << eth::Instruction::SWAP2; + // stack: itemsPerSlot index data_ref + m_context + << eth::Instruction::DUP3 << eth::Instruction::DUP3 + << eth::Instruction::DIV << eth::Instruction::ADD + // stack: itemsPerSlot index (data_ref + index / itemsPerSlot) + << eth::Instruction::SWAP2 << eth::Instruction::SWAP1 + << eth::Instruction::MOD; + if (byteSize != 1) + m_context << u256(byteSize) << eth::Instruction::MUL; + } + else + { + if (_arrayType.baseType()->storageSize() != 1) + m_context << _arrayType.baseType()->storageSize() << eth::Instruction::MUL; + m_context << eth::Instruction::ADD << u256(0); + } + m_context << endTag; + break; + } + default: + solAssert(false, ""); + } +} + +void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const +{ + solAssert(_byteSize < 32, ""); + solAssert(_byteSize != 0, ""); + // We do the following, but avoiding jumps: + // byteOffset += byteSize + // if (byteOffset + byteSize > 32) + // { + // storageOffset++; + // byteOffset = 0; + // } + if (_byteOffsetPosition > 1) + m_context << eth::swapInstruction(_byteOffsetPosition - 1); + m_context << u256(_byteSize) << eth::Instruction::ADD; + if (_byteOffsetPosition > 1) + m_context << eth::swapInstruction(_byteOffsetPosition - 1); + // compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32 + m_context + << u256(32) << eth::dupInstruction(1 + _byteOffsetPosition) << u256(_byteSize - 1) + << eth::Instruction::ADD << eth::Instruction::DIV; + // increment storage offset if X == 1 (just add X to it) + // stack: X + m_context + << eth::swapInstruction(_storageOffsetPosition) << eth::dupInstruction(_storageOffsetPosition + 1) + << eth::Instruction::ADD << eth::swapInstruction(_storageOffsetPosition); + // stack: X + // set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X) + m_context << u256(1) << eth::Instruction::SUB; + // stack: 1 - X + if (_byteOffsetPosition == 1) + m_context << eth::Instruction::MUL; + else + m_context + << eth::dupInstruction(_byteOffsetPosition + 1) << eth::Instruction::MUL + << eth::swapInstruction(_byteOffsetPosition) << eth::Instruction::POP; +} diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h new file mode 100644 index 00000000..53d36c14 --- /dev/null +++ b/libsolidity/codegen/ArrayUtils.h @@ -0,0 +1,103 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * Code generation utils that handle arrays. + */ + +#pragma once + +namespace dev +{ +namespace solidity +{ + +class CompilerContext; +class Type; +class ArrayType; + +/** + * Class that provides code generation for handling arrays. + */ +class ArrayUtils +{ +public: + ArrayUtils(CompilerContext& _context): m_context(_context) {} + + /// Copies an array to an array in storage. The arrays can be of different types only if + /// their storage representation is the same. + /// Stack pre: source_reference [source_length] target_reference + /// Stack post: target_reference + void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; + /// Copies the data part of an array (which cannot be dynamically nested) from anywhere + /// to a given position in memory. + /// This always copies contained data as is (i.e. structs and fixed-size arrays are copied in + /// place as required by the ABI encoding). Use CompilerUtils::convertType if you want real + /// memory copies of nested arrays. + /// Stack pre: memory_offset source_item + /// Stack post: memory_offest + length(padded) + void copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries = true) const; + /// Clears the given dynamic or static array. + /// Stack pre: storage_ref storage_byte_offset + /// Stack post: + void clearArray(ArrayType const& _type) const; + /// Clears the length and data elements of the array referenced on the stack. + /// Stack pre: reference (excludes byte offset) + /// Stack post: + void clearDynamicArray(ArrayType const& _type) const; + /// Changes the size of a dynamic array and clears the tail if it is shortened. + /// Stack pre: reference (excludes byte offset) new_length + /// Stack post: + void resizeDynamicArray(ArrayType const& _type) const; + /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). + /// Stack pre: end_ref start_ref + /// Stack post: end_ref + void clearStorageLoop(Type const& _type) const; + /// Converts length to size (number of storage slots or calldata/memory bytes). + /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory. + /// Stack pre: length + /// Stack post: size + void convertLengthToSize(ArrayType const& _arrayType, bool _pad = false) const; + /// Retrieves the length (number of elements) of the array ref on the stack. This also + /// works for statically-sized arrays. + /// @param _stackDepth number of stack elements between top of stack and top (!) of reference + /// Stack pre: reference (excludes byte offset for dynamic storage arrays) + /// Stack post: reference length + void retrieveLength(ArrayType const& _arrayType, unsigned _stackDepth = 0) const; + /// Stores the length of an array of type @a _arrayType in storage. The length itself is stored + /// on the stack at position @a _stackDepthLength and the storage reference at @a _stackDepthRef. + /// If @a _arrayType is a byte array, takes tight coding into account. + void storeLength(ArrayType const& _arrayType, unsigned _stackDepthLength = 0, unsigned _stackDepthRef = 1) const; + /// Performs bounds checking and returns a reference on the stack. + /// Stack pre: reference [length] index + /// Stack post (storage): storage_slot byte_offset + /// Stack post: memory/calldata_offset + void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true) const; + +private: + /// Adds the given number of bytes to a storage byte offset counter and also increments + /// the storage offset if adding this number again would increase the counter over 32. + /// @param byteOffsetPosition the stack offset of the storage byte offset + /// @param storageOffsetPosition the stack offset of the storage slot offset + void incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const; + + CompilerContext& m_context; +}; + +} +} diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp new file mode 100644 index 00000000..457b1e02 --- /dev/null +++ b/libsolidity/codegen/Compiler.cpp @@ -0,0 +1,778 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Solidity compiler. + */ + +#include <libsolidity/codegen/Compiler.h> +#include <algorithm> +#include <boost/range/adaptor/reversed.hpp> +#include <libevmcore/Instruction.h> +#include <libevmasm/Assembly.h> +#include <libevmcore/Params.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/codegen/ExpressionCompiler.h> +#include <libsolidity/codegen/CompilerUtils.h> + +using namespace std; +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. + */ +class StackHeightChecker +{ +public: + StackHeightChecker(CompilerContext const& _context): + m_context(_context), stackHeight(m_context.stackHeight()) {} + void check() { solAssert(m_context.stackHeight() == stackHeight, "I sense a disturbance in the stack."); } +private: + CompilerContext const& m_context; + unsigned stackHeight; +}; + +void Compiler::compileContract( + ContractDefinition const& _contract, + std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts +) +{ + m_context = CompilerContext(); + { + CompilerContext::LocationSetter locationSetterRunTime(m_context, _contract); + initializeContext(_contract, _contracts); + appendFunctionSelector(_contract); + appendFunctionsWithoutCode(); + } + + // Swap the runtime context with the creation-time context + swap(m_context, m_runtimeContext); + CompilerContext::LocationSetter locationSetterCreationTime(m_context, _contract); + initializeContext(_contract, _contracts); + packIntoContractCreator(_contract, m_runtimeContext); + if (m_optimize) + m_context.optimise(m_optimizeRuns); + + if (_contract.isLibrary()) + { + solAssert(m_runtimeSub != size_t(-1), ""); + m_context.injectVersionStampIntoSub(m_runtimeSub); + } +} + +void Compiler::compileClone( + ContractDefinition const& _contract, + map<ContractDefinition const*, eth::Assembly const*> const& _contracts +) +{ + m_context = CompilerContext(); // clear it just in case + initializeContext(_contract, _contracts); + + appendInitAndConstructorCode(_contract); + + //@todo determine largest return size of all runtime functions + eth::AssemblyItem runtimeSub = m_context.addSubroutine(cloneRuntime()); + solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), ""); + m_runtimeSub = size_t(runtimeSub.data()); + + // stack contains sub size + m_context << eth::Instruction::DUP1 << runtimeSub << u256(0) << eth::Instruction::CODECOPY; + m_context << u256(0) << eth::Instruction::RETURN; + + appendFunctionsWithoutCode(); + + if (m_optimize) + m_context.optimise(m_optimizeRuns); +} + +eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const +{ + return m_runtimeContext.functionEntryLabelIfExists(_function); +} + +void Compiler::initializeContext( + ContractDefinition const& _contract, + map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts +) +{ + m_context.setCompiledContracts(_compiledContracts); + m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); + CompilerUtils(m_context).initialiseFreeMemoryPointer(); + registerStateVariables(_contract); + m_context.resetVisitedNodes(&_contract); +} + +void Compiler::appendInitAndConstructorCode(ContractDefinition const& _contract) +{ + // Determine the arguments that are used for the base constructors. + std::vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts; + for (ContractDefinition const* contract: bases) + { + if (FunctionDefinition const* constructor = contract->constructor()) + for (auto const& modifier: constructor->modifiers()) + { + auto baseContract = dynamic_cast<ContractDefinition const*>( + modifier->name()->annotation().referencedDeclaration); + if (baseContract) + if (m_baseArguments.count(baseContract->constructor()) == 0) + m_baseArguments[baseContract->constructor()] = &modifier->arguments(); + } + + for (ASTPointer<InheritanceSpecifier> const& base: contract->baseContracts()) + { + ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>( + base->name().annotation().referencedDeclaration + ); + solAssert(baseContract, ""); + + if (m_baseArguments.count(baseContract->constructor()) == 0) + m_baseArguments[baseContract->constructor()] = &base->arguments(); + } + } + // Initialization of state variables in base-to-derived order. + for (ContractDefinition const* contract: boost::adaptors::reverse(bases)) + initializeStateVariables(*contract); + + if (FunctionDefinition const* constructor = _contract.constructor()) + appendConstructor(*constructor); + else if (auto c = m_context.nextConstructor(_contract)) + appendBaseConstructor(*c); +} + +void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) +{ + appendInitAndConstructorCode(_contract); + + eth::AssemblyItem runtimeSub = m_context.addSubroutine(_runtimeContext.assembly()); + solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), ""); + m_runtimeSub = size_t(runtimeSub.data()); + + // stack contains sub size + m_context << eth::Instruction::DUP1 << runtimeSub << u256(0) << eth::Instruction::CODECOPY; + m_context << u256(0) << eth::Instruction::RETURN; + + // note that we have to include the functions again because of absolute jump labels + appendFunctionsWithoutCode(); +} + +void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) +{ + CompilerContext::LocationSetter locationSetter(m_context, _constructor); + FunctionType constructorType(_constructor); + if (!constructorType.parameterTypes().empty()) + { + solAssert(m_baseArguments.count(&_constructor), ""); + std::vector<ASTPointer<Expression>> const* arguments = m_baseArguments[&_constructor]; + solAssert(arguments, ""); + for (unsigned i = 0; i < arguments->size(); ++i) + compileExpression(*(arguments->at(i)), constructorType.parameterTypes()[i]); + } + _constructor.accept(*this); +} + +void Compiler::appendConstructor(FunctionDefinition const& _constructor) +{ + CompilerContext::LocationSetter locationSetter(m_context, _constructor); + // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program + if (!_constructor.parameters().empty()) + { + unsigned argumentSize = 0; + for (ASTPointer<VariableDeclaration> const& var: _constructor.parameters()) + if (var->annotation().type->isDynamicallySized()) + { + argumentSize = 0; + break; + } + else + argumentSize += var->annotation().type->calldataEncodedSize(); + + CompilerUtils(m_context).fetchFreeMemoryPointer(); + if (argumentSize == 0) + { + // argument size is dynamic, use CODESIZE to determine it + m_context.appendProgramSize(); // program itself + // CODESIZE is program plus manually added arguments + m_context << eth::Instruction::CODESIZE << eth::Instruction::SUB; + } + else + m_context << u256(argumentSize); + // stack: <memptr> <argument size> + m_context << eth::Instruction::DUP1; + m_context.appendProgramSize(); + m_context << eth::Instruction::DUP4 << eth::Instruction::CODECOPY; + m_context << eth::Instruction::DUP2 << eth::Instruction::ADD; + CompilerUtils(m_context).storeFreeMemoryPointer(); + // stack: <memptr> + appendCalldataUnpacker(FunctionType(_constructor).parameterTypes(), true); + } + _constructor.accept(*this); +} + +void Compiler::appendFunctionSelector(ContractDefinition const& _contract) +{ + map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions(); + map<FixedHash<4>, const eth::AssemblyItem> callDataUnpackerEntryPoints; + + FunctionDefinition const* fallback = _contract.fallbackFunction(); + eth::AssemblyItem notFound = m_context.newTag(); + // shortcut messages without data if we have many functions in order to be able to receive + // ether with constant gas + if (interfaceFunctions.size() > 5 || fallback) + { + m_context << eth::Instruction::CALLDATASIZE << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(notFound); + } + + // retrieve the function signature hash from the calldata + if (!interfaceFunctions.empty()) + CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true); + + // stack now is: 1 0 <funhash> + for (auto const& it: interfaceFunctions) + { + callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag())); + m_context << eth::dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << eth::Instruction::EQ; + m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first)); + } + m_context.appendJumpTo(notFound); + + m_context << notFound; + if (fallback) + { + eth::AssemblyItem returnTag = m_context.pushNewTag(); + fallback->accept(*this); + m_context << returnTag; + appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary()); + } + else if (_contract.isLibrary()) + // Reject invalid library calls and ether sent to a library. + m_context.appendJumpTo(m_context.errorTag()); + else + m_context << eth::Instruction::STOP; // function not found + + for (auto const& it: interfaceFunctions) + { + FunctionTypePointer const& functionType = it.second; + solAssert(functionType->hasDeclaration(), ""); + CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration()); + m_context << callDataUnpackerEntryPoints.at(it.first); + eth::AssemblyItem returnTag = m_context.pushNewTag(); + m_context << CompilerUtils::dataStartOffset; + appendCalldataUnpacker(functionType->parameterTypes()); + m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration())); + m_context << returnTag; + appendReturnValuePacker(functionType->returnParameterTypes(), _contract.isLibrary()); + } +} + +void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory) +{ + // We do not check the calldata size, everything is zero-padded + + //@todo this does not yet support nested dynamic arrays + + // Retain the offset pointer as base_offset, the point from which the data offsets are computed. + m_context << eth::Instruction::DUP1; + for (TypePointer const& parameterType: _typeParameters) + { + // stack: v1 v2 ... v(k-1) base_offset current_offset + TypePointer type = parameterType->decodingType(); + if (type->category() == Type::Category::Array) + { + auto const& arrayType = dynamic_cast<ArrayType const&>(*type); + solAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); + if (_fromMemory) + { + solAssert( + arrayType.baseType()->isValueType(), + "Nested memory arrays not yet implemented here." + ); + // @todo If base type is an array or struct, it is still calldata-style encoded, so + // we would have to convert it like below. + solAssert(arrayType.location() == DataLocation::Memory, ""); + // compute data pointer + m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD; + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP2 << eth::Instruction::SWAP1; + m_context << u256(0x20) << eth::Instruction::ADD; + } + else + { + // first load from calldata and potentially convert to memory if arrayType is memory + TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false); + if (calldataType->isDynamicallySized()) + { + // put on stack: data_pointer length + CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory); + // stack: base_offset data_offset next_pointer + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP3 << eth::Instruction::ADD; + // stack: base_offset next_pointer data_pointer + // retrieve length + CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true); + // stack: base_offset next_pointer length data_pointer + m_context << eth::Instruction::SWAP2; + // stack: base_offset data_pointer length next_pointer + } + else + { + // leave the pointer on the stack + m_context << eth::Instruction::DUP1; + m_context << u256(calldataType->calldataEncodedSize()) << eth::Instruction::ADD; + } + if (arrayType.location() == DataLocation::Memory) + { + // stack: base_offset calldata_ref [length] next_calldata + // copy to memory + // move calldata type up again + CompilerUtils(m_context).moveIntoStack(calldataType->sizeOnStack()); + CompilerUtils(m_context).convertType(*calldataType, arrayType); + // fetch next pointer again + CompilerUtils(m_context).moveToStackTop(arrayType.sizeOnStack()); + } + // move base_offset up + CompilerUtils(m_context).moveToStackTop(1 + arrayType.sizeOnStack()); + m_context << eth::Instruction::SWAP1; + } + } + else + { + solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString()); + CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, true); + CompilerUtils(m_context).moveToStackTop(1 + type->sizeOnStack()); + m_context << eth::Instruction::SWAP1; + } + // stack: v1 v2 ... v(k-1) v(k) base_offset mem_offset + } + m_context << eth::Instruction::POP << eth::Instruction::POP; +} + +void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary) +{ + CompilerUtils utils(m_context); + if (_typeParameters.empty()) + m_context << eth::Instruction::STOP; + else + { + 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, true, false, _isLibrary); + utils.toSizeAfterFreeMemoryPointer(); + m_context << eth::Instruction::RETURN; + } +} + +void Compiler::registerStateVariables(ContractDefinition const& _contract) +{ + for (auto const& var: ContractType(_contract).stateVariables()) + m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); +} + +void Compiler::initializeStateVariables(ContractDefinition const& _contract) +{ + for (ASTPointer<VariableDeclaration> const& variable: _contract.stateVariables()) + if (variable->value() && !variable->isConstant()) + ExpressionCompiler(m_context, m_optimize).appendStateVariableInitialization(*variable); +} + +bool Compiler::visit(VariableDeclaration const& _variableDeclaration) +{ + solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration."); + CompilerContext::LocationSetter locationSetter(m_context, _variableDeclaration); + + m_context.startFunction(_variableDeclaration); + m_breakTags.clear(); + m_continueTags.clear(); + + if (_variableDeclaration.isConstant()) + ExpressionCompiler(m_context, m_optimize).appendConstStateVariableAccessor(_variableDeclaration); + else + ExpressionCompiler(m_context, m_optimize).appendStateVariableAccessor(_variableDeclaration); + + return false; +} + +bool Compiler::visit(FunctionDefinition const& _function) +{ + CompilerContext::LocationSetter locationSetter(m_context, _function); + + m_context.startFunction(_function); + + // stack upon entry: [return address] [arg0] [arg1] ... [argn] + // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] + + unsigned parametersSize = CompilerUtils::sizeOnStack(_function.parameters()); + if (!_function.isConstructor()) + // adding 1 for return address. + m_context.adjustStackOffset(parametersSize + 1); + for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters()) + { + m_context.addVariable(*variable, parametersSize); + parametersSize -= variable->annotation().type->sizeOnStack(); + } + + for (ASTPointer<VariableDeclaration const> const& variable: _function.returnParameters()) + appendStackVariableInitialisation(*variable); + for (VariableDeclaration const* localVariable: _function.localVariables()) + appendStackVariableInitialisation(*localVariable); + + if (_function.isConstructor()) + if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope()))) + appendBaseConstructor(*c); + + m_returnTag = m_context.newTag(); + m_breakTags.clear(); + m_continueTags.clear(); + m_stackCleanupForReturn = 0; + m_currentFunction = &_function; + m_modifierDepth = 0; + + appendModifierOrFunctionCode(); + + m_context << m_returnTag; + + // Now we need to re-shuffle the stack. For this we keep a record of the stack layout + // that shows the target positions of the elements, where "-1" denotes that this element needs + // to be removed from the stack. + // Note that the fact that the return arguments are of increasing index is vital for this + // algorithm to work. + + unsigned const c_argumentsSize = CompilerUtils::sizeOnStack(_function.parameters()); + unsigned const c_returnValuesSize = CompilerUtils::sizeOnStack(_function.returnParameters()); + unsigned const c_localVariablesSize = CompilerUtils::sizeOnStack(_function.localVariables()); + + vector<int> stackLayout; + stackLayout.push_back(c_returnValuesSize); // target of return address + stackLayout += vector<int>(c_argumentsSize, -1); // discard all arguments + for (unsigned i = 0; i < c_returnValuesSize; ++i) + stackLayout.push_back(i); + stackLayout += vector<int>(c_localVariablesSize, -1); + + solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables."); + while (stackLayout.back() != int(stackLayout.size() - 1)) + if (stackLayout.back() < 0) + { + m_context << eth::Instruction::POP; + stackLayout.pop_back(); + } + else + { + m_context << eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1); + swap(stackLayout[stackLayout.back()], stackLayout.back()); + } + //@todo assert that everything is in place now + + for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters() + _function.returnParameters()) + m_context.removeVariable(*variable); + for (VariableDeclaration const* localVariable: _function.localVariables()) + m_context.removeVariable(*localVariable); + + m_context.adjustStackOffset(-(int)c_returnValuesSize); + + if (!_function.isConstructor()) + m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); + return false; +} + +bool Compiler::visit(IfStatement const& _ifStatement) +{ + StackHeightChecker checker(m_context); + CompilerContext::LocationSetter locationSetter(m_context, _ifStatement); + compileExpression(_ifStatement.condition()); + m_context << eth::Instruction::ISZERO; + eth::AssemblyItem falseTag = m_context.appendConditionalJump(); + eth::AssemblyItem endTag = falseTag; + _ifStatement.trueStatement().accept(*this); + if (_ifStatement.falseStatement()) + { + endTag = m_context.appendJumpToNew(); + m_context << falseTag; + _ifStatement.falseStatement()->accept(*this); + } + m_context << endTag; + + checker.check(); + return false; +} + +bool Compiler::visit(WhileStatement const& _whileStatement) +{ + StackHeightChecker checker(m_context); + CompilerContext::LocationSetter locationSetter(m_context, _whileStatement); + eth::AssemblyItem loopStart = m_context.newTag(); + eth::AssemblyItem loopEnd = m_context.newTag(); + m_continueTags.push_back(loopStart); + m_breakTags.push_back(loopEnd); + + m_context << loopStart; + compileExpression(_whileStatement.condition()); + m_context << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(loopEnd); + + _whileStatement.body().accept(*this); + + m_context.appendJumpTo(loopStart); + m_context << loopEnd; + + m_continueTags.pop_back(); + m_breakTags.pop_back(); + + checker.check(); + return false; +} + +bool Compiler::visit(ForStatement const& _forStatement) +{ + StackHeightChecker checker(m_context); + CompilerContext::LocationSetter locationSetter(m_context, _forStatement); + eth::AssemblyItem loopStart = m_context.newTag(); + eth::AssemblyItem loopEnd = m_context.newTag(); + eth::AssemblyItem loopNext = m_context.newTag(); + m_continueTags.push_back(loopNext); + m_breakTags.push_back(loopEnd); + + if (_forStatement.initializationExpression()) + _forStatement.initializationExpression()->accept(*this); + + m_context << loopStart; + + // if there is no terminating condition in for, default is to always be true + if (_forStatement.condition()) + { + compileExpression(*_forStatement.condition()); + m_context << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(loopEnd); + } + + _forStatement.body().accept(*this); + + m_context << loopNext; + + // for's loop expression if existing + if (_forStatement.loopExpression()) + _forStatement.loopExpression()->accept(*this); + + m_context.appendJumpTo(loopStart); + m_context << loopEnd; + + m_continueTags.pop_back(); + m_breakTags.pop_back(); + + checker.check(); + return false; +} + +bool Compiler::visit(Continue const& _continueStatement) +{ + CompilerContext::LocationSetter locationSetter(m_context, _continueStatement); + if (!m_continueTags.empty()) + m_context.appendJumpTo(m_continueTags.back()); + return false; +} + +bool Compiler::visit(Break const& _breakStatement) +{ + CompilerContext::LocationSetter locationSetter(m_context, _breakStatement); + if (!m_breakTags.empty()) + m_context.appendJumpTo(m_breakTags.back()); + return false; +} + +bool Compiler::visit(Return const& _return) +{ + CompilerContext::LocationSetter locationSetter(m_context, _return); + if (Expression const* expression = _return.expression()) + { + solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer."); + vector<ASTPointer<VariableDeclaration>> const& returnParameters = + _return.annotation().functionReturnParameters->parameters(); + TypePointers types; + for (auto const& retVariable: returnParameters) + types.push_back(retVariable->annotation().type); + + TypePointer expectedType = types.size() == 1 ? types.front() : make_shared<TupleType>(types); + compileExpression(*expression, expectedType); + + for (auto const& retVariable: boost::adaptors::reverse(returnParameters)) + CompilerUtils(m_context).moveToStackVariable(*retVariable); + } + for (unsigned i = 0; i < m_stackCleanupForReturn; ++i) + m_context << eth::Instruction::POP; + m_context.appendJumpTo(m_returnTag); + m_context.adjustStackOffset(m_stackCleanupForReturn); + return false; +} + +bool Compiler::visit(Throw const& _throw) +{ + CompilerContext::LocationSetter locationSetter(m_context, _throw); + m_context.appendJumpTo(m_context.errorTag()); + return false; +} + +bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) +{ + StackHeightChecker checker(m_context); + CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); + if (Expression const* expression = _variableDeclarationStatement.initialValue()) + { + CompilerUtils utils(m_context); + compileExpression(*expression); + TypePointers valueTypes; + if (auto tupleType = dynamic_cast<TupleType const*>(expression->annotation().type.get())) + valueTypes = tupleType->components(); + else + valueTypes = TypePointers{expression->annotation().type}; + auto const& assignments = _variableDeclarationStatement.annotation().assignments; + solAssert(assignments.size() == valueTypes.size(), ""); + for (size_t i = 0; i < assignments.size(); ++i) + { + size_t j = assignments.size() - i - 1; + solAssert(!!valueTypes[j], ""); + VariableDeclaration const* varDecl = assignments[j]; + if (!varDecl) + utils.popStackElement(*valueTypes[j]); + else + { + utils.convertType(*valueTypes[j], *varDecl->annotation().type); + utils.moveToStackVariable(*varDecl); + } + } + } + checker.check(); + return false; +} + +bool Compiler::visit(ExpressionStatement const& _expressionStatement) +{ + StackHeightChecker checker(m_context); + CompilerContext::LocationSetter locationSetter(m_context, _expressionStatement); + Expression const& expression = _expressionStatement.expression(); + compileExpression(expression); + CompilerUtils(m_context).popStackElement(*expression.annotation().type); + checker.check(); + return false; +} + +bool Compiler::visit(PlaceholderStatement const& _placeholderStatement) +{ + StackHeightChecker checker(m_context); + CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); + ++m_modifierDepth; + appendModifierOrFunctionCode(); + --m_modifierDepth; + checker.check(); + return true; +} + +void Compiler::appendFunctionsWithoutCode() +{ + set<Declaration const*> functions = m_context.functionsWithoutCode(); + while (!functions.empty()) + { + for (Declaration const* function: functions) + { + m_context.setStackOffset(0); + function->accept(*this); + } + functions = m_context.functionsWithoutCode(); + } +} + +void Compiler::appendModifierOrFunctionCode() +{ + solAssert(m_currentFunction, ""); + if (m_modifierDepth >= m_currentFunction->modifiers().size()) + m_currentFunction->body().accept(*this); + else + { + ASTPointer<ModifierInvocation> const& modifierInvocation = m_currentFunction->modifiers()[m_modifierDepth]; + + // constructor call should be excluded + if (dynamic_cast<ContractDefinition const*>(modifierInvocation->name()->annotation().referencedDeclaration)) + { + ++m_modifierDepth; + appendModifierOrFunctionCode(); + --m_modifierDepth; + return; + } + + ModifierDefinition const& modifier = m_context.functionModifier(modifierInvocation->name()->name()); + CompilerContext::LocationSetter locationSetter(m_context, modifier); + solAssert(modifier.parameters().size() == modifierInvocation->arguments().size(), ""); + for (unsigned i = 0; i < modifier.parameters().size(); ++i) + { + m_context.addVariable(*modifier.parameters()[i]); + compileExpression( + *modifierInvocation->arguments()[i], + modifier.parameters()[i]->annotation().type + ); + } + for (VariableDeclaration const* localVariable: modifier.localVariables()) + appendStackVariableInitialisation(*localVariable); + + unsigned const c_stackSurplus = CompilerUtils::sizeOnStack(modifier.parameters()) + + CompilerUtils::sizeOnStack(modifier.localVariables()); + m_stackCleanupForReturn += c_stackSurplus; + + modifier.body().accept(*this); + + for (unsigned i = 0; i < c_stackSurplus; ++i) + m_context << eth::Instruction::POP; + m_stackCleanupForReturn -= c_stackSurplus; + } +} + +void Compiler::appendStackVariableInitialisation(VariableDeclaration const& _variable) +{ + CompilerContext::LocationSetter location(m_context, _variable); + m_context.addVariable(_variable); + CompilerUtils(m_context).pushZeroValue(*_variable.annotation().type); +} + +void Compiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) +{ + ExpressionCompiler expressionCompiler(m_context, m_optimize); + expressionCompiler.compile(_expression); + if (_targetType) + CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); +} + +eth::Assembly Compiler::cloneRuntime() +{ + eth::Assembly a; + a << eth::Instruction::CALLDATASIZE; + a << u256(0) << eth::Instruction::DUP1 << eth::Instruction::CALLDATACOPY; + //@todo adjust for larger return values, make this dynamic. + a << u256(0x20) << u256(0) << eth::Instruction::CALLDATASIZE; + // unfortunately, we have to send the value again, so that CALLVALUE returns the correct value + // in the callcoded contract. + a << u256(0) << eth::Instruction::CALLVALUE; + // this is the address which has to be substituted by the linker. + //@todo implement as special "marker" AssemblyItem. + a << u256("0xcafecafecafecafecafecafecafecafecafecafe"); + a << u256(eth::c_callGas + eth::c_callValueTransferGas + 10) << eth::Instruction::GAS << eth::Instruction::SUB; + a << eth::Instruction::CALLCODE; + //Propagate error condition (if CALLCODE pushes 0 on stack). + a << eth::Instruction::ISZERO; + a.appendJumpI(a.errorTag()); + //@todo adjust for larger return values, make this dynamic. + a << u256(0x20) << u256(0) << eth::Instruction::RETURN; + return a; +} diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h new file mode 100644 index 00000000..14314434 --- /dev/null +++ b/libsolidity/codegen/Compiler.h @@ -0,0 +1,144 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Solidity AST to EVM bytecode compiler. + */ + +#pragma once + +#include <ostream> +#include <functional> +#include <libsolidity/ast/ASTVisitor.h> +#include <libsolidity/codegen/CompilerContext.h> +#include <libevmasm/Assembly.h> + +namespace dev { +namespace solidity { + +class Compiler: private ASTConstVisitor +{ +public: + explicit Compiler(bool _optimize = false, unsigned _runs = 200): + m_optimize(_optimize), + m_optimizeRuns(_runs), + m_returnTag(m_context.newTag()) + { } + + void compileContract( + ContractDefinition const& _contract, + std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts + ); + /// Compiles a contract that uses CALLCODE to call into a pre-deployed version of the given + /// contract at runtime, but contains the full creation-time code. + void compileClone( + ContractDefinition const& _contract, + std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts + ); + eth::Assembly const& assembly() { return m_context.assembly(); } + eth::LinkerObject assembledObject() { return m_context.assembledObject(); } + eth::LinkerObject runtimeObject() { return m_context.assembledRuntimeObject(m_runtimeSub); } + /// @arg _sourceCodes is the map of input files to source code strings + /// @arg _inJsonFromat 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_context.streamAssembly(_stream, _sourceCodes, _inJsonFormat); + } + /// @returns Assembly items of the normal compiler context + eth::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); } + /// @returns Assembly items of the runtime compiler context + eth::AssemblyItems const& runtimeAssemblyItems() const { return m_context.assembly().sub(m_runtimeSub).items(); } + + /// @returns the entry label of the given function. Might return an AssemblyItem of type + /// UndefinedItem if it does not exist yet. + eth::AssemblyItem functionEntryLabel(FunctionDefinition const& _function) const; + +private: + /// Registers the non-function objects inside the contract with the context and stores the basic + /// information about the contract like the AST annotations. + void initializeContext( + ContractDefinition const& _contract, + std::map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts + ); + /// 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. + void packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); + /// 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); + /// 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. + void appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory = false); + void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary); + + void registerStateVariables(ContractDefinition const& _contract); + void initializeStateVariables(ContractDefinition const& _contract); + + /// Initialises all memory arrays in the local variables to point to an empty location. + void initialiseMemoryArrays(std::vector<VariableDeclaration const*> _variables); + /// Pushes the initialised value of the given type to the stack. If the type is a memory + /// reference type, allocates memory and pushes the memory pointer. + /// Not to be used for storage references. + void initialiseInMemory(Type const& _type); + + virtual bool visit(VariableDeclaration const& _variableDeclaration) override; + virtual bool visit(FunctionDefinition const& _function) override; + virtual bool visit(IfStatement const& _ifStatement) override; + virtual bool visit(WhileStatement const& _whileStatement) override; + virtual bool visit(ForStatement const& _forStatement) override; + virtual bool visit(Continue const& _continue) override; + virtual bool visit(Break const& _break) override; + virtual bool visit(Return const& _return) override; + virtual bool visit(Throw const& _throw) override; + virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; + virtual bool visit(ExpressionStatement const& _expressionStatement) override; + virtual bool visit(PlaceholderStatement const&) override; + + /// Repeatedly visits all function which are referenced but which are not compiled yet. + void appendFunctionsWithoutCode(); + + /// Appends one layer of function modifier code of the current function, or the function + /// body itself if the last modifier was reached. + void appendModifierOrFunctionCode(); + + void appendStackVariableInitialisation(VariableDeclaration const& _variable); + void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); + + /// @returns the runtime assembly for clone contracts. + static eth::Assembly cloneRuntime(); + + bool const m_optimize; + unsigned const m_optimizeRuns; + CompilerContext m_context; + size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly + CompilerContext m_runtimeContext; + 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 + eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement + unsigned m_modifierDepth = 0; + FunctionDefinition const* m_currentFunction = nullptr; + unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag + // arguments for base constructors, filled in derived-to-base order + std::map<FunctionDefinition const*, std::vector<ASTPointer<Expression>> const*> m_baseArguments; +}; + +} +} diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp new file mode 100644 index 00000000..00b9d87c --- /dev/null +++ b/libsolidity/codegen/CompilerContext.cpp @@ -0,0 +1,223 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Utilities for the solidity compiler. + */ + +#include <libsolidity/codegen/CompilerContext.h> +#include <utility> +#include <numeric> +#include <libsolidity/ast/AST.h> +#include <libsolidity/codegen/Compiler.h> +#include <libsolidity/interface/Version.h> + +using namespace std; + +namespace dev +{ +namespace solidity +{ + +void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaration) +{ + m_magicGlobals.insert(&_declaration); +} + +void CompilerContext::addStateVariable( + VariableDeclaration const& _declaration, + u256 const& _storageOffset, + unsigned _byteOffset +) +{ + m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset); +} + +void CompilerContext::startFunction(Declaration const& _function) +{ + m_functionsWithCode.insert(&_function); + *this << functionEntryLabel(_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; +} + +void CompilerContext::removeVariable(VariableDeclaration const& _declaration) +{ + solAssert(!!m_localVariables.count(&_declaration), ""); + m_localVariables.erase(&_declaration); +} + +eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const +{ + auto ret = m_compiledContracts.find(&_contract); + solAssert(ret != m_compiledContracts.end(), "Compiled contract not found."); + return *ret->second; +} + +bool CompilerContext::isLocalVariable(Declaration const* _declaration) const +{ + return !!m_localVariables.count(_declaration); +} + +eth::AssemblyItem CompilerContext::functionEntryLabel(Declaration const& _declaration) +{ + auto res = m_functionEntryLabels.find(&_declaration); + if (res == m_functionEntryLabels.end()) + { + eth::AssemblyItem tag(m_asm.newTag()); + m_functionEntryLabels.insert(make_pair(&_declaration, tag)); + return tag.tag(); + } + else + return res->second.tag(); +} + +eth::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration const& _declaration) const +{ + auto res = m_functionEntryLabels.find(&_declaration); + return res == m_functionEntryLabels.end() ? eth::AssemblyItem(eth::UndefinedItem) : res->second.tag(); +} + +eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel(FunctionDefinition const& _function) +{ + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + return virtualFunctionEntryLabel(_function, m_inheritanceHierarchy.begin()); +} + +eth::AssemblyItem CompilerContext::superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base) +{ + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + return virtualFunctionEntryLabel(_function, superContract(_base)); +} + +FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const +{ + vector<ContractDefinition const*>::const_iterator it = superContract(_contract); + for (; it != m_inheritanceHierarchy.end(); ++it) + if ((*it)->constructor()) + return (*it)->constructor(); + + return nullptr; +} + +set<Declaration const*> CompilerContext::functionsWithoutCode() +{ + set<Declaration const*> functions; + for (auto const& it: m_functionEntryLabels) + if (m_functionsWithCode.count(it.first) == 0) + functions.insert(it.first); + return functions; +} + +ModifierDefinition const& CompilerContext::functionModifier(string const& _name) const +{ + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + for (ContractDefinition const* contract: m_inheritanceHierarchy) + for (ASTPointer<ModifierDefinition> const& modifier: contract->functionModifiers()) + if (modifier->name() == _name) + return *modifier.get(); + BOOST_THROW_EXCEPTION(InternalCompilerError() + << errinfo_comment("Function modifier " + _name + " not found.")); +} + +unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const +{ + auto res = m_localVariables.find(&_declaration); + solAssert(res != m_localVariables.end(), "Variable not found on stack."); + return res->second; +} + +unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const +{ + return m_asm.deposit() - _baseOffset - 1; +} + +unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const +{ + return m_asm.deposit() - _offset - 1; +} + +pair<u256, unsigned> CompilerContext::storageLocationOfVariable(const Declaration& _declaration) const +{ + auto it = m_stateVariables.find(&_declaration); + solAssert(it != m_stateVariables.end(), "Variable not found in storage."); + return it->second; +} + +CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpType) +{ + eth::AssemblyItem item(eth::Instruction::JUMP); + item.setJumpType(_jumpType); + return *this << item; +} + +void CompilerContext::resetVisitedNodes(ASTNode const* _node) +{ + stack<ASTNode const*> newStack; + newStack.push(_node); + std::swap(m_visitedNodes, newStack); + updateSourceLocation(); +} + +void CompilerContext::injectVersionStampIntoSub(size_t _subIndex) +{ + eth::Assembly& sub = m_asm.sub(_subIndex); + sub.injectStart(eth::Instruction::POP); + sub.injectStart(fromBigEndian<u256>(binaryVersion())); +} + +eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( + FunctionDefinition const& _function, + vector<ContractDefinition const*>::const_iterator _searchStart +) +{ + string name = _function.name(); + FunctionType functionType(_function); + auto it = _searchStart; + for (; it != m_inheritanceHierarchy.end(); ++it) + for (ASTPointer<FunctionDefinition> const& function: (*it)->definedFunctions()) + if ( + function->name() == name && + !function->isConstructor() && + FunctionType(*function).hasEqualArgumentTypes(functionType) + ) + return functionEntryLabel(*function); + solAssert(false, "Super function " + name + " not found."); + return m_asm.newTag(); // not reached +} + +vector<ContractDefinition const*>::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const +{ + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_contract); + solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy."); + return ++it; +} + +void CompilerContext::updateSourceLocation() +{ + m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); +} + +} +} diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h new file mode 100644 index 00000000..5287088a --- /dev/null +++ b/libsolidity/codegen/CompilerContext.h @@ -0,0 +1,189 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Utilities for the solidity compiler. + */ + +#pragma once + +#include <ostream> +#include <stack> +#include <utility> +#include <libevmcore/Instruction.h> +#include <libevmasm/Assembly.h> +#include <libsolidity/ast/ASTForward.h> +#include <libsolidity/ast/Types.h> +#include <libsolidity/ast/ASTAnnotations.h> +#include <libdevcore/Common.h> + +namespace dev { +namespace solidity { + + +/** + * Context to be shared by all units that compile the same contract. + * It stores the generated bytecode and the position of identifiers in memory and on the stack. + */ +class CompilerContext +{ +public: + void addMagicGlobal(MagicVariableDeclaration const& _declaration); + void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); + void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); + void removeVariable(VariableDeclaration const& _declaration); + + 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()); } + + bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; } + bool isLocalVariable(Declaration const* _declaration) const; + bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration) != 0; } + + /// @returns the entry label of the given function and creates it if it does not exist yet. + eth::AssemblyItem functionEntryLabel(Declaration const& _declaration); + /// @returns the entry label of the given function. Might return an AssemblyItem of type + /// UndefinedItem if it does not exist yet. + 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 + /// above _base in the current inheritance hierarchy. + eth::AssemblyItem superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base); + FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const; + + /// @returns the set of functions for which we still need to generate code + std::set<Declaration const*> functionsWithoutCode(); + /// Resets function specific members, inserts the function entry label and marks the function + /// as "having code". + void startFunction(Declaration const& _function); + + ModifierDefinition const& functionModifier(std::string const& _name) const; + /// Returns the distance of the given local variable from the bottom of the stack (of the current function). + unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const; + /// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns + /// the distance of that variable from the current top of the stack. + unsigned baseToCurrentStackOffset(unsigned _baseOffset) const; + /// Converts an offset relative to the current stack height to a value that can be used later + /// with baseToCurrentStackOffset to point to the same stack element. + unsigned currentToBaseStackOffset(unsigned _offset) const; + /// @returns pair of slot and byte offset of the value inside this slot. + 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(); } + /// Appends a JUMPI instruction to @a _tag + 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(); } + /// 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(); } + /// Appends a JUMP to a specific tag + 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(); } + /// @returns a new tag without pushing any opcodes or data + eth::AssemblyItem newTag() { return m_asm.newTag(); } + /// Adds a subroutine to the code (in the data section) 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); } + /// Pushes the size of the final program + void appendProgramSize() { return m_asm.appendProgramSize(); } + /// Adds data to the data section, pushes a reference to the stack + eth::AssemblyItem appendData(bytes const& _data) { return m_asm.append(_data); } + /// Appends the address (virtual, will be filled in by linker) of a library. + 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 + void popVisitedNodes() { m_visitedNodes.pop(); updateSourceLocation(); } + /// Pushes an ASTNode to the stack of visited nodes + 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<<(eth::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; } + + /// Prepends "PUSH <compiler version number> POP" + void injectVersionStampIntoSub(size_t _subIndex); + + void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } + + eth::Assembly const& assembly() const { 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); + } + + 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 + */ + class LocationSetter: public ScopeGuard + { + public: + LocationSetter(CompilerContext& _compilerContext, ASTNode const& _node): + ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); } + }; + +private: + /// @returns the entry label of the given function - searches the inheritance hierarchy + /// startig from the given point towards the base. + eth::AssemblyItem virtualFunctionEntryLabel( + FunctionDefinition const& _function, + std::vector<ContractDefinition const*>::const_iterator _searchStart + ); + /// @returns an iterator to the contract directly above the given contract. + std::vector<ContractDefinition const*>::const_iterator superContract(const ContractDefinition &_contract) const; + /// Updates source location set in the assembly. + void updateSourceLocation(); + + eth::Assembly 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. + std::map<ContractDefinition const*, eth::Assembly const*> m_compiledContracts; + /// Storage offsets of state variables + std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables; + /// Offsets of local variables on the stack (relative to stack base). + std::map<Declaration const*, unsigned> m_localVariables; + /// Labels pointing to the entry points of functions. + std::map<Declaration const*, eth::AssemblyItem> m_functionEntryLabels; + /// Set of functions for which we did not yet generate code. + std::set<Declaration const*> m_functionsWithCode; + /// List of current inheritance hierarchy from derived to base. + std::vector<ContractDefinition const*> m_inheritanceHierarchy; + /// Stack of current visited AST nodes, used for location attachment + std::stack<ASTNode const*> m_visitedNodes; +}; + +} +} diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp new file mode 100644 index 00000000..cd84f5fc --- /dev/null +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -0,0 +1,802 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Routines used by both the compiler and the expression compiler. + */ + +#include <libsolidity/codegen/CompilerUtils.h> +#include <libsolidity/ast/AST.h> +#include <libevmcore/Instruction.h> +#include <libevmcore/Params.h> +#include <libsolidity/codegen/ArrayUtils.h> +#include <libsolidity/codegen/LValue.h> + +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 +) +{ + if (_keepUpdatedMemoryOffset) + m_context << eth::Instruction::DUP1; + + if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) + { + solAssert(!arrayType->isDynamicallySized(), ""); + solAssert(!_fromCalldata, ""); + solAssert(_padToWordBoundaries, ""); + if (_keepUpdatedMemoryOffset) + m_context << arrayType->memorySize() << eth::Instruction::ADD; + } + else + { + 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<ReferenceType const*>(&_type)) + { + solAssert(ref->location() == DataLocation::Memory, ""); + storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries); + } + else if (auto str = dynamic_cast<StringLiteralType const*>(&_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, + bool _encodeAsLibraryTypes +) +{ + // stack: <v1> <v2> ... <vn> <mem> + TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; + solAssert(targetTypes.size() == _givenTypes.size(), ""); + for (TypePointer& t: targetTypes) + t = t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(); + + // Stack during operation: + // <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem> + // 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) && targetType->isValueType()) + { + // special case: convert storage reference type to value type - this is only + // possible for library calls where we just forward the storage reference + solAssert(_encodeAsLibraryTypes, ""); + solAssert(_givenTypes[i]->sizeOnStack() == 1, ""); + } + else 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<ArrayType const*>(type.get())) + ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries); + else + storeInMemoryDynamic(*type, _padToWordBoundaries); + } + stackPos += _givenTypes[i]->sizeOnStack(); + } + + // now copy the dynamic part + // Stack: <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem> + 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: ... <end_of_mem> + if (_givenTypes[i]->category() == Type::Category::StringLiteral) + { + auto const& strType = dynamic_cast<StringLiteralType const&>(*_givenTypes[i]); + m_context << u256(strType.value().size()); + storeInMemoryDynamic(IntegerType(256), true); + // stack: ... <end_of_mem'> + storeInMemoryDynamic(strType, _padToWordBoundaries); + } + else + { + solAssert(_givenTypes[i]->category() == Type::Category::Array, "Unknown dynamic type."); + auto const& arrayType = dynamic_cast<ArrayType const&>(*_givenTypes[i]); + // now copy the array + copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.sizeOnStack()); + // stack: ... <end_of_mem> <value...> + // copy length to memory + m_context << eth::dupInstruction(1 + arrayType.sizeOnStack()); + ArrayUtils(m_context).retrieveLength(arrayType, 1); + // stack: ... <end_of_mem> <value...> <end_of_mem'> <length> + storeInMemoryDynamic(IntegerType(256), true); + // stack: ... <end_of_mem> <value...> <end_of_mem''> + // copy the new memory pointer + m_context << eth::swapInstruction(arrayType.sizeOnStack() + 1) << eth::Instruction::POP; + // stack: ... <end_of_mem''> <value...> + // copy data part + ArrayUtils(m_context).copyArrayToMemory(arrayType, _padToWordBoundaries); + // stack: ... <end_of_mem'''> + } + + 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<FixedBytesType const&>(_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<IntegerType const&>(_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<FixedBytesType const&>(_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<FixedBytesType const&>(_targetType); + if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_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<IntegerType const&>(_targetType) : addressType; + if (stackTypeCategory == Type::Category::IntegerConstant) + { + IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_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<IntegerType const&>(_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<StringLiteralType const&>(_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<ArrayType const&>(_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<ArrayType const&>(_typeOnStack); + ArrayType const& targetType = dynamic_cast<ArrayType const&>(_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: <source ref> (variably sized) + unsigned stackSize = typeOnStack.sizeOnStack(); + ArrayUtils(m_context).retrieveLength(typeOnStack); + + // allocate memory + // stack: <source ref> (variably sized) <length> + m_context << eth::Instruction::DUP1; + ArrayUtils(m_context).convertLengthToSize(targetType, true); + // stack: <source ref> (variably sized) <length> <size> + if (targetType.isDynamicallySized()) + m_context << u256(0x20) << eth::Instruction::ADD; + allocateMemory(); + // stack: <source ref> (variably sized) <length> <mem start> + m_context << eth::Instruction::DUP1; + moveIntoStack(2 + stackSize); + if (targetType.isDynamicallySized()) + { + m_context << eth::Instruction::DUP2; + storeInMemoryDynamic(IntegerType(256)); + } + // stack: <mem start> <source ref> (variably sized) <length> <mem data pos> + 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: <mem start> <source ref> (variably sized) <length> <counter> <mem data pos> + 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: <mem start> <source ref> (variably sized) <length> <mem data pos updated> + popStackSlots(2 + stackSize); + // Stack: <mem start> + } + 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<StructType const&>(_targetType); + auto& typeOnStack = dynamic_cast<StructType const&>(_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: <source ref> + m_context << typeOnStack.memorySize(); + allocateMemory(); + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; + // stack: <memory ptr> <source ref> <memory ptr> + for (auto const& member: typeOnStack.members()) + { + if (!member.type->canLiveOutsideStorage()) + continue; + pair<u256, unsigned> 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; + } + case Type::Category::Tuple: + { + TupleType const& sourceTuple = dynamic_cast<TupleType const&>(_typeOnStack); + TupleType const& targetTuple = dynamic_cast<TupleType const&>(_targetType); + // fillRight: remove excess values at right side, !fillRight: remove eccess values at left side + bool fillRight = !targetTuple.components().empty() && ( + !targetTuple.components().back() || + targetTuple.components().front() + ); + unsigned depth = sourceTuple.sizeOnStack(); + for (size_t i = 0; i < sourceTuple.components().size(); ++i) + { + TypePointer sourceType = sourceTuple.components()[i]; + TypePointer targetType; + if (fillRight && i < targetTuple.components().size()) + targetType = targetTuple.components()[i]; + else if (!fillRight && targetTuple.components().size() + i >= sourceTuple.components().size()) + targetType = targetTuple.components()[targetTuple.components().size() - (sourceTuple.components().size() - i)]; + if (!sourceType) + { + solAssert(!targetType, ""); + continue; + } + unsigned sourceSize = sourceType->sizeOnStack(); + unsigned targetSize = targetType ? targetType->sizeOnStack() : 0; + if (!targetType || *sourceType != *targetType || _cleanupNeeded) + { + if (targetType) + { + if (sourceSize > 0) + copyToStackTop(depth, sourceSize); + convertType(*sourceType, *targetType, _cleanupNeeded); + } + if (sourceSize > 0 || targetSize > 0) + { + // Move it back into its place. + for (unsigned j = 0; j < min(sourceSize, targetSize); ++j) + m_context << + eth::swapInstruction(depth + targetSize - sourceSize) << + eth::Instruction::POP; + // Value shrank + for (unsigned j = targetSize; j < sourceSize; ++j) + { + moveToStackTop(depth - 1, 1); + m_context << eth::Instruction::POP; + } + // Value grew + if (targetSize > sourceSize) + moveIntoStack(depth + targetSize - sourceSize, targetSize - sourceSize); + } + } + depth -= sourceSize; + } + 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<ReferenceType const*>(&_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<StructType const*>(&_type)) + for (auto const& member: structType->members()) + { + pushZeroValue(*member.type); + storeInMemoryDynamic(*member.type); + } + else if (auto arrayType = dynamic_cast<ArrayType const*>(&_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, unsigned _itemSize) +{ + solAssert(_stackDepth <= 15, "Stack too deep, try removing local variables."); + for (unsigned j = 0; j < _itemSize; ++j) + for (unsigned i = 0; i < _stackDepth + _itemSize - 1; ++i) + m_context << eth::swapInstruction(1 + i); +} + +void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize) +{ + solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); + for (unsigned j = 0; j < _itemSize; ++j) + for (unsigned i = _stackDepth; i > 0; --i) + m_context << eth::swapInstruction(i + _itemSize - 1); +} + +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<shared_ptr<Type const>> const& _variableTypes) +{ + unsigned size = 0; + for (shared_ptr<Type const> 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; +} + +} +} diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h new file mode 100644 index 00000000..6292e5c7 --- /dev/null +++ b/libsolidity/codegen/CompilerUtils.h @@ -0,0 +1,182 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Routines used by both the compiler and the expression compiler. + */ + +#pragma once + +#include <libsolidity/codegen/CompilerContext.h> +#include <libsolidity/ast/ASTForward.h> + +namespace dev { +namespace solidity { + +class Type; // forward + +class CompilerUtils +{ +public: + CompilerUtils(CompilerContext& _context): m_context(_context) {} + + /// Stores the initial value of the free-memory-pointer at its position; + void initialiseFreeMemoryPointer(); + /// Copies the free memory pointer to the stack. + void fetchFreeMemoryPointer(); + /// Stores the free memory pointer from the stack. + void storeFreeMemoryPointer(); + /// Allocates a number of bytes in memory as given on the stack. + /// Stack pre: <size> + /// Stack post: <mem_start> + void allocateMemory(); + /// Appends code that transforms memptr to (memptr - free_memptr) memptr + void toSizeAfterFreeMemoryPointer(); + + /// Loads data from memory to the stack. + /// @param _offset offset in memory (or calldata) + /// @param _type data type to load + /// @param _fromCalldata if true, load from calldata, not from memory + /// @param _padToWordBoundaries if true, assume the data is padded to word (32 byte) boundaries + /// @returns the number of bytes consumed in memory. + unsigned loadFromMemory( + unsigned _offset, + Type const& _type = IntegerType(256), + bool _fromCalldata = false, + bool _padToWordBoundaries = false + ); + /// Dynamic version of @see loadFromMemory, expects the memory offset on the stack. + /// Stack pre: memory_offset + /// Stack post: value... (memory_offset+length) + void loadFromMemoryDynamic( + Type const& _type, + bool _fromCalldata = false, + bool _padToWordBoundaries = true, + bool _keepUpdatedMemoryOffset = true + ); + /// Stores a 256 bit integer from stack in memory. + /// @param _offset offset in memory + /// @param _type type of the data on the stack + void storeInMemory(unsigned _offset); + /// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack + /// and also updates that. For reference types, only copies the data pointer. Fails for + /// non-memory-references. + /// @param _padToWordBoundaries if true, adds zeros to pad to multiple of 32 bytes. Array elements + /// are always padded (except for byte arrays), regardless of this parameter. + /// Stack pre: memory_offset value... + /// 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: <v1> <v2> ... <vn> <memptr> + /// Stack post: <memptr_updated> + /// 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. + /// @param _encodeAsLibraryTypes if true, encodes for a library function, e.g. does not + /// convert storage pointer types to memory types. + /// @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, + bool _encodeAsLibraryTypes = false + ); + + /// Uses a CALL to the identity contract to perform a memory-to-memory copy. + /// Stack pre: <size> <target> <source> + /// Stack post: + void memoryCopy(); + + /// 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. + /// 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); + + /// Creates a zero-value for the given type and puts it onto the stack. This might allocate + /// memory for memory references. + void pushZeroValue(Type const& _type); + + /// 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 + /// to the top of the stack. + void copyToStackTop(unsigned _stackDepth, unsigned _itemSize); + /// Moves an item that occupies @a _itemSize stack slots and has items occupying @a _stackDepth + /// slots above it to the top of the stack. + void moveToStackTop(unsigned _stackDepth, unsigned _itemSize = 1); + /// Moves @a _itemSize elements past @a _stackDepth other stack elements + void moveIntoStack(unsigned _stackDepth, unsigned _itemSize = 1); + /// 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. + void popStackSlots(size_t _amount); + + template <class T> + static unsigned sizeOnStack(std::vector<T> const& _variables); + static unsigned sizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes); + + /// Appends code that computes tha SHA3 hash of the topmost stack element of 32 byte type. + void computeHashStatic(); + + /// Bytes we need to the start of call data. + /// - The size in bytes of the function (hash) identifier. + static const unsigned dataStartOffset; + + /// Position of the free-memory-pointer in memory; + static const size_t freeMemoryPointer; + +private: + /// Address of the precompiled identity contract. + static const unsigned identityContractAddress; + + /// Stores the given string in memory. + /// Stack pre: mempos + /// Stack post: + void storeStringData(bytesConstRef _data); + + /// 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. + unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries); + + CompilerContext& m_context; +}; + + +template <class T> +unsigned CompilerUtils::sizeOnStack(std::vector<T> const& _variables) +{ + unsigned size = 0; + for (T const& variable: _variables) + size += variable->annotation().type->sizeOnStack(); + return size; +} + +} +} diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp new file mode 100644 index 00000000..3774e731 --- /dev/null +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -0,0 +1,1370 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Solidity AST to EVM bytecode compiler for expressions. + */ + +#include <utility> +#include <numeric> +#include <boost/range/adaptor/reversed.hpp> +#include <libevmcore/Params.h> +#include <libdevcore/Common.h> +#include <libdevcore/SHA3.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/codegen/ExpressionCompiler.h> +#include <libsolidity/codegen/CompilerContext.h> +#include <libsolidity/codegen/CompilerUtils.h> +#include <libsolidity/codegen/LValue.h> + +using namespace std; + +namespace dev +{ +namespace solidity +{ + +void ExpressionCompiler::compile(Expression const& _expression) +{ + _expression.accept(*this); +} + +void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration const& _varDecl) +{ + if (!_varDecl.value()) + return; + TypePointer type = _varDecl.value()->annotation().type; + solAssert(!!type, "Type information not available."); + CompilerContext::LocationSetter locationSetter(m_context, _varDecl); + _varDecl.value()->accept(*this); + + if (_varDecl.annotation().type->dataStoredIn(DataLocation::Storage)) + { + // reference type, only convert value to mobile type and do final conversion in storeValue. + utils().convertType(*type, *type->mobileType()); + type = type->mobileType(); + } + else + { + utils().convertType(*type, *_varDecl.annotation().type); + type = _varDecl.annotation().type; + } + StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true); +} + +void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration const& _varDecl) +{ + solAssert(_varDecl.isConstant(), ""); + _varDecl.value()->accept(*this); + utils().convertType(*_varDecl.value()->annotation().type, *_varDecl.annotation().type); + + // append return + m_context << eth::dupInstruction(_varDecl.annotation().type->sizeOnStack() + 1); + m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); +} + +void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) +{ + solAssert(!_varDecl.isConstant(), ""); + CompilerContext::LocationSetter locationSetter(m_context, _varDecl); + FunctionType accessorType(_varDecl); + + TypePointers const& paramTypes = accessorType.parameterTypes(); + + // retrieve the position of the variable + auto const& location = m_context.storageLocationOfVariable(_varDecl); + m_context << location.first << u256(location.second); + + TypePointer returnType = _varDecl.annotation().type; + + for (size_t i = 0; i < paramTypes.size(); ++i) + { + if (auto mappingType = dynamic_cast<MappingType const*>(returnType.get())) + { + solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); + solAssert( + !paramTypes[i]->isDynamicallySized(), + "Accessors for mapping with dynamically-sized keys not yet implemented." + ); + // pop offset + m_context << eth::Instruction::POP; + // move storage offset to memory. + utils().storeInMemory(32); + // move key to memory. + utils().copyToStackTop(paramTypes.size() - i, 1); + utils().storeInMemory(0); + m_context << u256(64) << u256(0) << eth::Instruction::SHA3; + // push offset + m_context << u256(0); + returnType = mappingType->valueType(); + } + else if (auto arrayType = dynamic_cast<ArrayType const*>(returnType.get())) + { + // pop offset + m_context << eth::Instruction::POP; + utils().copyToStackTop(paramTypes.size() - i + 1, 1); + ArrayUtils(m_context).accessIndex(*arrayType); + returnType = arrayType->baseType(); + } + else + solAssert(false, "Index access is allowed only for \"mapping\" and \"array\" types."); + } + // remove index arguments. + if (paramTypes.size() == 1) + m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::SWAP1; + else if (paramTypes.size() >= 2) + { + m_context << eth::swapInstruction(paramTypes.size()); + m_context << eth::Instruction::POP; + m_context << eth::swapInstruction(paramTypes.size()); + utils().popStackSlots(paramTypes.size() - 1); + } + unsigned retSizeOnStack = 0; + solAssert(accessorType.returnParameterTypes().size() >= 1, ""); + auto const& returnTypes = accessorType.returnParameterTypes(); + if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get())) + { + // remove offset + m_context << eth::Instruction::POP; + auto const& names = accessorType.returnParameterNames(); + // struct + for (size_t i = 0; i < names.size(); ++i) + { + if (returnTypes[i]->category() == Type::Category::Mapping) + continue; + if (auto arrayType = dynamic_cast<ArrayType const*>(returnTypes[i].get())) + if (!arrayType->isByteArray()) + continue; + pair<u256, unsigned> const& offsets = structType->storageOffsetsOfMember(names[i]); + m_context << eth::Instruction::DUP1 << u256(offsets.first) << eth::Instruction::ADD << u256(offsets.second); + TypePointer memberType = structType->memberType(names[i]); + StorageItem(m_context, *memberType).retrieveValue(SourceLocation(), true); + utils().convertType(*memberType, *returnTypes[i]); + utils().moveToStackTop(returnTypes[i]->sizeOnStack()); + retSizeOnStack += returnTypes[i]->sizeOnStack(); + } + // remove slot + m_context << eth::Instruction::POP; + } + else + { + // simple value or array + solAssert(returnTypes.size() == 1, ""); + StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); + utils().convertType(*returnType, *returnTypes.front()); + retSizeOnStack = returnTypes.front()->sizeOnStack(); + } + solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), ""); + solAssert(retSizeOnStack <= 15, "Stack is too deep."); + m_context << eth::dupInstruction(retSizeOnStack + 1); + m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); +} + +bool ExpressionCompiler::visit(Assignment const& _assignment) +{ + CompilerContext::LocationSetter locationSetter(m_context, _assignment); + _assignment.rightHandSide().accept(*this); + // Perform some conversion already. This will convert storage types to memory and literals + // to their actual type, but will not convert e.g. memory to storage. + TypePointer type = _assignment.rightHandSide().annotation().type->closestTemporaryType( + _assignment.leftHandSide().annotation().type + ); + utils().convertType(*_assignment.rightHandSide().annotation().type, *type); + + _assignment.leftHandSide().accept(*this); + solAssert(!!m_currentLValue, "LValue not retrieved."); + + Token::Value op = _assignment.assignmentOperator(); + if (op != Token::Assign) // compound assignment + { + solAssert(_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) + { + utils().copyToStackTop(lvalueSize + itemSize, itemSize); + utils().copyToStackTop(itemSize + lvalueSize, lvalueSize); + // value lvalue_ref value lvalue_ref + } + m_currentLValue->retrieveValue(_assignment.location(), true); + appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.annotation().type); + if (lvalueSize > 0) + { + solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables."); + // value [lvalue_ref] updated_value + for (unsigned i = 0; i < itemSize; ++i) + m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP; + } + } + m_currentLValue->storeValue(*type, _assignment.location()); + m_currentLValue.reset(); + return false; +} + +bool ExpressionCompiler::visit(TupleExpression const& _tuple) +{ + vector<unique_ptr<LValue>> lvalues; + for (auto const& component: _tuple.components()) + if (component) + { + component->accept(*this); + if (_tuple.annotation().lValueRequested) + { + solAssert(!!m_currentLValue, ""); + lvalues.push_back(move(m_currentLValue)); + } + } + else if (_tuple.annotation().lValueRequested) + lvalues.push_back(unique_ptr<LValue>()); + if (_tuple.annotation().lValueRequested) + m_currentLValue.reset(new TupleObject(m_context, move(lvalues))); + return false; +} + +bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) +{ + CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); + //@todo type checking and creating code for an operator should be in the same place: + // the operator should know how to convert itself and to which types it applies, so + // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that + // represents the operator + if (_unaryOperation.annotation().type->category() == Type::Category::IntegerConstant) + { + m_context << _unaryOperation.annotation().type->literalValue(nullptr); + return false; + } + + _unaryOperation.subExpression().accept(*this); + + switch (_unaryOperation.getOperator()) + { + case Token::Not: // ! + m_context << eth::Instruction::ISZERO; + break; + case Token::BitNot: // ~ + m_context << eth::Instruction::NOT; + break; + case Token::After: // after + m_context << eth::Instruction::TIMESTAMP << eth::Instruction::ADD; + break; + case Token::Delete: // delete + solAssert(!!m_currentLValue, "LValue not retrieved."); + m_currentLValue->setToZero(_unaryOperation.location()); + m_currentLValue.reset(); + break; + case Token::Inc: // ++ (pre- or postfix) + case Token::Dec: // -- (pre- or postfix) + solAssert(!!m_currentLValue, "LValue not retrieved."); + m_currentLValue->retrieveValue(_unaryOperation.location()); + if (!_unaryOperation.isPrefixOperation()) + { + // store value for later + solAssert(_unaryOperation.annotation().type->sizeOnStack() == 1, "Stack size != 1 not implemented."); + m_context << eth::Instruction::DUP1; + if (m_currentLValue->sizeOnStack() > 0) + for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); + } + m_context << u256(1); + if (_unaryOperation.getOperator() == Token::Inc) + m_context << eth::Instruction::ADD; + else + m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; + // Stack for prefix: [ref...] (*ref)+-1 + // Stack for postfix: *ref [ref...] (*ref)+-1 + for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); + m_currentLValue->storeValue( + *_unaryOperation.annotation().type, _unaryOperation.location(), + !_unaryOperation.isPrefixOperation()); + m_currentLValue.reset(); + break; + case Token::Add: // + + // unary add, so basically no-op + break; + case Token::Sub: // - + m_context << u256(0) << eth::Instruction::SUB; + break; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid unary operator: " + + string(Token::toString(_unaryOperation.getOperator())))); + } + return false; +} + +bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) +{ + CompilerContext::LocationSetter locationSetter(m_context, _binaryOperation); + Expression const& leftExpression = _binaryOperation.leftExpression(); + Expression const& rightExpression = _binaryOperation.rightExpression(); + solAssert(!!_binaryOperation.annotation().commonType, ""); + Type const& commonType = *_binaryOperation.annotation().commonType; + Token::Value const c_op = _binaryOperation.getOperator(); + + if (c_op == Token::And || c_op == Token::Or) // special case: short-circuiting + appendAndOrOperatorCode(_binaryOperation); + else if (commonType.category() == Type::Category::IntegerConstant) + m_context << commonType.literalValue(nullptr); + else + { + bool cleanupNeeded = commonType.category() == Type::Category::Integer && + (Token::isCompareOp(c_op) || c_op == Token::Div || c_op == Token::Mod); + + // for commutative operators, push the literal as late as possible to allow improved optimization + auto isLiteral = [](Expression const& _e) + { + return dynamic_cast<Literal const*>(&_e) || _e.annotation().type->category() == Type::Category::IntegerConstant; + }; + bool swap = m_optimize && Token::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); + if (swap) + { + leftExpression.accept(*this); + utils().convertType(*leftExpression.annotation().type, commonType, cleanupNeeded); + rightExpression.accept(*this); + utils().convertType(*rightExpression.annotation().type, commonType, cleanupNeeded); + } + else + { + rightExpression.accept(*this); + utils().convertType(*rightExpression.annotation().type, commonType, cleanupNeeded); + leftExpression.accept(*this); + utils().convertType(*leftExpression.annotation().type, commonType, cleanupNeeded); + } + if (Token::isCompareOp(c_op)) + appendCompareOperatorCode(c_op, commonType); + else + appendOrdinaryBinaryOperatorCode(c_op, commonType); + } + + // do not visit the child nodes, we already did that explicitly + return false; +} + +bool ExpressionCompiler::visit(FunctionCall const& _functionCall) +{ + CompilerContext::LocationSetter locationSetter(m_context, _functionCall); + using Location = FunctionType::Location; + if (_functionCall.annotation().isTypeConversion) + { + solAssert(_functionCall.arguments().size() == 1, ""); + solAssert(_functionCall.names().empty(), ""); + Expression const& firstArgument = *_functionCall.arguments().front(); + firstArgument.accept(*this); + utils().convertType(*firstArgument.annotation().type, *_functionCall.annotation().type); + return false; + } + + FunctionTypePointer functionType; + if (_functionCall.annotation().isStructConstructorCall) + { + auto const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type); + auto const& structType = dynamic_cast<StructType const&>(*type.actualType()); + functionType = structType.constructorType(); + } + else + functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type); + + TypePointers const& parameterTypes = functionType->parameterTypes(); + vector<ASTPointer<Expression const>> const& callArguments = _functionCall.arguments(); + vector<ASTPointer<ASTString>> const& callArgumentNames = _functionCall.names(); + if (!functionType->takesArbitraryParameters()) + solAssert(callArguments.size() == parameterTypes.size(), ""); + + vector<ASTPointer<Expression const>> arguments; + if (callArgumentNames.empty()) + // normal arguments + arguments = callArguments; + else + // named arguments + for (auto const& parameterName: functionType->parameterNames()) + { + bool found = false; + for (size_t j = 0; j < callArgumentNames.size() && !found; j++) + if ((found = (parameterName == *callArgumentNames[j]))) + // we found the actual parameter position + arguments.push_back(callArguments[j]); + solAssert(found, ""); + } + + if (_functionCall.annotation().isStructConstructorCall) + { + TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type); + auto const& structType = dynamic_cast<StructType const&>(*type.actualType()); + + m_context << max(u256(32u), structType.memorySize()); + utils().allocateMemory(); + m_context << eth::Instruction::DUP1; + + for (unsigned i = 0; i < arguments.size(); ++i) + { + arguments[i]->accept(*this); + utils().convertType(*arguments[i]->annotation().type, *functionType->parameterTypes()[i]); + utils().storeInMemoryDynamic(*functionType->parameterTypes()[i]); + } + m_context << eth::Instruction::POP; + } + else + { + FunctionType const& function = *functionType; + switch (function.location()) + { + case Location::Internal: + { + // Calling convention: Caller pushes return address and arguments + // Callee removes them and pushes return values + + eth::AssemblyItem returnLabel = m_context.pushNewTag(); + for (unsigned i = 0; i < arguments.size(); ++i) + { + arguments[i]->accept(*this); + utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]); + } + _functionCall.expression().accept(*this); + + m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction); + m_context << returnLabel; + + unsigned returnParametersSize = CompilerUtils::sizeOnStack(function.returnParameterTypes()); + // callee adds return parameters, but removes arguments and return label + m_context.adjustStackOffset(returnParametersSize - CompilerUtils::sizeOnStack(function.parameterTypes()) - 1); + break; + } + case Location::External: + case Location::CallCode: + case Location::Bare: + case Location::BareCallCode: + _functionCall.expression().accept(*this); + appendExternalFunctionCall(function, arguments); + break; + case Location::Creation: + { + _functionCall.expression().accept(*this); + solAssert(!function.gasSet(), "Gas limit set for contract creation."); + solAssert(function.returnParameterTypes().size() == 1, ""); + TypePointers argumentTypes; + for (auto const& arg: arguments) + { + arg->accept(*this); + argumentTypes.push_back(arg->annotation().type); + } + ContractDefinition const& contract = + dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition(); + // copy the contract's code into memory + eth::Assembly const& assembly = m_context.compiledContract(contract); + utils().fetchFreeMemoryPointer(); + // pushes size + eth::AssemblyItem subroutine = m_context.addSubroutine(assembly); + m_context << eth::Instruction::DUP1 << subroutine; + m_context << eth::Instruction::DUP4 << eth::Instruction::CODECOPY; + + m_context << eth::Instruction::ADD; + utils().encodeToMemory(argumentTypes, function.parameterTypes()); + // now on stack: memory_end_ptr + // need: size, offset, endowment + utils().toSizeAfterFreeMemoryPointer(); + if (function.valueSet()) + m_context << eth::dupInstruction(3); + else + m_context << u256(0); + m_context << eth::Instruction::CREATE; + if (function.valueSet()) + m_context << eth::swapInstruction(1) << eth::Instruction::POP; + break; + } + case Location::SetGas: + { + // stack layout: contract_address function_id [gas] [value] + _functionCall.expression().accept(*this); + + arguments.front()->accept(*this); + utils().convertType(*arguments.front()->annotation().type, 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); + if (stackDepth > 0) + m_context << eth::swapInstruction(stackDepth); + if (function.gasSet()) + m_context << eth::Instruction::POP; + break; + } + case Location::SetValue: + // stack layout: contract_address function_id [gas] [value] + _functionCall.expression().accept(*this); + // Note that function is not the original function, but the ".value" function. + // Its values of gasSet and valueSet is equal to the original function's though. + if (function.valueSet()) + m_context << eth::Instruction::POP; + arguments.front()->accept(*this); + break; + case Location::Send: + _functionCall.expression().accept(*this); + m_context << u256(0); // do not send gas (there still is the stipend) + arguments.front()->accept(*this); + utils().convertType( + *arguments.front()->annotation().type, + *function.parameterTypes().front(), true + ); + appendExternalFunctionCall( + FunctionType( + TypePointers{}, + TypePointers{}, + strings(), + strings(), + Location::Bare, + false, + nullptr, + true, + true + ), + {} + ); + break; + case Location::Suicide: + arguments.front()->accept(*this); + utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); + m_context << eth::Instruction::SUICIDE; + break; + case Location::SHA3: + { + TypePointers argumentTypes; + for (auto const& arg: arguments) + { + arg->accept(*this); + argumentTypes.push_back(arg->annotation().type); + } + utils().fetchFreeMemoryPointer(); + utils().encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true); + utils().toSizeAfterFreeMemoryPointer(); + m_context << eth::Instruction::SHA3; + break; + } + case Location::Log0: + case Location::Log1: + case Location::Log2: + case Location::Log3: + case Location::Log4: + { + unsigned logNumber = int(function.location()) - int(Location::Log0); + for (unsigned arg = logNumber; arg > 0; --arg) + { + arguments[arg]->accept(*this); + utils().convertType(*arguments[arg]->annotation().type, *function.parameterTypes()[arg], true); + } + arguments.front()->accept(*this); + utils().fetchFreeMemoryPointer(); + utils().encodeToMemory( + {arguments.front()->annotation().type}, + {function.parameterTypes().front()}, + false, + true); + utils().toSizeAfterFreeMemoryPointer(); + m_context << eth::logInstruction(logNumber); + break; + } + case Location::Event: + { + _functionCall.expression().accept(*this); + auto const& event = dynamic_cast<EventDefinition const&>(function.declaration()); + unsigned numIndexed = 0; + // All indexed arguments go to the stack + for (unsigned arg = arguments.size(); arg > 0; --arg) + if (event.parameters()[arg - 1]->isIndexed()) + { + ++numIndexed; + arguments[arg - 1]->accept(*this); + utils().convertType( + *arguments[arg - 1]->annotation().type, + *function.parameterTypes()[arg - 1], + true + ); + } + if (!event.isAnonymous()) + { + m_context << u256(h256::Arith(dev::sha3(function.externalSignature()))); + ++numIndexed; + } + solAssert(numIndexed <= 4, "Too many indexed arguments."); + // Copy all non-indexed arguments to memory (data) + // Memory position is only a hack and should be removed once we have free memory pointer. + TypePointers nonIndexedArgTypes; + TypePointers nonIndexedParamTypes; + for (unsigned arg = 0; arg < arguments.size(); ++arg) + if (!event.parameters()[arg]->isIndexed()) + { + arguments[arg]->accept(*this); + nonIndexedArgTypes.push_back(arguments[arg]->annotation().type); + nonIndexedParamTypes.push_back(function.parameterTypes()[arg]); + } + utils().fetchFreeMemoryPointer(); + utils().encodeToMemory(nonIndexedArgTypes, nonIndexedParamTypes); + // need: topic1 ... topicn memsize memstart + utils().toSizeAfterFreeMemoryPointer(); + m_context << eth::logInstruction(numIndexed); + break; + } + case Location::BlockHash: + { + arguments[0]->accept(*this); + utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true); + m_context << eth::Instruction::BLOCKHASH; + break; + } + case Location::ECRecover: + case Location::SHA256: + case Location::RIPEMD160: + { + _functionCall.expression().accept(*this); + static const map<Location, u256> contractAddresses{{Location::ECRecover, 1}, + {Location::SHA256, 2}, + {Location::RIPEMD160, 3}}; + m_context << contractAddresses.find(function.location())->second; + for (unsigned i = function.sizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); + appendExternalFunctionCall(function, arguments); + break; + } + case Location::ByteArrayPush: + case Location::ArrayPush: + { + _functionCall.expression().accept(*this); + solAssert(function.parameterTypes().size() == 1, ""); + solAssert(!!function.parameterTypes()[0], ""); + TypePointer const& paramType = function.parameterTypes()[0]; + shared_ptr<ArrayType> arrayType = + function.location() == Location::ArrayPush ? + make_shared<ArrayType>(DataLocation::Storage, paramType) : + make_shared<ArrayType>(DataLocation::Storage); + // get the current length + ArrayUtils(m_context).retrieveLength(*arrayType); + m_context << eth::Instruction::DUP1; + // stack: ArrayReference currentLength currentLength + m_context << u256(1) << eth::Instruction::ADD; + // stack: ArrayReference currentLength newLength + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP2; + ArrayUtils(m_context).resizeDynamicArray(*arrayType); + m_context << eth::Instruction::SWAP2 << eth::Instruction::SWAP1; + // stack: newLength ArrayReference oldLength + ArrayUtils(m_context).accessIndex(*arrayType, false); + + // stack: newLength storageSlot slotOffset + arguments[0]->accept(*this); + // stack: newLength storageSlot slotOffset argValue + TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); + utils().convertType(*arguments[0]->annotation().type, *type); + utils().moveToStackTop(1 + type->sizeOnStack()); + utils().moveToStackTop(1 + type->sizeOnStack()); + // stack: newLength argValue storageSlot slotOffset + if (function.location() == Location::ArrayPush) + StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true); + else + StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); + break; + } + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid function type.")); + } + } + return false; +} + +bool ExpressionCompiler::visit(NewExpression const&) +{ + // code is created for the function call (CREATION) only + return false; +} + +void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) +{ + CompilerContext::LocationSetter locationSetter(m_context, _memberAccess); + ASTString const& member = _memberAccess.memberName(); + switch (_memberAccess.expression().annotation().type->category()) + { + case Type::Category::Contract: + { + bool alsoSearchInteger = false; + ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type); + if (type.isSuper()) + { + solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved."); + m_context << m_context.superFunctionEntryLabel( + dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration), + type.contractDefinition() + ).pushTag(); + } + else + { + // ordinary contract type + if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration) + { + u256 identifier; + if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration)) + identifier = FunctionType(*variable).externalIdentifier(); + else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration)) + identifier = FunctionType(*function).externalIdentifier(); + else + solAssert(false, "Contract member is neither variable nor function."); + utils().convertType(type, IntegerType(0, IntegerType::Modifier::Address), true); + m_context << identifier; + } + else + // not found in contract, search in members inherited from address + alsoSearchInteger = true; + } + if (!alsoSearchInteger) + break; + } + case Type::Category::Integer: + if (member == "balance") + { + utils().convertType( + *_memberAccess.expression().annotation().type, + IntegerType(0, IntegerType::Modifier::Address), + true + ); + m_context << eth::Instruction::BALANCE; + } + else if ((set<string>{"send", "call", "callcode"}).count(member)) + utils().convertType( + *_memberAccess.expression().annotation().type, + IntegerType(0, IntegerType::Modifier::Address), + true + ); + else + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); + break; + case Type::Category::Function: + solAssert(!!_memberAccess.expression().annotation().type->memberType(member), + "Invalid member access to function."); + break; + case Type::Category::Magic: + // we can ignore the kind of magic and only look at the name of the member + if (member == "coinbase") + m_context << eth::Instruction::COINBASE; + else if (member == "timestamp") + m_context << eth::Instruction::TIMESTAMP; + else if (member == "difficulty") + m_context << eth::Instruction::DIFFICULTY; + else if (member == "number") + m_context << eth::Instruction::NUMBER; + else if (member == "gaslimit") + m_context << eth::Instruction::GASLIMIT; + else if (member == "sender") + m_context << eth::Instruction::CALLER; + else if (member == "value") + m_context << eth::Instruction::CALLVALUE; + else if (member == "origin") + m_context << eth::Instruction::ORIGIN; + else if (member == "gas") + m_context << eth::Instruction::GAS; + else if (member == "gasprice") + m_context << eth::Instruction::GASPRICE; + else if (member == "data") + m_context << u256(0) << eth::Instruction::CALLDATASIZE; + else if (member == "sig") + m_context << u256(0) << eth::Instruction::CALLDATALOAD + << (u256(0xffffffff) << (256 - 32)) << eth::Instruction::AND; + else + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member.")); + break; + case Type::Category::Struct: + { + StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.expression().annotation().type); + switch (type.location()) + { + case DataLocation::Storage: + { + pair<u256, unsigned> const& offsets = type.storageOffsetsOfMember(member); + m_context << offsets.first << eth::Instruction::ADD << u256(offsets.second); + setLValueToStorageItem(_memberAccess); + break; + } + case DataLocation::Memory: + { + m_context << type.memoryOffsetOfMember(member) << eth::Instruction::ADD; + setLValue<MemoryItem>(_memberAccess, *_memberAccess.annotation().type); + break; + } + default: + solAssert(false, "Illegal data location for struct."); + } + break; + } + case Type::Category::Enum: + { + EnumType const& type = dynamic_cast<EnumType const&>(*_memberAccess.expression().annotation().type); + m_context << type.memberValue(_memberAccess.memberName()); + break; + } + case Type::Category::TypeType: + { + TypeType const& type = dynamic_cast<TypeType const&>(*_memberAccess.expression().annotation().type); + solAssert( + !type.members().membersByName(_memberAccess.memberName()).empty(), + "Invalid member access to " + type.toString(false) + ); + + if (dynamic_cast<ContractType const*>(type.actualType().get())) + { + auto const& funType = dynamic_cast<FunctionType const&>(*_memberAccess.annotation().type); + if (funType.location() != FunctionType::Location::Internal) + m_context << funType.externalIdentifier(); + else + { + 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(); + } + } + else if (auto enumType = dynamic_cast<EnumType const*>(type.actualType().get())) + m_context << enumType->memberValue(_memberAccess.memberName()); + break; + } + case Type::Category::Array: + { + auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type); + if (member == "length") + { + if (!type.isDynamicallySized()) + { + utils().popStackElement(type); + m_context << type.length(); + } + else + switch (type.location()) + { + case DataLocation::CallData: + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + break; + case DataLocation::Storage: + setLValue<StorageArrayLength>(_memberAccess, type); + break; + case DataLocation::Memory: + m_context << eth::Instruction::MLOAD; + break; + } + } + else if (member == "push") + { + solAssert( + type.isDynamicallySized() && type.location() == DataLocation::Storage, + "Tried to use .push() on a non-dynamically sized array" + ); + } + else + solAssert(false, "Illegal array member."); + break; + } + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to unknown type.")); + } +} + +bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) +{ + CompilerContext::LocationSetter locationSetter(m_context, _indexAccess); + _indexAccess.baseExpression().accept(*this); + + Type const& baseType = *_indexAccess.baseExpression().annotation().type; + + if (baseType.category() == Type::Category::Mapping) + { + // stack: storage_base_ref + TypePointer keyType = dynamic_cast<MappingType const&>(baseType).keyType(); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + if (keyType->isDynamicallySized()) + { + _indexAccess.indexExpression()->accept(*this); + utils().fetchFreeMemoryPointer(); + // stack: base index mem + // note: the following operations must not allocate memory! + utils().encodeToMemory( + TypePointers{_indexAccess.indexExpression()->annotation().type}, + TypePointers{keyType}, + false, + true + ); + m_context << eth::Instruction::SWAP1; + utils().storeInMemoryDynamic(IntegerType(256)); + utils().toSizeAfterFreeMemoryPointer(); + } + else + { + m_context << u256(0); // memory position + appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression()); + m_context << eth::Instruction::SWAP1; + solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); + utils().storeInMemoryDynamic(IntegerType(256)); + m_context << u256(0); + } + m_context << eth::Instruction::SHA3; + m_context << u256(0); + setLValueToStorageItem(_indexAccess); + } + else if (baseType.category() == Type::Category::Array) + { + ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + _indexAccess.indexExpression()->accept(*this); + // stack layout: <base_ref> [<length>] <index> + ArrayUtils(m_context).accessIndex(arrayType); + switch (arrayType.location()) + { + case DataLocation::Storage: + if (arrayType.isByteArray()) + { + solAssert(!arrayType.isString(), "Index access to string is not allowed."); + setLValue<StorageByteArrayElement>(_indexAccess); + } + else + setLValueToStorageItem(_indexAccess); + break; + case DataLocation::Memory: + setLValue<MemoryItem>(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); + 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."); + if (arrayType.baseType()->isValueType()) + CompilerUtils(m_context).loadFromMemoryDynamic( + *arrayType.baseType(), + true, + !arrayType.isByteArray(), + false + ); + break; + } + } + else if (baseType.category() == Type::Category::TypeType) + { + solAssert(baseType.sizeOnStack() == 0, ""); + solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, ""); + // no-op - this seems to be a lone array type (`structType[];`) + } + else + solAssert(false, "Index access only allowed for mappings or arrays."); + + return false; +} + +void ExpressionCompiler::endVisit(Identifier const& _identifier) +{ + CompilerContext::LocationSetter locationSetter(m_context, _identifier); + Declaration const* declaration = _identifier.annotation().referencedDeclaration; + if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration)) + { + switch (magicVar->type(_identifier.annotation().contractScope)->category()) + { + case Type::Category::Contract: + // "this" or "super" + if (!dynamic_cast<ContractType const&>(*magicVar->type(_identifier.annotation().contractScope)).isSuper()) + m_context << eth::Instruction::ADDRESS; + break; + case Type::Category::Integer: + // "now" + m_context << eth::Instruction::TIMESTAMP; + break; + default: + break; + } + } + else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) + m_context << m_context.virtualFunctionEntryLabel(*functionDef).pushTag(); + else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration)) + { + if (!variable->isConstant()) + setLValueFromDeclaration(*declaration, _identifier); + else + { + variable->value()->accept(*this); + utils().convertType(*variable->value()->annotation().type, *variable->annotation().type); + } + } + else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) + { + if (contract->isLibrary()) + //@todo name should be unique, change once we have module management + m_context.appendLibraryAddress(contract->name()); + } + else if (dynamic_cast<EventDefinition const*>(declaration)) + { + // no-op + } + else if (dynamic_cast<EnumDefinition const*>(declaration)) + { + // no-op + } + else if (dynamic_cast<StructDefinition const*>(declaration)) + { + // no-op + } + else + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context.")); + } +} + +void ExpressionCompiler::endVisit(Literal const& _literal) +{ + CompilerContext::LocationSetter locationSetter(m_context, _literal); + TypePointer type = _literal.annotation().type; + switch (type->category()) + { + case Type::Category::IntegerConstant: + case Type::Category::Bool: + m_context << type->literalValue(&_literal); + break; + case Type::Category::StringLiteral: + break; // will be done during conversion + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer, boolean and string literals implemented for now.")); + } +} + +void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryOperation) +{ + Token::Value const c_op = _binaryOperation.getOperator(); + solAssert(c_op == Token::Or || c_op == Token::And, ""); + + _binaryOperation.leftExpression().accept(*this); + m_context << eth::Instruction::DUP1; + if (c_op == Token::And) + m_context << eth::Instruction::ISZERO; + eth::AssemblyItem endLabel = m_context.appendConditionalJump(); + m_context << eth::Instruction::POP; + _binaryOperation.rightExpression().accept(*this); + m_context << endLabel; +} + +void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) +{ + if (_operator == Token::Equal || _operator == Token::NotEqual) + { + m_context << eth::Instruction::EQ; + if (_operator == Token::NotEqual) + m_context << eth::Instruction::ISZERO; + } + else + { + bool isSigned = false; + if (auto type = dynamic_cast<IntegerType const*>(&_type)) + isSigned = type->isSigned(); + + switch (_operator) + { + case Token::GreaterThanOrEqual: + m_context << + (isSigned ? eth::Instruction::SLT : eth::Instruction::LT) << + eth::Instruction::ISZERO; + break; + case Token::LessThanOrEqual: + m_context << + (isSigned ? eth::Instruction::SGT : eth::Instruction::GT) << + eth::Instruction::ISZERO; + break; + case Token::GreaterThan: + m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT); + break; + case Token::LessThan: + m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT); + break; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown comparison operator.")); + } + } +} + +void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type) +{ + if (Token::isArithmeticOp(_operator)) + appendArithmeticOperatorCode(_operator, _type); + else if (Token::isBitOp(_operator)) + appendBitOperatorCode(_operator); + else if (Token::isShiftOp(_operator)) + appendShiftOperatorCode(_operator); + else + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown binary operator.")); +} + +void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) +{ + IntegerType const& type = dynamic_cast<IntegerType const&>(_type); + bool const c_isSigned = type.isSigned(); + + switch (_operator) + { + case Token::Add: + m_context << eth::Instruction::ADD; + break; + case Token::Sub: + m_context << eth::Instruction::SUB; + break; + case Token::Mul: + m_context << eth::Instruction::MUL; + break; + case Token::Div: + m_context << (c_isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); + break; + case Token::Mod: + m_context << (c_isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); + break; + case Token::Exp: + m_context << eth::Instruction::EXP; + break; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown arithmetic operator.")); + } +} + +void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) +{ + switch (_operator) + { + case Token::BitOr: + m_context << eth::Instruction::OR; + break; + case Token::BitAnd: + m_context << eth::Instruction::AND; + break; + case Token::BitXor: + m_context << eth::Instruction::XOR; + break; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown bit operator.")); + } +} + +void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) +{ + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Shift operators not yet implemented.")); + switch (_operator) + { + case Token::SHL: + break; + case Token::SAR: + break; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown shift operator.")); + } +} + +void ExpressionCompiler::appendExternalFunctionCall( + FunctionType const& _functionType, + vector<ASTPointer<Expression const>> const& _arguments +) +{ + solAssert( + _functionType.takesArbitraryParameters() || + _arguments.size() == _functionType.parameterTypes().size(), "" + ); + + // Assumed stack content here: + // <stack top> + // value [if _functionType.valueSet()] + // gas [if _functionType.gasSet()] + // function identifier [unless bare] + // contract address + + unsigned gasValueSize = (_functionType.gasSet() ? 1 : 0) + (_functionType.valueSet() ? 1 : 0); + + unsigned contractStackPos = m_context.currentToBaseStackOffset(1 + gasValueSize + (_functionType.isBareCall() ? 0 : 1)); + unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize); + unsigned valueStackPos = m_context.currentToBaseStackOffset(1); + + using FunctionKind = FunctionType::Location; + FunctionKind funKind = _functionType.location(); + bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode; + bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode; + + unsigned retSize = 0; + if (returnSuccessCondition) + retSize = 0; // return value actually is success condition + else + for (auto const& retType: _functionType.returnParameterTypes()) + { + solAssert(retType->calldataEncodedSize() > 0, "Unable to return dynamic type from external call."); + retSize += retType->calldataEncodedSize(); + } + + // Evaluate arguments. + TypePointers argumentTypes; + bool manualFunctionId = + (funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode) && + !_arguments.empty() && + _arguments.front()->annotation().type->mobileType()->calldataEncodedSize(false) == + CompilerUtils::dataStartOffset; + if (manualFunctionId) + { + // If we have a BareCall or BareCallCode and the first type has exactly 4 bytes, use it as + // function identifier. + _arguments.front()->accept(*this); + utils().convertType( + *_arguments.front()->annotation().type, + IntegerType(8 * CompilerUtils::dataStartOffset), + true + ); + for (unsigned i = 0; i < gasValueSize; ++i) + m_context << eth::swapInstruction(gasValueSize - i); + gasStackPos++; + valueStackPos++; + } + for (size_t i = manualFunctionId ? 1 : 0; i < _arguments.size(); ++i) + { + _arguments[i]->accept(*this); + argumentTypes.push_back(_arguments[i]->annotation().type); + } + + // Copy function identifier to memory. + utils().fetchFreeMemoryPointer(); + if (!_functionType.isBareCall() || manualFunctionId) + { + m_context << eth::dupInstruction(2 + gasValueSize + CompilerUtils::sizeOnStack(argumentTypes)); + 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). + utils().encodeToMemory( + argumentTypes, + _functionType.parameterTypes(), + _functionType.padArguments(), + _functionType.takesArbitraryParameters(), + isCallCode + ); + + // Stack now: + // <stack top> + // input_memory_end + // value [if _functionType.valueSet()] + // gas [if _functionType.gasSet()] + // function identifier [unless bare] + // contract address + + // Output data will replace input data. + // put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input> + m_context << u256(retSize); + utils().fetchFreeMemoryPointer(); + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SUB; + m_context << eth::Instruction::DUP2; + + // CALL arguments: outSize, outOff, inSize, inOff (already present up to here) + // value, addr, gas (stack top) + if (_functionType.valueSet()) + m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos)); + else + m_context << u256(0); + m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(contractStackPos)); + + if (_functionType.gasSet()) + m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); + else + { + // send all gas except the amount needed to execute "SUB" and "CALL" + // @todo this retains too much gas for now, needs to be fine-tuned. + u256 gasNeededByCaller = eth::c_callGas + 10; + if (_functionType.valueSet()) + gasNeededByCaller += eth::c_callValueTransferGas; + if (!isCallCode) + gasNeededByCaller += eth::c_callNewAccountGas; // we never know + m_context << + gasNeededByCaller << + eth::Instruction::GAS << + eth::Instruction::SUB; + } + if (isCallCode) + m_context << eth::Instruction::CALLCODE; + else + m_context << eth::Instruction::CALL; + + unsigned remainsSize = + 2 + // contract address, input_memory_end + _functionType.valueSet() + + _functionType.gasSet() + + (!_functionType.isBareCall() || manualFunctionId); + + if (returnSuccessCondition) + m_context << eth::swapInstruction(remainsSize); + else + { + //Propagate error condition (if CALL pushes 0 on stack). + m_context << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(m_context.errorTag()); + } + + utils().popStackSlots(remainsSize); + + if (returnSuccessCondition) + { + // already there + } + else if (funKind == FunctionKind::RIPEMD160) + { + // fix: built-in contract returns right-aligned data + utils().fetchFreeMemoryPointer(); + utils().loadFromMemoryDynamic(IntegerType(160), false, true, false); + utils().convertType(IntegerType(160), FixedBytesType(20)); + } + else if (!_functionType.returnParameterTypes().empty()) + { + utils().fetchFreeMemoryPointer(); + bool memoryNeeded = false; + for (auto const& retType: _functionType.returnParameterTypes()) + { + utils().loadFromMemoryDynamic(*retType, false, true, true); + if (dynamic_cast<ReferenceType const*>(retType.get())) + memoryNeeded = true; + } + if (memoryNeeded) + utils().storeFreeMemoryPointer(); + else + m_context << eth::Instruction::POP; + } +} + +void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) +{ + solAssert(_expectedType.isValueType(), "Not implemented for non-value types."); + _expression.accept(*this); + utils().convertType(*_expression.annotation().type, _expectedType, true); + utils().storeInMemoryDynamic(_expectedType); +} + +void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression) +{ + if (m_context.isLocalVariable(&_declaration)) + setLValue<StackVariable>(_expression, dynamic_cast<VariableDeclaration const&>(_declaration)); + else if (m_context.isStateVariable(&_declaration)) + setLValue<StorageItem>(_expression, dynamic_cast<VariableDeclaration const&>(_declaration)); + else + BOOST_THROW_EXCEPTION(InternalCompilerError() + << errinfo_sourceLocation(_expression.location()) + << errinfo_comment("Identifier type not supported or identifier not found.")); +} + +void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) +{ + setLValue<StorageItem>(_expression, *_expression.annotation().type); +} + +CompilerUtils ExpressionCompiler::utils() +{ + return CompilerUtils(m_context); +} + +} +} diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h new file mode 100644 index 00000000..379aa65a --- /dev/null +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -0,0 +1,138 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @author Gav Wood <g@ethdev.com> + * @date 2014 + * Solidity AST to EVM bytecode compiler for expressions. + */ + +#include <functional> +#include <memory> +#include <boost/noncopyable.hpp> +#include <libdevcore/Common.h> +#include <libevmasm/SourceLocation.h> +#include <libsolidity/ast/ASTVisitor.h> +#include <libsolidity/codegen/LValue.h> +#include <libsolidity/interface/Utils.h> + +namespace dev { +namespace eth +{ +class AssemblyItem; // forward +} +namespace solidity { + +// forward declarations +class CompilerContext; +class CompilerUtils; +class Type; +class IntegerType; +class ArrayType; + +/** + * Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream + * of EVM instructions. It needs a compiler context that is the same for the whole compilation + * unit. + */ +class ExpressionCompiler: private ASTConstVisitor +{ +public: + /// Appends code for a State Variable accessor function + static void appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize = false); + + explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimize = false): + m_optimize(_optimize), m_context(_compilerContext) {} + + /// Compile the given @a _expression and leave its value on the stack. + void compile(Expression const& _expression); + + /// Appends code to set a state variable to its initial value/expression. + void appendStateVariableInitialization(VariableDeclaration const& _varDecl); + + /// Appends code for a State Variable accessor function + void appendStateVariableAccessor(VariableDeclaration const& _varDecl); + + /// Appends code for a Constant State Variable accessor function + void appendConstStateVariableAccessor(const VariableDeclaration& _varDecl); + +private: + virtual bool visit(Assignment const& _assignment) override; + virtual bool visit(TupleExpression const& _tuple) override; + virtual bool visit(UnaryOperation const& _unaryOperation) override; + virtual bool visit(BinaryOperation const& _binaryOperation) override; + virtual bool visit(FunctionCall const& _functionCall) override; + virtual bool visit(NewExpression const& _newExpression) override; + virtual void endVisit(MemberAccess const& _memberAccess) override; + virtual bool visit(IndexAccess const& _indexAccess) override; + virtual void endVisit(Identifier const& _identifier) override; + virtual void endVisit(Literal const& _literal) override; + + ///@{ + ///@name Append code for various operator types + void appendAndOrOperatorCode(BinaryOperation const& _binaryOperation); + void appendCompareOperatorCode(Token::Value _operator, Type const& _type); + void appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type); + + void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type); + void appendBitOperatorCode(Token::Value _operator); + void appendShiftOperatorCode(Token::Value _operator); + /// @} + + /// Appends code to call a function of the given type with the given arguments. + void appendExternalFunctionCall( + FunctionType const& _functionType, + std::vector<ASTPointer<Expression const>> const& _arguments + ); + /// 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); + + /// Sets the current LValue to a new one (of the appropriate type) from the given declaration. + /// Also retrieves the value if it was not requested by @a _expression. + void setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression); + /// Sets the current LValue to a StorageItem holding the type of @a _expression. The reference is assumed + /// to be on the stack. + /// Also retrieves the value if it was not requested by @a _expression. + void setLValueToStorageItem(Expression const& _expression); + /// Sets the current LValue to a new LValue constructed from the arguments. + /// Also retrieves the value if it was not requested by @a _expression. + template <class _LValueType, class... _Arguments> + 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<LValue> m_currentLValue; + +}; + +template <class _LValueType, class... _Arguments> +void ExpressionCompiler::setLValue(Expression const& _expression, _Arguments const&... _arguments) +{ + solAssert(!m_currentLValue, "Current LValue not reset before trying to set new one."); + std::unique_ptr<_LValueType> lvalue(new _LValueType(m_context, _arguments...)); + if (_expression.annotation().lValueRequested) + m_currentLValue = move(lvalue); + else + lvalue->retrieveValue(_expression.location(), true); +} + +} +} diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp new file mode 100644 index 00000000..574d42f8 --- /dev/null +++ b/libsolidity/codegen/LValue.cpp @@ -0,0 +1,546 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * LValues for use in the expresison compiler. + */ + +#include <libsolidity/codegen/LValue.h> +#include <libevmcore/Instruction.h> +#include <libsolidity/ast/Types.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/codegen/CompilerUtils.h> + +using namespace std; +using namespace dev; +using namespace solidity; + + +StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration): + LValue(_compilerContext, _declaration.annotation().type.get()), + m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)), + m_size(m_dataType->sizeOnStack()) +{ +} + +void StackVariable::retrieveValue(SourceLocation const& _location, bool) const +{ + unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset); + if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_location) << + errinfo_comment("Stack too deep, try removing local variables.") + ); + solAssert(stackPos + 1 >= m_size, "Size and stack pos mismatch."); + for (unsigned i = 0; i < m_size; ++i) + m_context << eth::dupInstruction(stackPos + 1); +} + +void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const +{ + unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; + if (stackDiff > 16) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_location) << + errinfo_comment("Stack too deep, try removing local variables.") + ); + else if (stackDiff > 0) + for (unsigned i = 0; i < m_size; ++i) + m_context << eth::swapInstruction(stackDiff) << eth::Instruction::POP; + if (!_move) + retrieveValue(_location); +} + +void StackVariable::setToZero(SourceLocation const& _location, bool) const +{ + CompilerUtils(m_context).pushZeroValue(*m_dataType); + storeValue(*m_dataType, _location, true); +} + +MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded): + LValue(_compilerContext, &_type), + m_padded(_padded) +{ +} + +void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const +{ + if (m_dataType->isValueType()) + { + if (!_remove) + m_context << eth::Instruction::DUP1; + CompilerUtils(m_context).loadFromMemoryDynamic(*m_dataType, false, m_padded, false); + } + else + m_context << eth::Instruction::MLOAD; +} + +void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const +{ + CompilerUtils utils(m_context); + if (m_dataType->isValueType()) + { + solAssert(_sourceType.isValueType(), ""); + utils.moveIntoStack(_sourceType.sizeOnStack()); + utils.convertType(_sourceType, *m_dataType, true); + if (!_move) + { + utils.moveToStackTop(m_dataType->sizeOnStack()); + utils.copyToStackTop(2, m_dataType->sizeOnStack()); + } + utils.storeInMemoryDynamic(*m_dataType, m_padded); + m_context << eth::Instruction::POP; + } + else + { + solAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory."); + + solAssert(m_dataType->sizeOnStack() == 1, ""); + if (!_move) + m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; + // stack: [value] value lvalue + // only store the reference + m_context << eth::Instruction::MSTORE; + } +} + +void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const +{ + CompilerUtils utils(m_context); + if (!_removeReference) + m_context << eth::Instruction::DUP1; + utils.pushZeroValue(*m_dataType); + utils.storeInMemoryDynamic(*m_dataType, m_padded); + m_context << eth::Instruction::POP; +} + +StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration): + StorageItem(_compilerContext, *_declaration.annotation().type) +{ + auto const& location = m_context.storageLocationOfVariable(_declaration); + m_context << location.first << u256(location.second); +} + +StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): + LValue(_compilerContext, &_type) +{ + if (m_dataType->isValueType()) + { + solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); + solAssert(m_dataType->storageSize() == 1, "Invalid storage size."); + } +} + +void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const +{ + // stack: storage_key storage_offset + if (!m_dataType->isValueType()) + { + solAssert(m_dataType->sizeOnStack() == 1, "Invalid storage ref size."); + if (_remove) + m_context << eth::Instruction::POP; // remove byte offset + else + m_context << eth::Instruction::DUP2; + return; + } + if (!_remove) + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); + if (m_dataType->storageBytes() == 32) + m_context << eth::Instruction::POP << eth::Instruction::SLOAD; + else + { + m_context + << eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 + << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; + if (m_dataType->category() == Type::Category::FixedBytes) + m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << eth::Instruction::MUL; + else if ( + m_dataType->category() == Type::Category::Integer && + dynamic_cast<IntegerType const&>(*m_dataType).isSigned() + ) + m_context << u256(m_dataType->storageBytes() - 1) << eth::Instruction::SIGNEXTEND; + else + m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << eth::Instruction::AND; + } +} + +void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const +{ + CompilerUtils utils(m_context); + // stack: value storage_key storage_offset + if (m_dataType->isValueType()) + { + solAssert(m_dataType->storageBytes() <= 32, "Invalid storage bytes size."); + solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size."); + if (m_dataType->storageBytes() == 32) + { + // offset should be zero + m_context << eth::Instruction::POP; + if (!_move) + m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; + m_context << eth::Instruction::SSTORE; + } + else + { + // OR the value into the other values in the storage slot + m_context << u256(0x100) << eth::Instruction::EXP; + // stack: value storage_ref multiplier + // fetch old value + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: value storege_ref multiplier old_full_value + // clear bytes in old value + m_context + << eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) + << eth::Instruction::MUL; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: value storage_ref multiplier cleared_value + m_context + << eth::Instruction::SWAP1 << eth::Instruction::DUP4; + // stack: value storage_ref cleared_value multiplier value + if (m_dataType->category() == Type::Category::FixedBytes) + m_context + << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes())) + << eth::Instruction::SWAP1 << eth::Instruction::DIV; + else if ( + m_dataType->category() == Type::Category::Integer && + dynamic_cast<IntegerType const&>(*m_dataType).isSigned() + ) + // remove the higher order bits + m_context + << (u256(1) << (8 * (32 - m_dataType->storageBytes()))) + << eth::Instruction::SWAP1 + << eth::Instruction::DUP2 + << eth::Instruction::MUL + << eth::Instruction::DIV; + m_context << eth::Instruction::MUL << eth::Instruction::OR; + // stack: value storage_ref updated_value + m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + if (_move) + m_context << eth::Instruction::POP; + } + } + else + { + solAssert( + _sourceType.category() == m_dataType->category(), + "Wrong type conversation for assignment."); + if (m_dataType->category() == Type::Category::Array) + { + m_context << eth::Instruction::POP; // remove byte offset + ArrayUtils(m_context).copyArrayToStorage( + dynamic_cast<ArrayType const&>(*m_dataType), + dynamic_cast<ArrayType const&>(_sourceType) + ); + if (_move) + m_context << eth::Instruction::POP; + } + else if (m_dataType->category() == Type::Category::Struct) + { + // stack layout: source_ref target_ref target_offset + // note that we have structs, so offset should be zero and are ignored + m_context << eth::Instruction::POP; + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + auto const& sourceType = dynamic_cast<StructType const&>(_sourceType); + solAssert( + structType.structDefinition() == sourceType.structDefinition(), + "Struct assignment with conversion." + ); + solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported."); + for (auto const& member: structType.members()) + { + // assign each member that is not a mapping + TypePointer const& memberType = member.type; + if (memberType->category() == Type::Category::Mapping) + continue; + TypePointer sourceMemberType = sourceType.memberType(member.name); + if (sourceType.location() == DataLocation::Storage) + { + // stack layout: source_ref target_ref + pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name); + m_context << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD; + m_context << u256(offsets.second); + // stack: source_ref target_ref source_member_ref source_member_off + StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true); + // stack: source_ref target_ref source_value... + } + else + { + solAssert(sourceType.location() == DataLocation::Memory, ""); + // stack layout: source_ref target_ref + TypePointer sourceMemberType = sourceType.memberType(member.name); + m_context << sourceType.memoryOffsetOfMember(member.name); + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true); + // stack layout: source_ref target_ref source_value... + } + unsigned stackSize = sourceMemberType->sizeOnStack(); + pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name); + m_context << eth::dupInstruction(1 + stackSize) << offsets.first << eth::Instruction::ADD; + m_context << u256(offsets.second); + // stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off + StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true); + } + // stack layout: source_ref target_ref + solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size."); + if (_move) + utils.popStackSlots(2); + else + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + } + else + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_sourceLocation(_location) + << errinfo_comment("Invalid non-value type for assignment.")); + } +} + +void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const +{ + if (m_dataType->category() == Type::Category::Array) + { + if (!_removeReference) + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); + ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(*m_dataType)); + } + else if (m_dataType->category() == Type::Category::Struct) + { + // stack layout: storage_key storage_offset + // @todo this can be improved: use StorageItem for non-value types, and just store 0 in + // all slots that contain value types later. + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + for (auto const& member: structType.members()) + { + // zero each member that is not a mapping + TypePointer const& memberType = member.type; + if (memberType->category() == Type::Category::Mapping) + continue; + pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name); + m_context + << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD + << u256(offsets.second); + StorageItem(m_context, *memberType).setToZero(); + } + if (_removeReference) + m_context << eth::Instruction::POP << eth::Instruction::POP; + } + else + { + solAssert(m_dataType->isValueType(), "Clearing of unsupported type requested: " + m_dataType->toString()); + if (!_removeReference) + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); + if (m_dataType->storageBytes() == 32) + { + // offset should be zero + m_context + << eth::Instruction::POP << u256(0) + << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } + else + { + m_context << u256(0x100) << eth::Instruction::EXP; + // stack: storage_ref multiplier + // fetch old value + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: storege_ref multiplier old_full_value + // clear bytes in old value + m_context + << eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) + << eth::Instruction::MUL; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: storage_ref cleared_value + m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } + } +} + +/// Used in StorageByteArrayElement +static FixedBytesType byteType(1); + +StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): + LValue(_compilerContext, &byteType) +{ +} + +void StorageByteArrayElement::retrieveValue(SourceLocation const&, bool _remove) const +{ + // stack: ref byte_number + if (_remove) + m_context << eth::Instruction::SWAP1 << eth::Instruction::SLOAD + << eth::Instruction::SWAP1 << eth::Instruction::BYTE; + else + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD + << eth::Instruction::DUP2 << eth::Instruction::BYTE; + m_context << (u256(1) << (256 - 8)) << eth::Instruction::MUL; +} + +void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, bool _move) const +{ + // stack: value ref byte_number + m_context << u256(31) << eth::Instruction::SUB << u256(0x100) << eth::Instruction::EXP; + // stack: value ref (1<<(8*(31-byte_number))) + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: value ref (1<<(8*(31-byte_number))) old_full_value + // clear byte in old value + m_context << eth::Instruction::DUP2 << u256(0xff) << eth::Instruction::MUL + << eth::Instruction::NOT << eth::Instruction::AND; + // stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte + m_context << eth::Instruction::SWAP1; + m_context << (u256(1) << (256 - 8)) << eth::Instruction::DUP5 << eth::Instruction::DIV + << eth::Instruction::MUL << eth::Instruction::OR; + // stack: value ref new_full_value + m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + if (_move) + m_context << eth::Instruction::POP; +} + +void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeReference) const +{ + // stack: ref byte_number + if (!_removeReference) + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP2; + m_context << u256(31) << eth::Instruction::SUB << u256(0x100) << eth::Instruction::EXP; + // stack: ref (1<<(8*(31-byte_number))) + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: ref (1<<(8*(31-byte_number))) old_full_value + // clear byte in old value + m_context << eth::Instruction::SWAP1 << u256(0xff) << eth::Instruction::MUL; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: ref old_full_value_with_cleared_byte + m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; +} + +StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType): + LValue(_compilerContext, _arrayType.memberType("length").get()), + m_arrayType(_arrayType) +{ + solAssert(m_arrayType.isDynamicallySized(), ""); +} + +void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const +{ + ArrayUtils(m_context).retrieveLength(m_arrayType); + if (_remove) + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; +} + +void StorageArrayLength::storeValue(Type const&, SourceLocation const&, bool _move) const +{ + if (_move) + m_context << eth::Instruction::SWAP1; + else + m_context << eth::Instruction::DUP2; + ArrayUtils(m_context).resizeDynamicArray(m_arrayType); +} + +void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) const +{ + if (!_removeReference) + m_context << eth::Instruction::DUP1; + ArrayUtils(m_context).clearDynamicArray(m_arrayType); +} + + +TupleObject::TupleObject( + CompilerContext& _compilerContext, + std::vector<std::unique_ptr<LValue>>&& _lvalues +): + LValue(_compilerContext), m_lvalues(move(_lvalues)) +{ +} + +unsigned TupleObject::sizeOnStack() const +{ + unsigned size = 0; + for (auto const& lv: m_lvalues) + if (lv) + size += lv->sizeOnStack(); + return size; +} + +void TupleObject::retrieveValue(SourceLocation const& _location, bool _remove) const +{ + unsigned initialDepth = sizeOnStack(); + unsigned initialStack = m_context.stackHeight(); + for (auto const& lv: m_lvalues) + if (lv) + { + solAssert(initialDepth + m_context.stackHeight() >= initialStack, ""); + unsigned depth = initialDepth + m_context.stackHeight() - initialStack; + if (lv->sizeOnStack() > 0) + { + if (_remove && depth > lv->sizeOnStack()) + CompilerUtils(m_context).moveToStackTop(depth, depth - lv->sizeOnStack()); + else if (!_remove && depth > 0) + CompilerUtils(m_context).copyToStackTop(depth, lv->sizeOnStack()); + } + lv->retrieveValue(_location, true); + } +} + +void TupleObject::storeValue(Type const& _sourceType, SourceLocation const& _location, bool) const +{ + // values are below the lvalue references + unsigned valuePos = sizeOnStack(); + TypePointers const& valueTypes = dynamic_cast<TupleType const&>(_sourceType).components(); + solAssert(valueTypes.size() == m_lvalues.size(), ""); + // valuePos .... refPos ... + // We will assign from right to left to optimize stack layout. + for (size_t i = 0; i < m_lvalues.size(); ++i) + { + unique_ptr<LValue> const& lvalue = m_lvalues[m_lvalues.size() - i - 1]; + TypePointer const& valType = valueTypes[valueTypes.size() - i - 1]; + unsigned stackHeight = m_context.stackHeight(); + solAssert(!valType == !lvalue, ""); + if (!lvalue) + continue; + valuePos += valType->sizeOnStack(); + // copy value to top + CompilerUtils(m_context).copyToStackTop(valuePos, valType->sizeOnStack()); + // move lvalue ref above value + CompilerUtils(m_context).moveToStackTop(valType->sizeOnStack(), lvalue->sizeOnStack()); + lvalue->storeValue(*valType, _location, true); + valuePos += m_context.stackHeight() - stackHeight; + } + // As the type of an assignment to a tuple type is the empty tuple, we always move. + CompilerUtils(m_context).popStackElement(_sourceType); +} + +void TupleObject::setToZero(SourceLocation const& _location, bool _removeReference) const +{ + if (_removeReference) + { + for (size_t i = 0; i < m_lvalues.size(); ++i) + if (m_lvalues[m_lvalues.size() - i]) + m_lvalues[m_lvalues.size() - i]->setToZero(_location, true); + } + else + { + unsigned depth = sizeOnStack(); + for (auto const& val: m_lvalues) + if (val) + { + if (val->sizeOnStack() > 0) + CompilerUtils(m_context).copyToStackTop(depth, val->sizeOnStack()); + val->setToZero(_location, false); + depth -= val->sizeOnStack(); + } + } +} diff --git a/libsolidity/codegen/LValue.h b/libsolidity/codegen/LValue.h new file mode 100644 index 00000000..35cbec5b --- /dev/null +++ b/libsolidity/codegen/LValue.h @@ -0,0 +1,224 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * LValues for use in the expresison compiler. + */ + +#pragma once + +#include <memory> +#include <vector> +#include <libevmasm/SourceLocation.h> +#include <libsolidity/codegen/ArrayUtils.h> + +namespace dev +{ +namespace solidity +{ + +class Declaration; +class Type; +class TupleType; +class ArrayType; +class CompilerContext; +class VariableDeclaration; + +/** + * Abstract class used to retrieve, delete and store data in lvalues/variables. + */ +class LValue +{ +protected: + explicit LValue(CompilerContext& _compilerContext, Type const* _dataType = nullptr): + m_context(_compilerContext), m_dataType(_dataType) {} + +public: + /// @returns the number of stack slots occupied by the lvalue reference + virtual unsigned sizeOnStack() const { return 1; } + /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, + /// also removes the reference from the stack. + /// @a _location source location of the current expression, used for error reporting. + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const = 0; + /// Moves a value from the stack to the lvalue. Removes the value if @a _move is true. + /// @a _location is the source location of the expression that caused this operation. + /// Stack pre: value [lvalue_ref] + /// Stack post: if !_move: value_of(lvalue_ref) + virtual void storeValue(Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), bool _move = false) const = 0; + /// Stores zero in the lvalue. Removes the reference from the stack if @a _removeReference is true. + /// @a _location is the source location of the requested operation + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), + bool _removeReference = true + ) const = 0; + +protected: + CompilerContext& m_context; + Type const* m_dataType; +}; + +/** + * Local variable that is completely stored on the stack. + */ +class StackVariable: public LValue +{ +public: + StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration); + + virtual unsigned sizeOnStack() const override { return 0; } + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue( + Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), + bool _move = false + ) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), + bool _removeReference = true + ) const override; + +private: + /// Base stack offset (@see CompilerContext::baseStackOffsetOfVariable) of the local variable. + unsigned m_baseStackOffset; + /// Number of stack elements occupied by the value (not the reference). + unsigned m_size; +}; + +/** + * Reference to some item in memory. + */ +class MemoryItem: public LValue +{ +public: + MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded = true); + virtual unsigned sizeOnStack() const override { return 1; } + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue( + Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), + bool _move = false + ) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), + bool _removeReference = true + ) const override; +private: + /// Special flag to deal with byte array elements. + bool m_padded = false; +}; + +/** + * Reference to some item in storage. On the stack this is <storage key> <offset_inside_value>, + * where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied + * by 2**i before storing it. + */ +class StorageItem: public LValue +{ +public: + /// Constructs the LValue and pushes the location of @a _declaration onto the stack. + StorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration); + /// Constructs the LValue and assumes that the storage reference is already on the stack. + StorageItem(CompilerContext& _compilerContext, Type const& _type); + virtual unsigned sizeOnStack() const override { return 2; } + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue( + Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), + bool _move = false + ) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), + bool _removeReference = true + ) const override; +}; + +/** + * Reference to a single byte inside a storage byte array. + * Stack: <storage_ref> <byte_number> + */ +class StorageByteArrayElement: public LValue +{ +public: + /// Constructs the LValue and assumes that the storage reference is already on the stack. + StorageByteArrayElement(CompilerContext& _compilerContext); + virtual unsigned sizeOnStack() const override { return 2; } + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue( + Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), + bool _move = false + ) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), + bool _removeReference = true + ) const override; +}; + +/** + * Reference to the "length" member of a dynamically-sized array. This is an LValue with special + * semantics since assignments to it might reduce its length and thus arrays members have to be + * deleted. + */ +class StorageArrayLength: public LValue +{ +public: + /// Constructs the LValue, assumes that the reference to the array head is already on the stack. + StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType); + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue( + Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), + bool _move = false + ) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), + bool _removeReference = true + ) const override; + +private: + ArrayType const& m_arrayType; +}; + +/** + * Tuple object that can itself hold several LValues. + */ +class TupleObject: public LValue +{ +public: + /// Constructs the LValue assuming that the other LValues are present on the stack. + /// Empty unique_ptrs are possible if e.g. some values should be ignored during assignment. + TupleObject(CompilerContext& _compilerContext, std::vector<std::unique_ptr<LValue>>&& _lvalues); + virtual unsigned sizeOnStack() const; + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue( + Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), + bool _move = false + ) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), + bool _removeReference = true + ) const override; + +private: + std::vector<std::unique_ptr<LValue>> m_lvalues; +}; + +} +} |