From 910cb8d32986c751932993a0dddd80687a3b741e Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 6 Jun 2019 14:07:40 +0200 Subject: [PATCH] [Sol->Yul] Implement .length for storage arrays --- libsolidity/codegen/YulUtilFunctions.cpp | 114 ++++++++++++++++++ libsolidity/codegen/YulUtilFunctions.h | 16 +++ .../codegen/ir/IRGeneratorForStatements.cpp | 30 ++++- libsolidity/codegen/ir/IRLValue.cpp | 28 +++++ libsolidity/codegen/ir/IRLValue.h | 19 +++ .../viaYul/array_storage_length_access.sol | 19 +++ 6 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 7724dd57e..4c8b20636 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -517,6 +517,120 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) }); } +std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type) +{ + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!"); + solUnimplementedAssert(_type.baseType()->isValueType(), "..."); + solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "..."); + solUnimplementedAssert(_type.baseType()->storageSize() == 1, ""); + + string functionName = "resize_array_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (array, newLen) { + if gt(newLen, ) { + invalid() + } + + let oldLen := (array) + + // Store new length + sstore(array, newLen) + + // Size was reduced, clear end of array + if lt(newLen, oldLen) { + let oldSlotCount := (oldLen) + let newSlotCount := (newLen) + let arrayDataStart := (array) + let deleteStart := add(arrayDataStart, newSlotCount) + let deleteEnd := add(arrayDataStart, oldSlotCount) + (deleteStart, deleteEnd) + } + })") + ("functionName", functionName) + ("fetchLength", arrayLengthFunction(_type)) + ("convertToSize", arrayConvertLengthToSize(_type)) + ("dataPosition", arrayDataAreaFunction(_type)) + ("clearStorageRange", clearStorageRangeFunction(*_type.baseType())) + ("maxArrayLength", (u256(1) << 64).str()) + .render(); + }); +} + +string YulUtilFunctions::clearStorageRangeFunction(Type const& _type) +{ + string functionName = "clear_storage_range_" + _type.identifier(); + + solUnimplementedAssert(_type.isValueType(), "..."); + + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (start, end) { + for {} lt(start, end) { start := add(start, 1) } + { + sstore(start, 0) + } + } + )") + ("functionName", functionName) + .render(); + }); +} + +string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) +{ + string functionName = "array_convert_length_to_size_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Type const& baseType = *_type.baseType(); + + switch (_type.location()) + { + case DataLocation::Storage: + { + unsigned const baseStorageBytes = baseType.storageBytes(); + solAssert(baseStorageBytes > 0, ""); + solAssert(32 / baseStorageBytes > 0, ""); + + return Whiskers(R"( + function (length) -> size { + size := length + + size := (, length) + + // Number of slots rounded up + size := div(add(length, sub(, 1)), ) + + })") + ("functionName", functionName) + ("multiSlot", baseType.storageSize() > 1) + ("itemsPerSlot", to_string(32 / baseStorageBytes)) + ("storageSize", baseType.storageSize().str()) + ("mul", overflowCheckedUIntMulFunction(TypeProvider::uint256()->numBits())) + .render(); + } + case DataLocation::CallData: // fallthrough + case DataLocation::Memory: + return Whiskers(R"( + function (length) -> size { + + size := length + + size := (length, ) + + })") + ("functionName", functionName) + ("elementSize", _type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize()) + ("byteArray", _type.isByteArray()) + ("mul", overflowCheckedUIntMulFunction(TypeProvider::uint256()->numBits())) + .render(); + default: + solAssert(false, ""); + } + + }); +} string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) { solAssert(_type.dataStoredIn(DataLocation::Memory), ""); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 9b512f767..91b47a180 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -104,6 +104,22 @@ public: std::string overflowCheckedUIntSubFunction(); std::string arrayLengthFunction(ArrayType const& _type); + + /// @returns the name of a function that resizes a storage array + /// signature: (array, newLen) + std::string resizeDynamicArrayFunction(ArrayType const& _type); + + /// @returns the name of a function that will clear the storage area given + /// by the start and end (exclusive) parameters (slots). Only works for value types. + /// signature: (start, end) + std::string clearStorageRangeFunction(Type const& _type); + + /// Returns the name of a function that will convert a given length to the + /// size in memory (number of storage slots or calldata/memory bytes) it + /// will require. + /// signature: (length) -> size + std::string arrayConvertLengthToSize(ArrayType const& _type); + /// @returns the name of a function that computes the number of bytes required /// to store an array in memory given its length (internally encoded, not ABI encoded). /// The function reverts for too large lengths. diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index e9adf36a2..80a09ccbd 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -703,7 +703,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) } case Type::Category::Array: { - solUnimplementedAssert(false, ""); + auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type); + + solAssert(member == "length", ""); + + if (!type.isDynamicallySized()) + defineExpression(_memberAccess) << type.length() << "\n"; + else + switch (type.location()) + { + case DataLocation::CallData: + solUnimplementedAssert(false, ""); + //m_context << Instruction::SWAP1 << Instruction::POP; + break; + case DataLocation::Storage: + setLValue(_memberAccess, make_unique( + m_context, + m_context.variable(_memberAccess.expression()), + *_memberAccess.annotation().type, + type + )); + + break; + case DataLocation::Memory: + solUnimplementedAssert(false, ""); + //m_context << Instruction::MLOAD; + break; + } + + break; } case Type::Category::FixedBytes: { diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp index defabd081..010eecb75 100644 --- a/libsolidity/codegen/ir/IRLValue.cpp +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -137,3 +137,31 @@ string IRStorageItem::setToZero() const { solUnimplemented("Delete for storage location not yet implemented"); } + +IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string _slot, Type const& _type, ArrayType const& _arrayType): + IRLValue(_context, &_type), m_arrayType(_arrayType), m_slot(move(_slot)) +{ + solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!"); +} + +string IRStorageArrayLength::retrieveValue() const +{ + return m_context.utils().arrayLengthFunction(m_arrayType) + "(" + m_slot + ")\n"; +} + +string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const +{ + solAssert(_type == *m_type, "Different type, but might not be an error."); + + return m_context.utils().resizeDynamicArrayFunction(m_arrayType) + + "(" + + m_slot + + ", " + + _value + + ")\n"; +} + +string IRStorageArrayLength::setToZero() const +{ + return storeValue("0", *TypeProvider::uint256()); +} diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h index 3c9338ebd..f8ac5b6be 100644 --- a/libsolidity/codegen/ir/IRLValue.h +++ b/libsolidity/codegen/ir/IRLValue.h @@ -34,6 +34,7 @@ namespace solidity class VariableDeclaration; class IRGenerationContext; class Type; +class ArrayType; /** * Abstract class used to retrieve, delete and store data in LValues. @@ -107,6 +108,24 @@ private: boost::variant const m_offset; }; +/** + * Reference to the "length" member of a dynamically-sized storage array. This is an LValue with special + * semantics since assignments to it might reduce its length and thus the array's members have to be + * deleted. + */ +class IRStorageArrayLength: public IRLValue +{ +public: + IRStorageArrayLength(IRGenerationContext& _context, std::string _slot, Type const& _type, ArrayType const& _arrayType); + + std::string retrieveValue() const override; + std::string storeValue(std::string const& _value, Type const& _type) const override; + std::string setToZero() const override; + +private: + ArrayType const& m_arrayType; + std::string const m_slot; +}; } } diff --git a/test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol b/test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol new file mode 100644 index 000000000..b972d8ade --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol @@ -0,0 +1,19 @@ +contract C { + uint[] storageArray; + function set_get_length(uint256 len) public returns (uint256) + { + storageArray.length = len; + return storageArray.length; + } + +} +// ==== +// compileViaYul: true +// ---- +// set_get_length(uint256): 0 -> 0 +// set_get_length(uint256): 1 -> 1 +// set_get_length(uint256): 10 -> 10 +// set_get_length(uint256): 20 -> 20 +// set_get_length(uint256): 0 -> 0 +// set_get_length(uint256): 0xFF -> 0xFF +// set_get_length(uint256): 0xFFFF -> 0xFFFF