Merge pull request #1588 from ethereum/fixrecursivestructs

Introduce low-level functions
This commit is contained in:
chriseth 2017-01-24 17:37:22 +01:00 committed by GitHub
commit 3dc83aa34e
8 changed files with 588 additions and 438 deletions

View File

@ -6,6 +6,10 @@ Features:
* AST: Use deterministic node identifiers. * AST: Use deterministic node identifiers.
* Type system: Introduce type identifier strings. * Type system: Introduce type identifier strings.
* Metadata: Do not include platform in the version number. * 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) * Type checker: Allow multiple events of the same name (but with different arities or argument types)
### 0.4.8 (2017-01-13) ### 0.4.8 (2017-01-13)

View File

@ -40,9 +40,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// stack layout: [source_ref] [source length] target_ref (top) // stack layout: [source_ref] [source length] target_ref (top)
solAssert(_targetType.location() == DataLocation::Storage, ""); solAssert(_targetType.location() == DataLocation::Storage, "");
IntegerType uint256(256); TypePointer uint256 = make_shared<IntegerType>(256);
Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.baseType()); TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType();
Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.baseType()); TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType();
// TODO unroll loop for small sizes // 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 // stack: target_ref source_ref source_length
m_context << Instruction::DUP3; TypePointer targetType = _targetType.shared_from_this();
// stack: target_ref source_ref source_length target_ref TypePointer sourceType = _sourceType.shared_from_this();
retrieveLength(_targetType); m_context.callLowLevelFunction(
// stack: target_ref source_ref source_length target_ref target_length "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(),
if (_targetType.isDynamicallySized()) 3,
// store new target length 1,
if (!_targetType.isByteArray()) [=](CompilerContext& _context)
// 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 ArrayUtils utils(_context);
m_context << Instruction::DUP1 << Instruction::SLOAD; ArrayType const& _sourceType = dynamic_cast<ArrayType const&>(*sourceType);
m_context << Instruction::DUP6 << Instruction::SSTORE; ArrayType const& _targetType = dynamic_cast<ArrayType const&>(*targetType);
} // stack: target_ref source_ref source_length
else _context << Instruction::DUP3;
{ // stack: target_ref source_ref source_length target_ref
m_context << Instruction::DUP1; utils.retrieveLength(_targetType);
CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); // stack: target_ref source_ref source_length target_ref target_length
// stack: target_ref target_data_end source_length target_data_pos source_ref value if (_targetType.isDynamicallySized())
// clear the lower-order byte - which will hold the length // store new target length
m_context << u256(0xff) << Instruction::NOT << Instruction::AND; if (!_targetType.isByteArray())
// fetch the length and shift it left by one // Otherwise, length will be stored below.
m_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD; _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
// combine value and length and store them if (sourceBaseType->category() == Type::Category::Mapping)
m_context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE; {
} solAssert(targetBaseType->category() == Type::Category::Mapping, "");
// end of special case, jump right into cleaning target data area solAssert(_sourceType.location() == DataLocation::Storage, "");
m_context.appendJumpTo(copyLoopEndWithoutByteOffset); // nothing to copy
m_context << longByteArray; _context
// Store length (2*length+1) << Instruction::POP << Instruction::POP
m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; << Instruction::POP << Instruction::POP;
m_context << u256(1) << Instruction::ADD; return;
m_context << Instruction::DUP6 << Instruction::SSTORE; }
} // 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
// skip copying if source length is zero eth::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag();
m_context << Instruction::DUP3 << Instruction::ISZERO;
m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) // special case for short byte arrays: Store them together with their length.
CompilerUtils(m_context).computeHashStatic(); if (_targetType.isByteArray())
// stack: target_ref target_data_end source_length target_data_pos source_data_pos {
m_context << Instruction::SWAP2; // stack: target_ref target_data_end source_length target_data_pos source_ref
convertLengthToSize(_sourceType); _context << Instruction::DUP3 << u256(31) << Instruction::LT;
m_context << Instruction::DUP3 << Instruction::ADD; eth::AssemblyItem longByteArray = _context.appendConditionalJump();
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end // store the short byte array
if (haveByteOffsetTarget) solAssert(_sourceType.isByteArray(), "");
m_context << u256(0); if (_sourceType.location() == DataLocation::Storage)
if (haveByteOffsetSource) {
m_context << u256(0); // just copy the slot, it contains length and data
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] _context << Instruction::DUP1 << Instruction::SLOAD;
eth::AssemblyItem copyLoopStart = m_context.newTag(); _context << Instruction::DUP6 << Instruction::SSTORE;
m_context << copyLoopStart; }
// check for loop condition else
m_context {
<< dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) _context << Instruction::DUP1;
<< Instruction::GT << Instruction::ISZERO; CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
eth::AssemblyItem copyLoopEnd = m_context.appendConditionalJump(); // stack: target_ref target_data_end source_length target_data_pos source_ref value
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] // clear the lower-order byte - which will hold the length
// copy _context << u256(0xff) << Instruction::NOT << Instruction::AND;
if (sourceBaseType->category() == Type::Category::Array) // fetch the length and shift it left by one
{ _context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); // combine value and length and store them
auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType); _context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
m_context << Instruction::DUP3; }
if (sourceBaseArrayType.location() == DataLocation::Memory) // end of special case, jump right into cleaning target data area
m_context << Instruction::MLOAD; _context.appendJumpTo(copyLoopEndWithoutByteOffset);
m_context << Instruction::DUP3; _context << longByteArray;
copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType); // Store length (2*length+1)
m_context << Instruction::POP; _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
} _context << u256(1) << Instruction::ADD;
else if (directCopy) _context << Instruction::DUP6 << Instruction::SSTORE;
{ }
solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
m_context // skip copying if source length is zero
<< Instruction::DUP3 << Instruction::SLOAD _context << Instruction::DUP3 << Instruction::ISZERO;
<< Instruction::DUP3 << Instruction::SSTORE; _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
}
else if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
{ CompilerUtils(_context).computeHashStatic();
// Note that we have to copy each element on its own in case conversion is involved. // stack: target_ref target_data_end source_length target_data_pos source_data_pos
// We might copy too much if there is padding at the last element, but this way end _context << Instruction::SWAP2;
// checking is easier. utils.convertLengthToSize(_sourceType);
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] _context << Instruction::DUP3 << Instruction::ADD;
m_context << dupInstruction(3 + byteOffsetSize); // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
if (_sourceType.location() == DataLocation::Storage) if (haveByteOffsetTarget)
{ _context << u256(0);
if (haveByteOffsetSource) 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 else
m_context << u256(0); {
StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); // 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
{
_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 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(); TypePointer type = _typeIn.shared_from_this();
solAssert(_type.location() == DataLocation::Storage, ""); m_context.callLowLevelFunction(
if (_type.baseType()->storageBytes() < 32) "$clearArray_" + _typeIn.identifier(),
{ 2,
solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); 0,
solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); [type](CompilerContext& _context)
}
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)
{ {
m_context << u256(0); ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false); unsigned stackHeightStart = _context.stackHeight();
m_context solAssert(_type.location() == DataLocation::Storage, "");
<< Instruction::POP if (_type.baseType()->storageBytes() < 32)
<< u256(_type.baseType()->storageSize()) << Instruction::ADD; {
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 void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
@ -589,191 +613,209 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
<< Instruction::SWAP1; << Instruction::SWAP1;
// stack: data_pos_end data_pos // stack: data_pos_end data_pos
if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
clearStorageLoop(IntegerType(256)); clearStorageLoop(make_shared<IntegerType>(256));
else else
clearStorageLoop(*_type.baseType()); clearStorageLoop(_type.baseType());
// cleanup // cleanup
m_context << endTag; m_context << endTag;
m_context << Instruction::POP; m_context << Instruction::POP;
} }
void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
{ {
solAssert(_type.location() == DataLocation::Storage, ""); TypePointer type = _typeIn.shared_from_this();
solAssert(_type.isDynamicallySized(), ""); m_context.callLowLevelFunction(
if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) "$resizeDynamicArray_" + _typeIn.identifier(),
solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); 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 = m_context.stackHeight(); unsigned stackHeightStart = _context.stackHeight();
eth::AssemblyItem resizeEnd = m_context.newTag(); eth::AssemblyItem resizeEnd = _context.newTag();
// stack: ref new_length // stack: ref new_length
// fetch old length // fetch old length
retrieveLength(_type, 1); ArrayUtils(_context).retrieveLength(_type, 1);
// stack: ref new_length old_length // stack: ref new_length old_length
solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "2"); solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2");
// Special case for short byte arrays, they are stored together with their length // Special case for short byte arrays, they are stored together with their length
if (_type.isByteArray()) if (_type.isByteArray())
{ {
eth::AssemblyItem regularPath = m_context.newTag(); eth::AssemblyItem regularPath = _context.newTag();
// We start by a large case-distinction about the old and new length of the byte array. // We start by a large case-distinction about the old and new length of the byte array.
m_context << Instruction::DUP3 << Instruction::SLOAD; _context << Instruction::DUP3 << Instruction::SLOAD;
// stack: ref new_length current_length ref_value // stack: ref new_length current_length ref_value
solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
m_context << Instruction::DUP2 << u256(31) << Instruction::LT; _context << Instruction::DUP2 << u256(31) << Instruction::LT;
eth::AssemblyItem currentIsLong = m_context.appendConditionalJump(); eth::AssemblyItem currentIsLong = _context.appendConditionalJump();
m_context << Instruction::DUP3 << u256(31) << Instruction::LT; _context << Instruction::DUP3 << u256(31) << Instruction::LT;
eth::AssemblyItem newIsLong = m_context.appendConditionalJump(); eth::AssemblyItem newIsLong = _context.appendConditionalJump();
// Here: short -> short // Here: short -> short
// Compute 1 << (256 - 8 * new_size) // Compute 1 << (256 - 8 * new_size)
eth::AssemblyItem shortToShort = m_context.newTag(); eth::AssemblyItem shortToShort = _context.newTag();
m_context << shortToShort; _context << shortToShort;
m_context << Instruction::DUP3 << u256(8) << Instruction::MUL; _context << Instruction::DUP3 << u256(8) << Instruction::MUL;
m_context << u256(0x100) << Instruction::SUB; _context << u256(0x100) << Instruction::SUB;
m_context << u256(2) << Instruction::EXP; _context << u256(2) << Instruction::EXP;
// Divide and multiply by that value, clearing bits. // Divide and multiply by that value, clearing bits.
m_context << Instruction::DUP1 << Instruction::SWAP2; _context << Instruction::DUP1 << Instruction::SWAP2;
m_context << Instruction::DIV << Instruction::MUL; _context << Instruction::DIV << Instruction::MUL;
// Insert 2*length. // Insert 2*length.
m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
m_context << Instruction::OR; _context << Instruction::OR;
// Store. // Store.
m_context << Instruction::DUP4 << Instruction::SSTORE; _context << Instruction::DUP4 << Instruction::SSTORE;
solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
m_context.appendJumpTo(resizeEnd); _context.appendJumpTo(resizeEnd);
m_context.adjustStackOffset(1); // we have to do that because of the jumps _context.adjustStackOffset(1); // we have to do that because of the jumps
// Here: short -> long // Here: short -> long
m_context << newIsLong; _context << newIsLong;
// stack: ref new_length current_length ref_value // stack: ref new_length current_length ref_value
solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
// Zero out lower-order byte. // Zero out lower-order byte.
m_context << u256(0xff) << Instruction::NOT << Instruction::AND; _context << u256(0xff) << Instruction::NOT << Instruction::AND;
// Store at data location. // Store at data location.
m_context << Instruction::DUP4; _context << Instruction::DUP4;
CompilerUtils(m_context).computeHashStatic(); CompilerUtils(_context).computeHashStatic();
m_context << Instruction::SSTORE; _context << Instruction::SSTORE;
// stack: ref new_length current_length // stack: ref new_length current_length
// Store new length: Compule 2*length + 1 and store it. // Store new length: Compule 2*length + 1 and store it.
m_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
m_context << u256(1) << Instruction::ADD; _context << u256(1) << Instruction::ADD;
// stack: ref new_length current_length 2*new_length+1 // stack: ref new_length current_length 2*new_length+1
m_context << Instruction::DUP4 << Instruction::SSTORE; _context << Instruction::DUP4 << Instruction::SSTORE;
solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
m_context.appendJumpTo(resizeEnd); _context.appendJumpTo(resizeEnd);
m_context.adjustStackOffset(1); // we have to do that because of the jumps _context.adjustStackOffset(1); // we have to do that because of the jumps
m_context << currentIsLong; _context << currentIsLong;
m_context << Instruction::DUP3 << u256(31) << Instruction::LT; _context << Instruction::DUP3 << u256(31) << Instruction::LT;
m_context.appendConditionalJumpTo(regularPath); _context.appendConditionalJumpTo(regularPath);
// Here: long -> short // Here: long -> short
// Read the first word of the data and store it on the stack. Clear the data location and // 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. // then jump to the short -> short case.
// stack: ref new_length current_length ref_value // stack: ref new_length current_length ref_value
solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
m_context << Instruction::POP << Instruction::DUP3; _context << Instruction::POP << Instruction::DUP3;
CompilerUtils(m_context).computeHashStatic(); CompilerUtils(_context).computeHashStatic();
m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
// stack: ref new_length current_length first_word data_location // stack: ref new_length current_length first_word data_location
m_context << Instruction::DUP3; _context << Instruction::DUP3;
convertLengthToSize(_type); ArrayUtils(_context).convertLengthToSize(_type);
m_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
// stack: ref new_length current_length first_word data_location_end data_location // stack: ref new_length current_length first_word data_location_end data_location
clearStorageLoop(IntegerType(256)); ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
m_context << Instruction::POP; _context << Instruction::POP;
// stack: ref new_length current_length first_word // stack: ref new_length current_length first_word
solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
m_context.appendJumpTo(shortToShort); _context.appendJumpTo(shortToShort);
m_context << regularPath; _context << regularPath;
// stack: ref new_length current_length ref_value // stack: ref new_length current_length ref_value
m_context << Instruction::POP; _context << Instruction::POP;
} }
// Change of length for a regular array (i.e. length at location, data at sha3(location)). // Change of length for a regular array (i.e. length at location, data at sha3(location)).
// stack: ref new_length old_length // stack: ref new_length old_length
// store new length // store new length
m_context << Instruction::DUP2; _context << Instruction::DUP2;
if (_type.isByteArray()) if (_type.isByteArray())
// For a "long" byte array, store length as 2*length+1 // For a "long" byte array, store length as 2*length+1
m_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
m_context<< Instruction::DUP4 << Instruction::SSTORE; _context<< Instruction::DUP4 << Instruction::SSTORE;
// skip if size is not reduced // skip if size is not reduced
m_context << Instruction::DUP2 << Instruction::DUP2 _context << Instruction::DUP2 << Instruction::DUP2
<< Instruction::ISZERO << Instruction::GT; << Instruction::ISZERO << Instruction::GT;
m_context.appendConditionalJumpTo(resizeEnd); _context.appendConditionalJumpTo(resizeEnd);
// size reduced, clear the end of the array // size reduced, clear the end of the array
// stack: ref new_length old_length // stack: ref new_length old_length
convertLengthToSize(_type); ArrayUtils(_context).convertLengthToSize(_type);
m_context << Instruction::DUP2; _context << Instruction::DUP2;
convertLengthToSize(_type); ArrayUtils(_context).convertLengthToSize(_type);
// stack: ref new_length old_size new_size // stack: ref new_length old_size new_size
// compute data positions // compute data positions
m_context << Instruction::DUP4; _context << Instruction::DUP4;
CompilerUtils(m_context).computeHashStatic(); CompilerUtils(_context).computeHashStatic();
// stack: ref new_length old_size new_size data_pos // stack: ref new_length old_size new_size data_pos
m_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
// stack: ref new_length data_pos new_size delete_end // stack: ref new_length data_pos new_size delete_end
m_context << Instruction::SWAP2 << Instruction::ADD; _context << Instruction::SWAP2 << Instruction::ADD;
// stack: ref new_length delete_end delete_start // stack: ref new_length delete_end delete_start
if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
clearStorageLoop(IntegerType(256)); ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
else else
clearStorageLoop(*_type.baseType()); ArrayUtils(_context).clearStorageLoop(_type.baseType());
m_context << resizeEnd; _context << resizeEnd;
// cleanup // cleanup
m_context << Instruction::POP << Instruction::POP << Instruction::POP; _context << Instruction::POP << Instruction::POP << Instruction::POP;
solAssert(m_context.stackHeight() == stackHeightStart - 2, ""); solAssert(_context.stackHeight() == stackHeightStart - 2, "");
}
);
} }
void ArrayUtils::clearStorageLoop(Type const& _type) const void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
{ {
unsigned stackHeightStart = m_context.stackHeight(); m_context.callLowLevelFunction(
if (_type.category() == Type::Category::Mapping) "$clearStorageLoop_" + _type->identifier(),
{ 2,
m_context << Instruction::POP; 1,
return; [_type](CompilerContext& _context)
} {
// stack: end_pos pos 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 // jump to and return from the loop to allow for duplicate code removal
eth::AssemblyItem returnTag = m_context.pushNewTag(); eth::AssemblyItem returnTag = _context.pushNewTag();
m_context << Instruction::SWAP2 << Instruction::SWAP1; _context << Instruction::SWAP2 << Instruction::SWAP1;
// stack: <return tag> end_pos pos // stack: <return tag> end_pos pos
eth::AssemblyItem loopStart = m_context.appendJumpToNew(); eth::AssemblyItem loopStart = _context.appendJumpToNew();
m_context << loopStart; _context << loopStart;
// check for loop condition // check for loop condition
m_context << Instruction::DUP1 << Instruction::DUP3 _context << Instruction::DUP1 << Instruction::DUP3
<< Instruction::GT << Instruction::ISZERO; << Instruction::GT << Instruction::ISZERO;
eth::AssemblyItem zeroLoopEnd = m_context.newTag(); eth::AssemblyItem zeroLoopEnd = _context.newTag();
m_context.appendConditionalJumpTo(zeroLoopEnd); _context.appendConditionalJumpTo(zeroLoopEnd);
// delete // delete
m_context << u256(0); _context << u256(0);
StorageItem(m_context, _type).setToZero(SourceLocation(), false); StorageItem(_context, *_type).setToZero(SourceLocation(), false);
m_context << Instruction::POP; _context << Instruction::POP;
// increment // increment
m_context << _type.storageSize() << Instruction::ADD; _context << _type->storageSize() << Instruction::ADD;
m_context.appendJumpTo(loopStart); _context.appendJumpTo(loopStart);
// cleanup // cleanup
m_context << zeroLoopEnd; _context << zeroLoopEnd;
m_context << Instruction::POP << Instruction::SWAP1; _context << Instruction::POP << Instruction::SWAP1;
// "return" // "return"
m_context << Instruction::JUMP; _context << Instruction::JUMP;
m_context << returnTag; _context << returnTag;
solAssert(m_context.stackHeight() == stackHeightStart - 1, ""); solAssert(_context.stackHeight() == stackHeightStart - 1, "");
}
);
} }
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const

