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..ec828ce4f 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -78,9 +78,10 @@ public: std::string shiftRightFunction(size_t _numBits); std::string shiftRightFunctionDynamic(); - /// @returns the name of a function f(value, toInsert) -> newValue which replaces the + /// @returns the name of a function which replaces the /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant /// byte) by the _numBytes least significant bytes of `toInsert`. + /// signature: (value, toInsert) -> result std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); /// signature: (value, shiftBytes, toInsert) -> result @@ -88,10 +89,13 @@ public: /// @returns the name of a function that rounds its input to the next multiple /// of 32 or the input if it is a multiple of 32. + /// signature: (value) -> result std::string roundUpFunction(); + /// signature: (x, y) -> sum std::string overflowCheckedUIntAddFunction(size_t _bits); + /// signature: (x, y) -> product std::string overflowCheckedUIntMulFunction(size_t _bits); /// @returns name of function to perform division on integers. @@ -101,9 +105,29 @@ public: /// @returns computes the difference between two values. /// Assumes the input to be in range for the type. + /// signature: (x, y) -> diff std::string overflowCheckedUIntSubFunction(); + /// @returns the name of a function that fetches the length of the given + /// array + /// signature: (array) -> length 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/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index acf74805a..17b44fcd8 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -54,7 +54,7 @@ pair IRGenerator::run(ContractDefinition const& _contract) string errorMessage; for (auto const& error: asmStack.errors()) errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error); - solAssert(false, "Invalid IR generated:\n" + errorMessage + "\n" + ir); + solAssert(false, ir + "\n\nInvalid IR generated:\n" + errorMessage + "\n"); } asmStack.optimize(); 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