aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Changelog.md4
-rw-r--r--libsolidity/codegen/ArrayUtils.cpp878
-rw-r--r--libsolidity/codegen/ArrayUtils.h5
-rw-r--r--libsolidity/codegen/CompilerContext.cpp54
-rw-r--r--libsolidity/codegen/CompilerContext.h33
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp67
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp1
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp22
8 files changed, 607 insertions, 457 deletions
diff --git a/Changelog.md b/Changelog.md
index cd3e31b2..7dd607d8 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -6,6 +6,10 @@ Features:
* AST: Use deterministic node identifiers.
* Type system: Introduce type identifier strings.
* Metadata: Do not include platform in the version number.
+ * Code generator: Extract array utils into low-level functions.
+
+Bugfixes:
+ * Code generator: Allow recursive structs.
* Type checker: Allow multiple events of the same name (but with different arities or argument types)
### 0.4.8 (2017-01-13)
diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp
index 352c7177..4d100d1d 100644
--- a/libsolidity/codegen/ArrayUtils.cpp
+++ b/libsolidity/codegen/ArrayUtils.cpp
@@ -40,9 +40,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// 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());
+ TypePointer uint256 = make_shared<IntegerType>(256);
+ TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType();
+ TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType();
// TODO unroll loop for small sizes
@@ -70,202 +70,216 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
}
// stack: target_ref source_ref source_length
- m_context << 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 << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
- if (sourceBaseType->category() == Type::Category::Mapping)
- {
- solAssert(targetBaseType->category() == Type::Category::Mapping, "");
- solAssert(_sourceType.location() == DataLocation::Storage, "");
- // nothing to copy
- m_context
- << Instruction::POP << Instruction::POP
- << Instruction::POP << Instruction::POP;
- return;
- }
- // stack: target_ref source_ref source_length target_ref target_length
- // compute hashes (data positions)
- m_context << Instruction::SWAP1;
- if (_targetType.isDynamicallySized())
- CompilerUtils(m_context).computeHashStatic();
- // stack: target_ref source_ref source_length target_length target_data_pos
- m_context << Instruction::SWAP1;
- convertLengthToSize(_targetType);
- m_context << Instruction::DUP2 << Instruction::ADD;
- // stack: target_ref source_ref source_length target_data_pos target_data_end
- m_context << 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 << Instruction::DUP3 << u256(31) << 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 << Instruction::DUP1 << Instruction::SLOAD;
- m_context << Instruction::DUP6 << Instruction::SSTORE;
- }
- else
+ TypePointer targetType = _targetType.shared_from_this();
+ TypePointer sourceType = _sourceType.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(),
+ 3,
+ 1,
+ [=](CompilerContext& _context)
{
- m_context << 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) << Instruction::NOT << Instruction::AND;
- // fetch the length and shift it left by one
- m_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
- // combine value and length and store them
- m_context << Instruction::OR << Instruction::DUP6 << 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 << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
- m_context << u256(1) << Instruction::ADD;
- m_context << Instruction::DUP6 << Instruction::SSTORE;
- }
+ ArrayUtils utils(_context);
+ ArrayType const& _sourceType = dynamic_cast<ArrayType const&>(*sourceType);
+ ArrayType const& _targetType = dynamic_cast<ArrayType const&>(*targetType);
+ // stack: target_ref source_ref source_length
+ _context << Instruction::DUP3;
+ // stack: target_ref source_ref source_length target_ref
+ utils.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.
+ _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
+ if (sourceBaseType->category() == Type::Category::Mapping)
+ {
+ solAssert(targetBaseType->category() == Type::Category::Mapping, "");
+ solAssert(_sourceType.location() == DataLocation::Storage, "");
+ // nothing to copy
+ _context
+ << Instruction::POP << Instruction::POP
+ << Instruction::POP << Instruction::POP;
+ return;
+ }
+ // stack: target_ref source_ref source_length target_ref target_length
+ // compute hashes (data positions)
+ _context << Instruction::SWAP1;
+ if (_targetType.isDynamicallySized())
+ CompilerUtils(_context).computeHashStatic();
+ // stack: target_ref source_ref source_length target_length target_data_pos
+ _context << Instruction::SWAP1;
+ utils.convertLengthToSize(_targetType);
+ _context << Instruction::DUP2 << Instruction::ADD;
+ // stack: target_ref source_ref source_length target_data_pos target_data_end
+ _context << Instruction::SWAP3;
+ // stack: target_ref target_data_end source_length target_data_pos source_ref
+
+ eth::AssemblyItem copyLoopEndWithoutByteOffset = _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
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ eth::AssemblyItem longByteArray = _context.appendConditionalJump();
+ // store the short byte array
+ solAssert(_sourceType.isByteArray(), "");
+ if (_sourceType.location() == DataLocation::Storage)
+ {
+ // just copy the slot, it contains length and data
+ _context << Instruction::DUP1 << Instruction::SLOAD;
+ _context << Instruction::DUP6 << Instruction::SSTORE;
+ }
+ else
+ {
+ _context << Instruction::DUP1;
+ CompilerUtils(_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
+ _context << u256(0xff) << Instruction::NOT << Instruction::AND;
+ // fetch the length and shift it left by one
+ _context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
+ // combine value and length and store them
+ _context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
+ }
+ // end of special case, jump right into cleaning target data area
+ _context.appendJumpTo(copyLoopEndWithoutByteOffset);
+ _context << longByteArray;
+ // Store length (2*length+1)
+ _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
+ _context << u256(1) << Instruction::ADD;
+ _context << Instruction::DUP6 << Instruction::SSTORE;
+ }
- // skip copying if source length is zero
- m_context << Instruction::DUP3 << 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 << Instruction::SWAP2;
- convertLengthToSize(_sourceType);
- m_context << Instruction::DUP3 << 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
- << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
- << Instruction::GT << 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 << Instruction::DUP3;
- if (sourceBaseArrayType.location() == DataLocation::Memory)
- m_context << Instruction::MLOAD;
- m_context << Instruction::DUP3;
- copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
- m_context << Instruction::POP;
- }
- else if (directCopy)
- {
- solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
- m_context
- << Instruction::DUP3 << Instruction::SLOAD
- << Instruction::DUP3 << 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 << dupInstruction(3 + byteOffsetSize);
- if (_sourceType.location() == DataLocation::Storage)
- {
+ // skip copying if source length is zero
+ _context << Instruction::DUP3 << Instruction::ISZERO;
+ _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
+
+ if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
+ CompilerUtils(_context).computeHashStatic();
+ // stack: target_ref target_data_end source_length target_data_pos source_data_pos
+ _context << Instruction::SWAP2;
+ utils.convertLengthToSize(_sourceType);
+ _context << Instruction::DUP3 << Instruction::ADD;
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
+ if (haveByteOffsetTarget)
+ _context << u256(0);
if (haveByteOffsetSource)
- m_context << Instruction::DUP2;
+ _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 = _context.newTag();
+ _context << copyLoopStart;
+ // check for loop condition
+ _context
+ << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
+ << Instruction::GT << Instruction::ISZERO;
+ eth::AssemblyItem copyLoopEnd = _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);
+ _context << Instruction::DUP3;
+ if (sourceBaseArrayType.location() == DataLocation::Memory)
+ _context << Instruction::MLOAD;
+ _context << Instruction::DUP3;
+ utils.copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
+ _context << Instruction::POP;
+ }
+ else if (directCopy)
+ {
+ solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
+ _context
+ << Instruction::DUP3 << Instruction::SLOAD
+ << Instruction::DUP3 << 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]
+ _context << dupInstruction(3 + byteOffsetSize);
+ if (_sourceType.location() == DataLocation::Storage)
+ {
+ if (haveByteOffsetSource)
+ _context << Instruction::DUP2;
+ else
+ _context << u256(0);
+ StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
+ }
+ else if (sourceBaseType->isValueType())
+ CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
+ else
+ solUnimplemented("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
+ _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
+ if (haveByteOffsetTarget)
+ _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack());
+ else
+ _context << u256(0);
+ StorageItem(_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)
+ utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
else
- m_context << u256(0);
- StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
+ {
+ _context << swapInstruction(2 + byteOffsetSize);
+ if (sourceIsStorage)
+ _context << sourceBaseType->storageSize();
+ else if (_sourceType.location() == DataLocation::Memory)
+ _context << sourceBaseType->memoryHeadSize();
+ else
+ _context << sourceBaseType->calldataEncodedSize(true);
+ _context
+ << Instruction::ADD
+ << swapInstruction(2 + byteOffsetSize);
+ }
+ // increment target
+ if (haveByteOffsetTarget)
+ utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
+ else
+ _context
+ << swapInstruction(1 + byteOffsetSize)
+ << targetBaseType->storageSize()
+ << Instruction::ADD
+ << swapInstruction(1 + byteOffsetSize);
+ _context.appendJumpTo(copyLoopStart);
+ _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]
+ _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
+ eth::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump();
+ _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
+ StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true);
+ utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
+ _context.appendJumpTo(copyLoopEnd);
+
+ _context << copyCleanupLoopEnd;
+ _context << Instruction::POP; // might pop the source, but then target is popped next
+ }
+ if (haveByteOffsetSource)
+ _context << Instruction::POP;
+ _context << copyLoopEndWithoutByteOffset;
+
+ // zero-out leftovers in target
+ // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
+ _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
+ // stack: target_ref target_data_end target_data_pos_updated
+ utils.clearStorageLoop(targetBaseType);
+ _context << Instruction::POP;
}
- else if (sourceBaseType->isValueType())
- CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
- else
- solUnimplemented("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 << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
- if (haveByteOffsetTarget)
- m_context << 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 << 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
- << Instruction::ADD
- << swapInstruction(2 + byteOffsetSize);
- }
- // increment target
- if (haveByteOffsetTarget)
- incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
- else
- m_context
- << swapInstruction(1 + byteOffsetSize)
- << targetBaseType->storageSize()
- << Instruction::ADD
- << 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 << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
- eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump();
- m_context << dupInstruction(2 + byteOffsetSize) << 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 << Instruction::POP; // might pop the source, but then target is popped next
- }
- if (haveByteOffsetSource)
- m_context << 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 << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
- // stack: target_ref target_data_end target_data_pos_updated
- clearStorageLoop(*targetBaseType);
- m_context << Instruction::POP;
+ );
}
void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const
@@ -502,60 +516,70 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
}
}
-void ArrayUtils::clearArray(ArrayType const& _type) const
+void ArrayUtils::clearArray(ArrayType const& _typeIn) 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 << Instruction::POP; // remove byte offset
- if (_type.isDynamicallySized())
- clearDynamicArray(_type);
- else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
- m_context << 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) << Instruction::DUP2 << Instruction::SSTORE
- << u256(1) << Instruction::ADD;
- m_context << u256(0) << Instruction::SWAP1 << 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)
+ TypePointer type = _typeIn.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$clearArray_" + _typeIn.identifier(),
+ 2,
+ 0,
+ [type](CompilerContext& _context)
{
- m_context << u256(0);
- StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false);
- m_context
- << Instruction::POP
- << u256(_type.baseType()->storageSize()) << Instruction::ADD;
+ ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
+ unsigned stackHeightStart = _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.");
+
+ _context << Instruction::POP; // remove byte offset
+ if (_type.isDynamicallySized())
+ ArrayUtils(_context).clearDynamicArray(_type);
+ else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
+ _context << 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)
+ _context
+ << u256(0) << Instruction::DUP2 << Instruction::SSTORE
+ << u256(1) << Instruction::ADD;
+ _context << u256(0) << Instruction::SWAP1 << 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)
+ {
+ _context << u256(0);
+ StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false);
+ _context
+ << Instruction::POP
+ << u256(_type.baseType()->storageSize()) << Instruction::ADD;
+ }
+ _context << u256(0);
+ StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true);
+ }
+ else
+ {
+ _context << Instruction::DUP1 << _type.length();
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::ADD << Instruction::SWAP1;
+ if (_type.baseType()->storageBytes() < 32)
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ else
+ ArrayUtils(_context).clearStorageLoop(_type.baseType());
+ _context << Instruction::POP;
+ }
+ solAssert(_context.stackHeight() == stackHeightStart - 2, "");
}
- m_context << u256(0);
- StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true);
- }
- else
- {
- m_context << Instruction::DUP1 << _type.length();
- convertLengthToSize(_type);
- m_context << Instruction::ADD << Instruction::SWAP1;
- if (_type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
- else
- clearStorageLoop(*_type.baseType());
- m_context << Instruction::POP;
- }
- solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
+ );
}
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
@@ -589,191 +613,209 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
<< Instruction::SWAP1;
// stack: data_pos_end data_pos
if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
+ clearStorageLoop(make_shared<IntegerType>(256));
else
- clearStorageLoop(*_type.baseType());
+ clearStorageLoop(_type.baseType());
// cleanup
m_context << endTag;
m_context << Instruction::POP;
}
-void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const
+void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) 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 << Instruction::DUP3 << Instruction::SLOAD;
- // stack: ref new_length current_length ref_value
-
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context << Instruction::DUP2 << u256(31) << Instruction::LT;
- eth::AssemblyItem currentIsLong = m_context.appendConditionalJump();
- m_context << Instruction::DUP3 << u256(31) << 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 << Instruction::DUP3 << u256(8) << Instruction::MUL;
- m_context << u256(0x100) << Instruction::SUB;
- m_context << u256(2) << Instruction::EXP;
- // Divide and multiply by that value, clearing bits.
- m_context << Instruction::DUP1 << Instruction::SWAP2;
- m_context << Instruction::DIV << Instruction::MUL;
- // Insert 2*length.
- m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
- m_context << Instruction::OR;
- // Store.
- m_context << Instruction::DUP4 << 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) << Instruction::NOT << Instruction::AND;
- // Store at data location.
- m_context << Instruction::DUP4;
- CompilerUtils(m_context).computeHashStatic();
- m_context << Instruction::SSTORE;
- // stack: ref new_length current_length
- // Store new length: Compule 2*length + 1 and store it.
- m_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
- m_context << u256(1) << Instruction::ADD;
- // stack: ref new_length current_length 2*new_length+1
- m_context << Instruction::DUP4 << 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 << Instruction::DUP3 << u256(31) << 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 << Instruction::POP << Instruction::DUP3;
- CompilerUtils(m_context).computeHashStatic();
- m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
- // stack: ref new_length current_length first_word data_location
- m_context << Instruction::DUP3;
- convertLengthToSize(_type);
- m_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
- // stack: ref new_length current_length first_word data_location_end data_location
- clearStorageLoop(IntegerType(256));
- m_context << 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 << Instruction::POP;
- }
+ TypePointer type = _typeIn.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$resizeDynamicArray_" + _typeIn.identifier(),
+ 2,
+ 0,
+ [type](CompilerContext& _context)
+ {
+ ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
+ 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 = _context.stackHeight();
+ eth::AssemblyItem resizeEnd = _context.newTag();
+
+ // stack: ref new_length
+ // fetch old length
+ ArrayUtils(_context).retrieveLength(_type, 1);
+ // stack: ref new_length old_length
+ solAssert(_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 = _context.newTag();
+ // We start by a large case-distinction about the old and new length of the byte array.
+
+ _context << Instruction::DUP3 << Instruction::SLOAD;
+ // stack: ref new_length current_length ref_value
+
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context << Instruction::DUP2 << u256(31) << Instruction::LT;
+ eth::AssemblyItem currentIsLong = _context.appendConditionalJump();
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ eth::AssemblyItem newIsLong = _context.appendConditionalJump();
+
+ // Here: short -> short
+
+ // Compute 1 << (256 - 8 * new_size)
+ eth::AssemblyItem shortToShort = _context.newTag();
+ _context << shortToShort;
+ _context << Instruction::DUP3 << u256(8) << Instruction::MUL;
+ _context << u256(0x100) << Instruction::SUB;
+ _context << u256(2) << Instruction::EXP;
+ // Divide and multiply by that value, clearing bits.
+ _context << Instruction::DUP1 << Instruction::SWAP2;
+ _context << Instruction::DIV << Instruction::MUL;
+ // Insert 2*length.
+ _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
+ _context << Instruction::OR;
+ // Store.
+ _context << Instruction::DUP4 << Instruction::SSTORE;
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
+ _context.appendJumpTo(resizeEnd);
+
+ _context.adjustStackOffset(1); // we have to do that because of the jumps
+ // Here: short -> long
+
+ _context << newIsLong;
+ // stack: ref new_length current_length ref_value
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ // Zero out lower-order byte.
+ _context << u256(0xff) << Instruction::NOT << Instruction::AND;
+ // Store at data location.
+ _context << Instruction::DUP4;
+ CompilerUtils(_context).computeHashStatic();
+ _context << Instruction::SSTORE;
+ // stack: ref new_length current_length
+ // Store new length: Compule 2*length + 1 and store it.
+ _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
+ _context << u256(1) << Instruction::ADD;
+ // stack: ref new_length current_length 2*new_length+1
+ _context << Instruction::DUP4 << Instruction::SSTORE;
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
+ _context.appendJumpTo(resizeEnd);
+
+ _context.adjustStackOffset(1); // we have to do that because of the jumps
+
+ _context << currentIsLong;
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ _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(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context << Instruction::POP << Instruction::DUP3;
+ CompilerUtils(_context).computeHashStatic();
+ _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
+ // stack: ref new_length current_length first_word data_location
+ _context << Instruction::DUP3;
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
+ // stack: ref new_length current_length first_word data_location_end data_location
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ _context << Instruction::POP;
+ // stack: ref new_length current_length first_word
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context.appendJumpTo(shortToShort);
+
+ _context << regularPath;
+ // stack: ref new_length current_length ref_value
+ _context << 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 << Instruction::DUP2;
- if (_type.isByteArray())
- // For a "long" byte array, store length as 2*length+1
- m_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
- m_context<< Instruction::DUP4 << Instruction::SSTORE;
- // skip if size is not reduced
- m_context << Instruction::DUP2 << Instruction::DUP2
- << Instruction::ISZERO << Instruction::GT;
- m_context.appendConditionalJumpTo(resizeEnd);
-
- // size reduced, clear the end of the array
- // stack: ref new_length old_length
- convertLengthToSize(_type);
- m_context << Instruction::DUP2;
- convertLengthToSize(_type);
- // stack: ref new_length old_size new_size
- // compute data positions
- m_context << Instruction::DUP4;
- CompilerUtils(m_context).computeHashStatic();
- // stack: ref new_length old_size new_size data_pos
- m_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
- // stack: ref new_length data_pos new_size delete_end
- m_context << Instruction::SWAP2 << Instruction::ADD;
- // stack: ref new_length delete_end delete_start
- if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
- else
- clearStorageLoop(*_type.baseType());
+ // 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
+ _context << Instruction::DUP2;
+ if (_type.isByteArray())
+ // For a "long" byte array, store length as 2*length+1
+ _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
+ _context<< Instruction::DUP4 << Instruction::SSTORE;
+ // skip if size is not reduced
+ _context << Instruction::DUP2 << Instruction::DUP2
+ << Instruction::ISZERO << Instruction::GT;
+ _context.appendConditionalJumpTo(resizeEnd);
+
+ // size reduced, clear the end of the array
+ // stack: ref new_length old_length
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::DUP2;
+ ArrayUtils(_context).convertLengthToSize(_type);
+ // stack: ref new_length old_size new_size
+ // compute data positions
+ _context << Instruction::DUP4;
+ CompilerUtils(_context).computeHashStatic();
+ // stack: ref new_length old_size new_size data_pos
+ _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
+ // stack: ref new_length data_pos new_size delete_end
+ _context << Instruction::SWAP2 << Instruction::ADD;
+ // stack: ref new_length delete_end delete_start
+ if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ else
+ ArrayUtils(_context).clearStorageLoop(_type.baseType());
- m_context << resizeEnd;
- // cleanup
- m_context << Instruction::POP << Instruction::POP << Instruction::POP;
- solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
+ _context << resizeEnd;
+ // cleanup
+ _context << Instruction::POP << Instruction::POP << Instruction::POP;
+ solAssert(_context.stackHeight() == stackHeightStart - 2, "");
+ }
+ );
}
-void ArrayUtils::clearStorageLoop(Type const& _type) const
+void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
{
- unsigned stackHeightStart = m_context.stackHeight();
- if (_type.category() == Type::Category::Mapping)
- {
- m_context << 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 << Instruction::SWAP2 << Instruction::SWAP1;
-
- // stack: <return tag> end_pos pos
- eth::AssemblyItem loopStart = m_context.appendJumpToNew();
- m_context << loopStart;
- // check for loop condition
- m_context << Instruction::DUP1 << Instruction::DUP3
- << Instruction::GT << 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 << Instruction::POP;
- // increment
- m_context << _type.storageSize() << Instruction::ADD;
- m_context.appendJumpTo(loopStart);
- // cleanup
- m_context << zeroLoopEnd;
- m_context << Instruction::POP << Instruction::SWAP1;
- // "return"
- m_context << Instruction::JUMP;
-
- m_context << returnTag;
- solAssert(m_context.stackHeight() == stackHeightStart - 1, "");
+ m_context.callLowLevelFunction(
+ "$clearStorageLoop_" + _type->identifier(),
+ 2,
+ 1,
+ [_type](CompilerContext& _context)
+ {
+ unsigned stackHeightStart = _context.stackHeight();
+ if (_type->category() == Type::Category::Mapping)
+ {
+ _context << Instruction::POP;
+ return;
+ }
+ // stack: end_pos pos
+
+ // jump to and return from the loop to allow for duplicate code removal
+ eth::AssemblyItem returnTag = _context.pushNewTag();
+ _context << Instruction::SWAP2 << Instruction::SWAP1;
+
+ // stack: <return tag> end_pos pos
+ eth::AssemblyItem loopStart = _context.appendJumpToNew();
+ _context << loopStart;
+ // check for loop condition
+ _context << Instruction::DUP1 << Instruction::DUP3
+ << Instruction::GT << Instruction::ISZERO;
+ eth::AssemblyItem zeroLoopEnd = _context.newTag();
+ _context.appendConditionalJumpTo(zeroLoopEnd);
+ // delete
+ _context << u256(0);
+ StorageItem(_context, *_type).setToZero(SourceLocation(), false);
+ _context << Instruction::POP;
+ // increment
+ _context << _type->storageSize() << Instruction::ADD;
+ _context.appendJumpTo(loopStart);
+ // cleanup
+ _context << zeroLoopEnd;
+ _context << Instruction::POP << Instruction::SWAP1;
+ // "return"
+ _context << Instruction::JUMP;
+
+ _context << returnTag;
+ solAssert(_context.stackHeight() == stackHeightStart - 1, "");
+ }
+ );
}
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const
diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h
index d0ba2892..806fbea7 100644
--- a/libsolidity/codegen/ArrayUtils.h
+++ b/libsolidity/codegen/ArrayUtils.h
@@ -22,6 +22,8 @@
#pragma once
+#include <memory>
+
namespace dev
{
namespace solidity
@@ -30,6 +32,7 @@ namespace solidity
class CompilerContext;
class Type;
class ArrayType;
+using TypePointer = std::shared_ptr<Type const>;
/**
* Class that provides code generation for handling arrays.
@@ -67,7 +70,7 @@ public:
/// 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;
+ void clearStorageLoop(TypePointer 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
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index c14ab845..e26f96e8 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -21,15 +21,18 @@
*/
#include <libsolidity/codegen/CompilerContext.h>
-#include <utility>
-#include <numeric>
-#include <boost/algorithm/string/replace.hpp>
+#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/Compiler.h>
#include <libsolidity/interface/Version.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmStack.h>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <utility>
+#include <numeric>
+
using namespace std;
namespace dev
@@ -57,6 +60,51 @@ void CompilerContext::startFunction(Declaration const& _function)
*this << functionEntryLabel(_function);
}
+void CompilerContext::callLowLevelFunction(
+ string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ function<void(CompilerContext&)> const& _generator
+)
+{
+ eth::AssemblyItem retTag = pushNewTag();
+ CompilerUtils(*this).moveIntoStack(_inArgs);
+
+ auto it = m_lowLevelFunctions.find(_name);
+ if (it == m_lowLevelFunctions.end())
+ {
+ eth::AssemblyItem tag = newTag().pushTag();
+ m_lowLevelFunctions.insert(make_pair(_name, tag));
+ m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator));
+ *this << tag;
+ }
+ else
+ *this << it->second;
+ appendJump(eth::AssemblyItem::JumpType::IntoFunction);
+ adjustStackOffset(int(_outArgs) - 1 - _inArgs);
+ *this << retTag.tag();
+}
+
+void CompilerContext::appendMissingLowLevelFunctions()
+{
+ while (!m_lowLevelFunctionGenerationQueue.empty())
+ {
+ string name;
+ unsigned inArgs;
+ unsigned outArgs;
+ function<void(CompilerContext&)> generator;
+ tie(name, inArgs, outArgs, generator) = m_lowLevelFunctionGenerationQueue.front();
+ m_lowLevelFunctionGenerationQueue.pop();
+
+ setStackOffset(inArgs + 1);
+ *this << m_lowLevelFunctions.at(name).tag();
+ generator(*this);
+ CompilerUtils(*this).moveToStackTop(outArgs);
+ appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ solAssert(stackHeight() == outArgs, "Invalid stack height in low-level function " + name + ".");
+ }
+}
+
void CompilerContext::addVariable(VariableDeclaration const& _declaration,
unsigned _offsetToCurrent)
{
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 80671528..f024b010 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -22,17 +22,21 @@
#pragma once
-#include <ostream>
-#include <stack>
-#include <queue>
-#include <utility>
-#include <libevmasm/Instruction.h>
-#include <libevmasm/Assembly.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/ast/ASTAnnotations.h>
+
+#include <libevmasm/Instruction.h>
+#include <libevmasm/Assembly.h>
+
#include <libdevcore/Common.h>
+#include <ostream>
+#include <stack>
+#include <queue>
+#include <utility>
+#include <functional>
+
namespace dev {
namespace solidity {
@@ -90,6 +94,19 @@ public:
/// as "having code".
void startFunction(Declaration const& _function);
+ /// Appends a call to the named low-level function and inserts the generator into the
+ /// list of low-level-functions to be generated, unless it already exists.
+ /// Note that the generator should not assume that objects are still alive when it is called,
+ /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
+ void callLowLevelFunction(
+ std::string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ std::function<void(CompilerContext&)> const& _generator
+ );
+ /// Generates the code for missing low-level functions, i.e. calls the generators passed above.
+ void appendMissingLowLevelFunctions();
+
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;
@@ -248,6 +265,10 @@ private:
CompilerContext *m_runtimeContext;
/// The index of the runtime subroutine.
size_t m_runtimeSub = -1;
+ /// An index of low-level function labels by name.
+ std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions;
+ /// The queue of low-level functions to generate.
+ std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
};
}
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index 7d382aba..caf3b1ac 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -820,37 +820,46 @@ void CompilerUtils::pushZeroValue(Type const& _type)
}
solAssert(referenceType->location() == DataLocation::Memory, "");
- m_context << u256(max(32u, _type.calldataEncodedSize()));
- allocateMemory();
- m_context << Instruction::DUP1;
+ TypePointer type = _type.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$pushZeroValue_" + referenceType->identifier(),
+ 0,
+ 1,
+ [type](CompilerContext& _context) {
+ CompilerUtils utils(_context);
+ _context << u256(max(32u, type->calldataEncodedSize()));
+ utils.allocateMemory();
+ _context << Instruction::DUP1;
+
+ if (auto structType = dynamic_cast<StructType const*>(type.get()))
+ for (auto const& member: structType->members(nullptr))
+ {
+ utils.pushZeroValue(*member.type);
+ utils.storeInMemoryDynamic(*member.type);
+ }
+ else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get()))
+ {
+ if (arrayType->isDynamicallySized())
+ {
+ // zero length
+ _context << u256(0);
+ utils.storeInMemoryDynamic(IntegerType(256));
+ }
+ else if (arrayType->length() > 0)
+ {
+ _context << arrayType->length() << Instruction::SWAP1;
+ // stack: items_to_do memory_pos
+ utils.zeroInitialiseMemoryArray(*arrayType);
+ // stack: updated_memory_pos
+ }
+ }
+ else
+ solAssert(false, "Requested initialisation for unknown type: " + type->toString());
- if (auto structType = dynamic_cast<StructType const*>(&_type))
- for (auto const& member: structType->members(nullptr))
- {
- pushZeroValue(*member.type);
- storeInMemoryDynamic(*member.type);
+ // remove the updated memory pointer
+ _context << Instruction::POP;
}
- 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() << Instruction::SWAP1;
- // stack: items_to_do memory_pos
- zeroInitialiseMemoryArray(*arrayType);
- // stack: updated_memory_pos
- }
- }
- else
- solAssert(false, "Requested initialisation for unknown type: " + _type.toString());
-
- // remove the updated memory pointer
- m_context << Instruction::POP;
+ );
}
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index bdff0da4..9dc1fb37 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -827,6 +827,7 @@ void ContractCompiler::appendMissingFunctions()
function->accept(*this);
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
}
+ m_context.appendMissingLowLevelFunctions();
}
void ContractCompiler::appendModifierOrFunctionCode()
diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp
index da7adbbf..e8e5ced1 100644
--- a/test/libsolidity/SolidityEndToEndTest.cpp
+++ b/test/libsolidity/SolidityEndToEndTest.cpp
@@ -8992,6 +8992,28 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment)
compileAndRun(sourceCode, 0, "C2");
}
+BOOST_AUTO_TEST_CASE(recursive_structs)
+{
+ char const* sourceCode = R"(
+ contract C {
+ struct S {
+ S[] x;
+ }
+ S sstorage;
+ function f() returns (uint) {
+ S memory s;
+ s.x = new S[](10);
+ delete s;
+ sstorage.x.length++;
+ delete sstorage;
+ return 1;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1)));
+}
+
BOOST_AUTO_TEST_SUITE_END()
}