aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity/codegen
diff options
context:
space:
mode:
authorchriseth <c@ethdev.com>2015-10-21 06:21:52 +0800
committerchriseth <c@ethdev.com>2015-10-21 06:46:01 +0800
commite3dffb611fe1736e3ffa170e6d8dc4dee17366bd (patch)
treeb2df13e7c4c16c01b6cdc7cd5c15932031185d95 /libsolidity/codegen
parentd41f8b7ce702c3b25c48d27e2e895ccdcd04e4e0 (diff)
downloaddexon-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.cpp968
-rw-r--r--libsolidity/codegen/ArrayUtils.h103
-rw-r--r--libsolidity/codegen/Compiler.cpp778
-rw-r--r--libsolidity/codegen/Compiler.h144
-rw-r--r--libsolidity/codegen/CompilerContext.cpp223
-rw-r--r--libsolidity/codegen/CompilerContext.h189
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp802
-rw-r--r--libsolidity/codegen/CompilerUtils.h182
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp1370
-rw-r--r--libsolidity/codegen/ExpressionCompiler.h138
-rw-r--r--libsolidity/codegen/LValue.cpp546
-rw-r--r--libsolidity/codegen/LValue.h224
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;
+};
+
+}
+}