mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #6923 from ethereum/sol-yul-storage-array
[Sol->Yul] Implement .length for storage arrays
This commit is contained in:
		
						commit
						fc64de6d90
					
				| @ -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 <functionName>(array, newLen) { | ||||
| 				if gt(newLen, <maxArrayLength>) { | ||||
| 					invalid() | ||||
| 				} | ||||
| 
 | ||||
| 				let oldLen := <fetchLength>(array) | ||||
| 
 | ||||
| 				// Store new length
 | ||||
| 				sstore(array, newLen) | ||||
| 
 | ||||
| 				// Size was reduced, clear end of array
 | ||||
| 				if lt(newLen, oldLen) { | ||||
| 					let oldSlotCount := <convertToSize>(oldLen) | ||||
| 					let newSlotCount := <convertToSize>(newLen) | ||||
| 					let arrayDataStart := <dataPosition>(array) | ||||
| 					let deleteStart := add(arrayDataStart, newSlotCount) | ||||
| 					let deleteEnd := add(arrayDataStart, oldSlotCount) | ||||
| 					<clearStorageRange>(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 <functionName>(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 <functionName>(length) -> size { | ||||
| 						size := length | ||||
| 						<?multiSlot> | ||||
| 							size := <mul>(<storageSize>, length) | ||||
| 						<!multiSlot> | ||||
| 							// Number of slots rounded up
 | ||||
| 							size := div(add(length, sub(<itemsPerSlot>, 1)), <itemsPerSlot>) | ||||
| 						</multiSlot> | ||||
| 					})") | ||||
| 					("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 <functionName>(length) -> size { | ||||
| 						<?byteArray> | ||||
| 							size := length | ||||
| 						<!byteArray> | ||||
| 							size := <mul>(length, <elementSize>) | ||||
| 						</byteArray> | ||||
| 					})") | ||||
| 					("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), ""); | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -54,7 +54,7 @@ pair<string, string> 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(); | ||||
| 
 | ||||
|  | ||||
| @ -703,7 +703,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) | ||||
| 	} | ||||
| 	case Type::Category::Array: | ||||
| 	{ | ||||
| 		solUnimplementedAssert(false, ""); | ||||
| 		auto const& type = dynamic_cast<ArrayType const&>(*_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<IRStorageArrayLength>( | ||||
| 					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: | ||||
| 	{ | ||||
|  | ||||
| @ -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()); | ||||
| } | ||||
|  | ||||
| @ -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<std::string, unsigned> 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; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user