diff options
author | Christian <c@ethdev.com> | 2015-02-26 03:27:55 +0800 |
---|---|---|
committer | Christian <c@ethdev.com> | 2015-02-28 05:52:19 +0800 |
commit | 87365f7612336c4c7f443aac568f8a515bc9bd35 (patch) | |
tree | e304628c5c65d0fb33082d1c8f9e49776fb0a70c | |
parent | a5b4f18dd7291e403237061d5f9660db0299601d (diff) | |
download | dexon-solidity-87365f7612336c4c7f443aac568f8a515bc9bd35.tar.gz dexon-solidity-87365f7612336c4c7f443aac568f8a515bc9bd35.tar.zst dexon-solidity-87365f7612336c4c7f443aac568f8a515bc9bd35.zip |
Shortening of dynamic arrays.
-rw-r--r-- | ArrayUtils.cpp | 284 | ||||
-rw-r--r-- | ArrayUtils.h | 72 | ||||
-rw-r--r-- | CompilerUtils.cpp | 170 | ||||
-rw-r--r-- | CompilerUtils.h | 13 | ||||
-rw-r--r-- | ExpressionCompiler.cpp | 8 | ||||
-rw-r--r-- | LValue.cpp | 122 | ||||
-rw-r--r-- | LValue.h | 44 |
7 files changed, 477 insertions, 236 deletions
diff --git a/ArrayUtils.cpp b/ArrayUtils.cpp new file mode 100644 index 00000000..07949c79 --- /dev/null +++ b/ArrayUtils.cpp @@ -0,0 +1,284 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * Code generation utils that handle arrays. + */ + +#include <libsolidity/ArrayUtils.h> +#include <libevmcore/Instruction.h> +#include <libsolidity/CompilerContext.h> +#include <libsolidity/CompilerUtils.h> +#include <libsolidity/Types.h> +#include <libsolidity/Utils.h> +#include <libsolidity/LValue.h> + +using namespace std; +using namespace dev; +using namespace solidity; + +void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const +{ + // stack layout: [source_ref] target_ref (top) + // need to leave target_ref on the stack at the end + solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); + solAssert(_targetType.isByteArray(), "Non byte arrays not yet implemented here."); + solAssert(_sourceType.isByteArray(), "Non byte arrays not yet implemented here."); + + switch (_sourceType.getLocation()) + { + case ArrayType::Location::CallData: + { + // This also assumes that after "length" we only have zeros, i.e. it cannot be used to + // slice a byte array from calldata. + + // stack: source_offset source_len target_ref + // fetch old length and convert to words + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + convertLengthToSize(_targetType); + // stack here: source_offset source_len target_ref target_length_words + // actual array data is stored at SHA3(storage_offset) + m_context << eth::Instruction::DUP2; + CompilerUtils(m_context).computeHashStatic(); + // compute target_data_end + m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::ADD + << eth::Instruction::SWAP1; + // stack here: source_offset source_len target_ref target_data_end target_data_ref + // store length (in bytes) + m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::DUP5 + << eth::Instruction::SSTORE; + // jump to end if length is zero + m_context << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEnd); + // store start offset + m_context << eth::Instruction::DUP5; + // stack now: source_offset source_len target_ref target_data_end target_data_ref calldata_offset + eth::AssemblyItem copyLoopStart = m_context.newTag(); + m_context << copyLoopStart + // copy from calldata and store + << eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD + << eth::Instruction::DUP3 << eth::Instruction::SSTORE + // increment target_data_ref by 1 + << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD + // increment calldata_offset by 32 + << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD + // check for loop condition + << eth::Instruction::DUP1 << eth::Instruction::DUP6 << eth::Instruction::GT; + m_context.appendConditionalJumpTo(copyLoopStart); + m_context << eth::Instruction::POP; + m_context << copyLoopEnd; + + // now clear leftover bytes of the old value + // stack now: source_offset source_len target_ref target_data_end target_data_ref + clearStorageLoop(IntegerType(256)); + // stack now: source_offset source_len target_ref target_data_end + + m_context << eth::Instruction::POP << eth::Instruction::SWAP2 + << eth::Instruction::POP << eth::Instruction::POP; + break; + } + case ArrayType::Location::Storage: + { + // this copies source to target and also clears target if it was larger + + // stack: source_ref target_ref + // store target_ref + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; + // fetch lengthes + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP2 + << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + // stack: target_ref target_len_bytes target_ref source_ref source_len_bytes + // store new target length + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SSTORE; + // compute hashes (data positions) + m_context << eth::Instruction::SWAP2; + CompilerUtils(m_context).computeHashStatic(); + m_context << eth::Instruction::SWAP1; + CompilerUtils(m_context).computeHashStatic(); + // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos + // convert lengthes from bytes to storage slots + m_context << u256(31) << u256(32) << eth::Instruction::DUP1 << eth::Instruction::DUP3 + << eth::Instruction::DUP8 << eth::Instruction::ADD << eth::Instruction::DIV + << eth::Instruction::SWAP2 + << eth::Instruction::DUP6 << eth::Instruction::ADD << eth::Instruction::DIV; + // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len + // @todo we might be able to go without a third counter + m_context << u256(0); + // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter + eth::AssemblyItem copyLoopStart = m_context.newTag(); + m_context << copyLoopStart; + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 + << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEnd); + // copy + m_context << eth::Instruction::DUP4 << eth::Instruction::DUP2 << eth::Instruction::ADD + << eth::Instruction::SLOAD + << eth::Instruction::DUP6 << eth::Instruction::DUP3 << eth::Instruction::ADD + << eth::Instruction::SSTORE; + // increment + m_context << u256(1) << eth::Instruction::ADD; + m_context.appendJumpTo(copyLoopStart); + m_context << copyLoopEnd; + + // zero-out leftovers in target + // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter + // add counter to target_data_pos + m_context << eth::Instruction::DUP5 << eth::Instruction::ADD + << eth::Instruction::SWAP5 << eth::Instruction::POP; + // stack: target_ref target_len_bytes target_data_pos_updated target_data_pos source_data_pos target_len source_len + // add length to target_data_pos to get target_data_end + m_context << eth::Instruction::POP << eth::Instruction::DUP3 << eth::Instruction::ADD + << eth::Instruction::SWAP4 + << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + // stack: target_ref target_data_end target_data_pos_updated + clearStorageLoop(IntegerType(256)); + m_context << eth::Instruction::POP; + break; + } + default: + solAssert(false, "Given byte array location not implemented."); + } +} + +void ArrayUtils::clearArray(ArrayType const& _type) const +{ + solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); + if (_type.isDynamicallySized()) + clearDynamicArray(_type); + else if (_type.getLength() == 0) + m_context << eth::Instruction::POP; + else if (_type.getLength() < 5) // unroll loop for small arrays @todo choose a good value + { + for (unsigned i = 1; i < _type.getLength(); ++i) + { + StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false); + m_context << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; + } + StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true); + } + else + { + m_context + << eth::Instruction::DUP1 << u256(_type.getLength()) + << u256(_type.getBaseType()->getStorageSize()) + << eth::Instruction::MUL << eth::Instruction::ADD << eth::Instruction::SWAP1; + clearStorageLoop(*_type.getBaseType()); + m_context << eth::Instruction::POP; + } +} + +void ArrayUtils::clearDynamicArray(ArrayType const& _type) const +{ + solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + + // fetch length + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + // set length to zero + m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE; + // stack: ref old_length + convertLengthToSize(_type); + // compute data positions + m_context << eth::Instruction::SWAP1; + CompilerUtils(m_context).computeHashStatic(); + // stack: len data_pos (len is in slots for byte array and in items for other arrays) + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD + << eth::Instruction::SWAP1; + // stack: data_pos_end data_pos + if (_type.isByteArray()) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.getBaseType()); + // cleanup + m_context << eth::Instruction::POP; +} + +void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const +{ + solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + + eth::AssemblyItem resizeEnd = m_context.newTag(); + + // stack: ref new_length + // fetch old length + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: ref new_length old_length + // store new length + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP4 << eth::Instruction::SSTORE; + // skip if size is not reduced + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP2 + << eth::Instruction::ISZERO << eth::Instruction::GT; + m_context.appendConditionalJumpTo(resizeEnd); + + // size reduced, clear the end of the array + // stack: ref new_length old_length + convertLengthToSize(_type); + m_context << eth::Instruction::DUP2; + convertLengthToSize(_type); + // stack: ref new_length old_size new_size + // compute data positions + m_context << eth::Instruction::DUP4; + CompilerUtils(m_context).computeHashStatic(); + // stack: ref new_length old_size new_size data_pos + m_context << eth::Instruction::SWAP2 << eth::Instruction::DUP3 << eth::Instruction::ADD; + // stack: ref new_length data_pos new_size delete_end + m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD; + // stack: ref new_length delete_end delete_start + if (_type.isByteArray()) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.getBaseType()); + + m_context << resizeEnd; + // cleanup + m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; +} + +void ArrayUtils::clearStorageLoop(Type const& _type) const +{ + // stack: end_pos pos + eth::AssemblyItem loopStart = m_context.newTag(); + m_context << loopStart; + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 + << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem zeroLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(zeroLoopEnd); + // delete + StorageItem(m_context, _type).setToZero(SourceLocation(), false); + // increment + m_context << u256(1) << eth::Instruction::ADD; + m_context.appendJumpTo(loopStart); + // cleanup + m_context << zeroLoopEnd; + m_context << eth::Instruction::POP; +} + +void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType) const +{ + if (_arrayType.isByteArray()) + m_context << u256(31) << eth::Instruction::ADD + << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + else if (_arrayType.getBaseType()->getStorageSize() > 1) + m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; +} + diff --git a/ArrayUtils.h b/ArrayUtils.h new file mode 100644 index 00000000..f080dce6 --- /dev/null +++ b/ArrayUtils.h @@ -0,0 +1,72 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * Code generation utils that handle arrays. + */ + +#pragma once + +namespace dev +{ +namespace solidity +{ + +class CompilerContext; +class Type; +class ArrayType; + +/** + * Class that provides code generation for handling arrays. + */ +class ArrayUtils +{ +public: + ArrayUtils(CompilerContext& _context): m_context(_context) {} + + /// Copies an array to an array in storage. + /// Stack pre: [source_reference] target_reference + /// Stack post: target_reference + void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; + /// Clears the given dynamic or static array. + /// Stack pre: reference + /// Stack post: + void clearArray(ArrayType const& _type) const; + /// Clears the length and data elements of the array referenced on the stack. + /// Stack pre: reference + /// Stack post: + void clearDynamicArray(ArrayType const& _type) const; + /// Changes the size of a dynamic array and clears the tail if it is shortened. + /// Stack pre: reference new_length + /// Stack post: + void resizeDynamicArray(ArrayType const& _type) const; + /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). + /// Stack pre: end_ref start_ref + /// Stack post: end_ref + void clearStorageLoop(Type const& _type) const; + /// Converts length to size (multiplies by size on stack), rounds up for byte arrays. + /// Stack pre: length + /// Stack post: size + void convertLengthToSize(ArrayType const& _arrayType) const; + +private: + CompilerContext& m_context; +}; + +} +} diff --git a/CompilerUtils.cpp b/CompilerUtils.cpp index c7ce9445..826651e6 100644 --- a/CompilerUtils.cpp +++ b/CompilerUtils.cpp @@ -164,134 +164,6 @@ void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundari m_context << u256(length) << u256(0) << eth::Instruction::SHA3; } -void CompilerUtils::copyByteArrayToStorage( - ArrayType const& _targetType, ArrayType const& _sourceType) const -{ - // stack layout: [source_ref] target_ref (top) - // need to leave target_ref on the stack at the end - solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); - solAssert(_targetType.isByteArray(), "Non byte arrays not yet implemented here."); - solAssert(_sourceType.isByteArray(), "Non byte arrays not yet implemented here."); - - switch (_sourceType.getLocation()) - { - case ArrayType::Location::CallData: - { - // This also assumes that after "length" we only have zeros, i.e. it cannot be used to - // slice a byte array from calldata. - - // stack: source_offset source_len target_ref - // fetch old length and convert to words - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; - m_context << u256(31) << eth::Instruction::ADD - << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - // stack here: source_offset source_len target_ref target_length_words - // actual array data is stored at SHA3(storage_offset) - m_context << eth::Instruction::DUP2; - CompilerUtils(m_context).computeHashStatic(); - // compute target_data_end - m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::ADD - << eth::Instruction::SWAP1; - // stack here: source_offset source_len target_ref target_data_end target_data_ref - // store length (in bytes) - m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::DUP5 - << eth::Instruction::SSTORE; - // jump to end if length is zero - m_context << eth::Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(copyLoopEnd); - // store start offset - m_context << eth::Instruction::DUP5; - // stack now: source_offset source_len target_ref target_data_end target_data_ref calldata_offset - eth::AssemblyItem copyLoopStart = m_context.newTag(); - m_context << copyLoopStart - // copy from calldata and store - << eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD - << eth::Instruction::DUP3 << eth::Instruction::SSTORE - // increment target_data_ref by 1 - << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD - // increment calldata_offset by 32 - << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD - // check for loop condition - << eth::Instruction::DUP1 << eth::Instruction::DUP6 << eth::Instruction::GT; - m_context.appendConditionalJumpTo(copyLoopStart); - m_context << eth::Instruction::POP; - m_context << copyLoopEnd; - - // now clear leftover bytes of the old value - // stack now: source_offset source_len target_ref target_data_end target_data_ref - clearStorageLoop(); - // stack now: source_offset source_len target_ref target_data_end - - m_context << eth::Instruction::POP << eth::Instruction::SWAP2 - << eth::Instruction::POP << eth::Instruction::POP; - break; - } - case ArrayType::Location::Storage: - { - // this copies source to target and also clears target if it was larger - - // stack: source_ref target_ref - // store target_ref - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - // fetch lengthes - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP2 - << eth::Instruction::DUP1 << eth::Instruction::SLOAD; - // stack: target_ref target_len_bytes target_ref source_ref source_len_bytes - // store new target length - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SSTORE; - // compute hashes (data positions) - m_context << eth::Instruction::SWAP2; - CompilerUtils(m_context).computeHashStatic(); - m_context << eth::Instruction::SWAP1; - CompilerUtils(m_context).computeHashStatic(); - // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos - // convert lengthes from bytes to storage slots - m_context << u256(31) << u256(32) << eth::Instruction::DUP1 << eth::Instruction::DUP3 - << eth::Instruction::DUP8 << eth::Instruction::ADD << eth::Instruction::DIV - << eth::Instruction::SWAP2 - << eth::Instruction::DUP6 << eth::Instruction::ADD << eth::Instruction::DIV; - // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len - // @todo we might be able to go without a third counter - m_context << u256(0); - // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter - eth::AssemblyItem copyLoopStart = m_context.newTag(); - m_context << copyLoopStart; - // check for loop condition - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 - << eth::Instruction::GT << eth::Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(copyLoopEnd); - // copy - m_context << eth::Instruction::DUP4 << eth::Instruction::DUP2 << eth::Instruction::ADD - << eth::Instruction::SLOAD - << eth::Instruction::DUP6 << eth::Instruction::DUP3 << eth::Instruction::ADD - << eth::Instruction::SSTORE; - // increment - m_context << u256(1) << eth::Instruction::ADD; - m_context.appendJumpTo(copyLoopStart); - m_context << copyLoopEnd; - - // zero-out leftovers in target - // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter - // add counter to target_data_pos - m_context << eth::Instruction::DUP5 << eth::Instruction::ADD - << eth::Instruction::SWAP5 << eth::Instruction::POP; - // stack: target_ref target_len_bytes target_data_pos_updated target_data_pos source_data_pos target_len source_len - // add length to target_data_pos to get target_data_end - m_context << eth::Instruction::POP << eth::Instruction::DUP3 << eth::Instruction::ADD - << eth::Instruction::SWAP4 - << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; - // stack: target_ref target_data_end target_data_pos_updated - clearStorageLoop(); - m_context << eth::Instruction::POP; - break; - } - default: - solAssert(false, "Given byte array location not implemented."); - } -} - unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { unsigned _encodedSize = _type.getCalldataEncodedSize(); @@ -316,28 +188,6 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda return numBytes; } -void CompilerUtils::clearByteArray(ArrayType const& _type) const -{ - solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); - solAssert(_type.isByteArray(), "Non byte arrays not yet implemented here."); - - // fetch length - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; - // set length to zero - m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE; - // convert length from bytes to storage slots - m_context << u256(31) << eth::Instruction::ADD - << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - // compute data positions - m_context << eth::Instruction::SWAP1; - CompilerUtils(m_context).computeHashStatic(); - // stack: len data_pos - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD - << eth::Instruction::SWAP1; - clearStorageLoop(); - // cleanup - m_context << eth::Instruction::POP; -} unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const { @@ -356,25 +206,5 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBou return numBytes; } -void CompilerUtils::clearStorageLoop() const -{ - // stack: end_pos pos - eth::AssemblyItem loopStart = m_context.newTag(); - m_context << loopStart; - // check for loop condition - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 - << eth::Instruction::GT << eth::Instruction::ISZERO; - eth::AssemblyItem zeroLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(zeroLoopEnd); - // zero out - m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE; - // increment - m_context << u256(1) << eth::Instruction::ADD; - m_context.appendJumpTo(loopStart); - // cleanup - m_context << zeroLoopEnd; - m_context << eth::Instruction::POP; -} - } } diff --git a/CompilerUtils.h b/CompilerUtils.h index 2fb97d80..2df85f11 100644 --- a/CompilerUtils.h +++ b/CompilerUtils.h @@ -79,15 +79,6 @@ public: /// @note Only works for types of fixed size. void computeHashStatic(Type const& _type = IntegerType(256), bool _padToWordBoundaries = false); - /// Copies a byte array to a byte array in storage. - /// Stack pre: [source_reference] target_reference - /// Stack post: target_reference - void copyByteArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; - /// Clears the length and data elements of the byte array referenced on the stack. - /// Stack pre: reference - /// Stack post: - void clearByteArray(ArrayType const& _type) const; - /// Bytes we need to the start of call data. /// - The size in bytes of the function (hash) identifier. static const unsigned int dataStartOffset; @@ -97,10 +88,6 @@ private: unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const; /// Loads type from memory assuming memory offset is on stack top. unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries); - /// Appends a loop that clears a sequence of storage slots (excluding end). - /// Stack pre: end_ref start_ref - /// Stack post: end_ref - void clearStorageLoop() const; CompilerContext& m_context; }; diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index 430e46b0..cdf9436d 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -93,7 +93,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& m_context << eth::Instruction::DUP1 << structType->getStorageOffsetOfMember(names[i]) << eth::Instruction::ADD; - StorageItem(m_context, types[i]).retrieveValue(SourceLocation(), true); + StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true); solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); m_context << eth::Instruction::SWAP1; retSizeOnStack += types[i]->getSizeOnStack(); @@ -104,7 +104,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { // simple value solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); - StorageItem(m_context, returnType).retrieveValue(SourceLocation(), true); + StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); retSizeOnStack = returnType->getSizeOnStack(); } solAssert(retSizeOnStack <= 15, "Stack too deep."); @@ -680,7 +680,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; break; case ArrayType::Location::Storage: - setLValueToStorageItem(_memberAccess); + setLValue<StorageArrayLength>(_memberAccess, type); break; default: solAssert(false, "Unsupported array location."); @@ -1044,7 +1044,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) { - setLValue<StorageItem>(_expression, _expression.getType()); + setLValue<StorageItem>(_expression, *_expression.getType()); } } @@ -32,15 +32,14 @@ using namespace solidity; StackVariable::StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration): - LValue(_compilerContext, _declaration.getType()), + LValue(_compilerContext, *_declaration.getType()), m_baseStackOffset(m_context.getBaseStackOffsetOfVariable(_declaration)), - m_size(m_dataType->getSizeOnStack()) + m_size(m_dataType.getSizeOnStack()) { } -void StackVariable::retrieveValue(SourceLocation const& _location, bool _remove) const +void StackVariable::retrieveValue(SourceLocation const& _location, bool) const { - (void)_remove; unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset); if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory BOOST_THROW_EXCEPTION(CompilerError() @@ -49,9 +48,8 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool _remove) m_context << eth::dupInstruction(stackPos + 1); } -void StackVariable::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const +void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const { - (void)_sourceType; unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; if (stackDiff > 16) BOOST_THROW_EXCEPTION(CompilerError() @@ -63,7 +61,7 @@ void StackVariable::storeValue(Type const& _sourceType, SourceLocation const& _l retrieveValue(_location); } -void StackVariable::setToZero(SourceLocation const& _location) const +void StackVariable::setToZero(SourceLocation const& _location, bool) const { unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset); if (stackDiff > 16) @@ -77,20 +75,20 @@ void StackVariable::setToZero(SourceLocation const& _location) const StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): - StorageItem(_compilerContext, _declaration.getType()) + StorageItem(_compilerContext, *_declaration.getType()) { m_context << m_context.getStorageLocationOfVariable(_declaration); } -StorageItem::StorageItem(CompilerContext& _compilerContext, TypePointer const& _type): +StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): LValue(_compilerContext, _type) { - if (m_dataType->isValueType()) + if (m_dataType.isValueType()) { - solAssert(m_dataType->getStorageSize() == m_dataType->getSizeOnStack(), ""); - solAssert(m_dataType->getStorageSize() <= numeric_limits<unsigned>::max(), - "The storage size of " + m_dataType->toString() + " should fit in an unsigned"); - m_size = unsigned(m_dataType->getStorageSize()); + solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), ""); + solAssert(m_dataType.getStorageSize() <= numeric_limits<unsigned>::max(), + "The storage size of " + m_dataType.toString() + " should fit in an unsigned"); + m_size = unsigned(m_dataType.getStorageSize()); } else m_size = 0; // unused @@ -98,7 +96,7 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, TypePointer const& _ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const { - if (!m_dataType->isValueType()) + if (!m_dataType.isValueType()) return; // no distinction between value and reference for non-value types if (!_remove) m_context << eth::Instruction::DUP1; @@ -118,7 +116,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const { // stack layout: value value ... value target_ref - if (m_dataType->isValueType()) + if (m_dataType.isValueType()) { if (!_move) // copy values { @@ -143,20 +141,20 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc } else { - solAssert(_sourceType.getCategory() == m_dataType->getCategory(), + solAssert(_sourceType.getCategory() == m_dataType.getCategory(), "Wrong type conversation for assignment."); - if (m_dataType->getCategory() == Type::Category::Array) + if (m_dataType.getCategory() == Type::Category::Array) { - CompilerUtils(m_context).copyByteArrayToStorage( - dynamic_cast<ArrayType const&>(*m_dataType), + ArrayUtils(m_context).copyArrayToStorage( + dynamic_cast<ArrayType const&>(m_dataType), dynamic_cast<ArrayType const&>(_sourceType)); if (_move) m_context << eth::Instruction::POP; } - else if (m_dataType->getCategory() == Type::Category::Struct) + else if (m_dataType.getCategory() == Type::Category::Struct) { // stack layout: source_ref target_ref - auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + auto const& structType = dynamic_cast<StructType const&>(m_dataType); solAssert(structType == _sourceType, "Struct assignment with conversion."); for (auto const& member: structType.getMembers()) { @@ -167,12 +165,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc m_context << structType.getStorageOffsetOfMember(member.first) << eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD; // stack: source_ref target_ref member_offset source_member_ref - StorageItem(m_context, memberType).retrieveValue(_location, true); + StorageItem(m_context, *memberType).retrieveValue(_location, true); // stack: source_ref target_ref member_offset source_value... m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; // stack: source_ref target_ref member_offset source_value... target_member_ref - StorageItem(m_context, memberType).storeValue(*memberType, _location, true); + StorageItem(m_context, *memberType).storeValue(*memberType, _location, true); m_context << eth::Instruction::POP; } if (_move) @@ -187,16 +185,18 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc } } - -void StorageItem::setToZero(SourceLocation const& _location) const +void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const { - (void)_location; - if (m_dataType->getCategory() == Type::Category::Array) - CompilerUtils(m_context).clearByteArray(dynamic_cast<ArrayType const&>(*m_dataType)); - else if (m_dataType->getCategory() == Type::Category::Struct) + if (m_dataType.getCategory() == Type::Category::Array) + { + if (!_removeReference) + m_context << eth::Instruction::DUP1; + ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType)); + } + else if (m_dataType.getCategory() == Type::Category::Struct) { // stack layout: ref - auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + auto const& structType = dynamic_cast<StructType const&>(m_dataType); for (auto const& member: structType.getMembers()) { // zero each member that is not a mapping @@ -205,19 +205,61 @@ void StorageItem::setToZero(SourceLocation const& _location) const continue; m_context << structType.getStorageOffsetOfMember(member.first) << eth::Instruction::DUP2 << eth::Instruction::ADD; - StorageItem(m_context, memberType).setToZero(); + StorageItem(m_context, *memberType).setToZero(); } - m_context << eth::Instruction::POP; + if (_removeReference) + m_context << eth::Instruction::POP; } else { - if (m_size == 0) + if (m_size == 0 && _removeReference) m_context << eth::Instruction::POP; - for (unsigned i = 0; i < m_size; ++i) - if (i + 1 >= m_size) - m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; - else - m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::ADD; + else if (m_size == 1) + m_context + << u256(0) << (_removeReference ? eth::Instruction::SWAP1 : eth::Instruction::DUP2) + << eth::Instruction::SSTORE; + else + { + if (!_removeReference) + m_context << eth::Instruction::DUP1; + for (unsigned i = 0; i < m_size; ++i) + if (i + 1 >= m_size) + m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + else + m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::ADD; + } } } + + +StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType): + LValue(_compilerContext, *_arrayType.getMemberType("length")), + m_arrayType(_arrayType) +{ + solAssert(m_arrayType.isDynamicallySized(), ""); +} + +void StorageArrayLength::retrieveValue(SourceLocation const& _location, bool _remove) const +{ + if (!_remove) + m_context << eth::Instruction::DUP1; + m_context << eth::Instruction::SLOAD; +} + +void StorageArrayLength::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const +{ + if (_move) + m_context << eth::Instruction::SWAP1; + else + m_context << eth::Instruction::DUP2; + ArrayUtils(m_context).resizeDynamicArray(m_arrayType); +} + +void StorageArrayLength::setToZero(SourceLocation const& _location, bool _removeReference) const +{ + if (!_removeReference) + m_context << eth::Instruction::DUP1; + ArrayUtils(m_context).clearDynamicArray(m_arrayType); +} + @@ -24,6 +24,7 @@ #include <memory> #include <libevmcore/SourceLocation.h> +#include <libsolidity/ArrayUtils.h> namespace dev { @@ -32,6 +33,7 @@ namespace solidity class Declaration; class Type; +class ArrayType; class CompilerContext; /** @@ -40,7 +42,7 @@ class CompilerContext; class LValue { protected: - LValue(CompilerContext& _compilerContext, std::shared_ptr<Type const> const& _dataType): + LValue(CompilerContext& _compilerContext, Type const& _dataType): m_context(_compilerContext), m_dataType(_dataType) {} public: @@ -56,13 +58,14 @@ public: /// Stack post: if !_move: value_of(lvalue_ref) virtual void storeValue(Type const& _sourceType, SourceLocation const& _location = SourceLocation(), bool _move = false) const = 0; - /// Stores zero in the lvalue. + /// Stores zero in the lvalue. Removes the reference from the stack if @a _removeReference is true. /// @a _location is the source location of the requested operation - virtual void setToZero(SourceLocation const& _location = SourceLocation()) const = 0; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const = 0; protected: CompilerContext& m_context; - std::shared_ptr<Type const> m_dataType; + Type const& m_dataType; }; /** @@ -71,13 +74,14 @@ protected: class StackVariable: public LValue { public: - explicit StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration); + StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration); virtual bool storesReferenceOnStack() const { return false; } virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue(Type const& _sourceType, SourceLocation const& _location = SourceLocation(), bool _move = false) const override; - virtual void setToZero(SourceLocation const& _location = SourceLocation()) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const override; private: /// Base stack offset (@see CompilerContext::getBaseStackOffsetOfVariable) of the local variable. @@ -93,14 +97,15 @@ class StorageItem: public LValue { public: /// Constructs the LValue and pushes the location of @a _declaration onto the stack. - explicit StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); + StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); /// Constructs the LValue and assumes that the storage reference is already on the stack. - explicit StorageItem(CompilerContext& _compilerContext, std::shared_ptr<Type const> const& _type); + StorageItem(CompilerContext& _compilerContext, Type const& _type); virtual bool storesReferenceOnStack() const { return true; } virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue(Type const& _sourceType, SourceLocation const& _location = SourceLocation(), bool _move = false) const override; - virtual void setToZero(SourceLocation const& _location = SourceLocation()) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const override; private: /// Number of stack elements occupied by the value (not the reference). @@ -108,5 +113,26 @@ private: unsigned m_size; }; +/** + * Reference to the "length" member of a dynamically-sized array. This is an LValue with special + * semantics since assignments to it might reduce its length and thus arrays members have to be + * deleted. + */ +class StorageArrayLength: public LValue +{ +public: + /// Constructs the LValue, assumes that the reference to the array head is already on the stack. + StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType); + virtual bool storesReferenceOnStack() const { return true; } + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue(Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), bool _move = false) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const override; + +private: + ArrayType const& m_arrayType; +}; + } } |