View File

@ -22,6 +22,8 @@
#pragma once #pragma once
#include <memory>
namespace dev namespace dev
{ {
namespace solidity namespace solidity
@ -30,6 +32,7 @@ namespace solidity
class CompilerContext; class CompilerContext;
class Type; class Type;
class ArrayType; class ArrayType;
using TypePointer = std::shared_ptr<Type const>;
/** /**
* Class that provides code generation for handling arrays. * 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). /// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
/// Stack pre: end_ref start_ref /// Stack pre: end_ref start_ref
/// Stack post: end_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). /// 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. /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory.
/// Stack pre: length /// Stack pre: length

View File

@ -21,15 +21,18 @@
*/ */
#include <libsolidity/codegen/CompilerContext.h> #include <libsolidity/codegen/CompilerContext.h>
#include <utility> #include <libsolidity/codegen/CompilerUtils.h>
#include <numeric>
#include <boost/algorithm/string/replace.hpp>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/Compiler.h> #include <libsolidity/codegen/Compiler.h>
#include <libsolidity/interface/Version.h> #include <libsolidity/interface/Version.h>
#include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmStack.h> #include <libsolidity/inlineasm/AsmStack.h>
#include <boost/algorithm/string/replace.hpp>
#include <utility>
#include <numeric>
using namespace std; using namespace std;
namespace dev namespace dev
@ -57,6 +60,51 @@ void CompilerContext::startFunction(Declaration const& _function)
*this << functionEntryLabel(_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, void CompilerContext::addVariable(VariableDeclaration const& _declaration,
unsigned _offsetToCurrent) unsigned _offsetToCurrent)
{ {

View File

@ -22,16 +22,20 @@
#pragma once #pragma once
#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 <ostream>
#include <stack> #include <stack>
#include <queue> #include <queue>
#include <utility> #include <utility>
#include <libevmasm/Instruction.h> #include <functional>
#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 dev {
namespace solidity { namespace solidity {
@ -90,6 +94,19 @@ public:
/// as "having code". /// as "having code".
void startFunction(Declaration const& _function); 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; 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). /// Returns the distance of the given local variable from the bottom of the stack (of the current function).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const; unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
@ -248,6 +265,10 @@ private:
CompilerContext *m_runtimeContext; CompilerContext *m_runtimeContext;
/// The index of the runtime subroutine. /// The index of the runtime subroutine.
size_t m_runtimeSub = -1; 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;
}; };
} }

View File

@ -820,37 +820,46 @@ void CompilerUtils::pushZeroValue(Type const& _type)
} }
solAssert(referenceType->location() == DataLocation::Memory, ""); solAssert(referenceType->location() == DataLocation::Memory, "");
m_context << u256(max(32u, _type.calldataEncodedSize())); TypePointer type = _type.shared_from_this();
allocateMemory(); m_context.callLowLevelFunction(
m_context << Instruction::DUP1; "$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)) if (auto structType = dynamic_cast<StructType const*>(type.get()))
for (auto const& member: structType->members(nullptr)) for (auto const& member: structType->members(nullptr))
{ {
pushZeroValue(*member.type); utils.pushZeroValue(*member.type);
storeInMemoryDynamic(*member.type); utils.storeInMemoryDynamic(*member.type);
} }
else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get()))
{ {
if (arrayType->isDynamicallySized()) if (arrayType->isDynamicallySized())
{ {
// zero length // zero length
m_context << u256(0); _context << u256(0);
storeInMemoryDynamic(IntegerType(256)); utils.storeInMemoryDynamic(IntegerType(256));
} }
else if (arrayType->length() > 0) else if (arrayType->length() > 0)
{ {
m_context << arrayType->length() << Instruction::SWAP1; _context << arrayType->length() << Instruction::SWAP1;
// stack: items_to_do memory_pos // stack: items_to_do memory_pos
zeroInitialiseMemoryArray(*arrayType); utils.zeroInitialiseMemoryArray(*arrayType);
// stack: updated_memory_pos // stack: updated_memory_pos
} }
} }
else else
solAssert(false, "Requested initialisation for unknown type: " + _type.toString()); solAssert(false, "Requested initialisation for unknown type: " + type->toString());
// remove the updated memory pointer // remove the updated memory pointer
m_context << Instruction::POP; _context << Instruction::POP;
}
);
} }
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)

View File

@ -827,6 +827,7 @@ void ContractCompiler::appendMissingFunctions()
function->accept(*this); function->accept(*this);
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?"); solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
} }
m_context.appendMissingLowLevelFunctions();
} }
void ContractCompiler::appendModifierOrFunctionCode() void ContractCompiler::appendModifierOrFunctionCode()

View File

@ -8992,6 +8992,28 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment)
compileAndRun(sourceCode, 0, "C2"); 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() BOOST_AUTO_TEST_SUITE_END()
} }