diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index d8c6bbb45..0eea475f9 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -808,7 +808,7 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) string functionName = "array_length_" + _type.identifier(); return m_functionCollector.createFunction(functionName, [&]() { Whiskers w(R"( - function (value) -> length { + function (value, len) -> length { length := mload(value) @@ -819,6 +819,9 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) length := (length) + + length := len + length := @@ -830,17 +833,14 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) w("length", toCompactHexWithPrefix(_type.length())); w("memory", _type.location() == DataLocation::Memory); w("storage", _type.location() == DataLocation::Storage); + w("calldata", _type.location() == DataLocation::CallData); if (_type.location() == DataLocation::Storage) { w("byteArray", _type.isByteArray()); if (_type.isByteArray()) w("extractByteArrayLength", extractByteArrayLengthFunction()); } - if (_type.isDynamicallySized()) - solAssert( - _type.location() != DataLocation::CallData, - "called regular array length function on calldata array" - ); + return w.render(); }); } @@ -1185,6 +1185,105 @@ string YulUtilFunctions::clearStorageStructFunction(StructType const& _type) }); } +string YulUtilFunctions::copyArrayToStorage(ArrayType const& _fromType, ArrayType const& _toType) +{ + solAssert( + *_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast(_toType), + "" + ); + solUnimplementedAssert(!_fromType.isByteArray(), ""); + solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), ""); + + string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier(); + return m_functionCollector.createFunction(functionName, [&](){ + Whiskers templ(R"( + function (slot, value, len) { + let length := (value, len) + + (slot, length) + + + let srcPtr := + + add(value, 0x20) + + value + + + let elementSlot := (slot) + let elementOffset := 0 + + for { let i := 0 } lt(i, length) {i := add(i, 1)} { + + let := + + (value, srcPtr) + + srcPtr + + + + := () + + + + + let := (srcPtr) + + + (elementSlot, elementOffset, ) + + srcPtr := add(srcPtr, ) + + + elementOffset := add(elementOffset, ) + if gt(elementOffset, sub(32, )) { + elementOffset := 0 + elementSlot := add(elementSlot, 1) + } + + elementSlot := add(elementSlot, ) + elementOffset := 0 + + } + } + )"); + templ("functionName", functionName); + bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData); + templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata); + templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory)); + templ("fromCalldata", fromCalldata); + templ("isToDynamic", _toType.isDynamicallySized()); + templ("isFromMemoryDynamic", _fromType.isDynamicallySized() && _fromType.dataStoredIn(DataLocation::Memory)); + if (fromCalldata) + { + templ("dynamicallySizedBase", _fromType.baseType()->isDynamicallySized()); + templ("dynamicallyEncodedBase", _fromType.baseType()->isDynamicallyEncoded()); + if (_fromType.baseType()->isDynamicallyEncoded()) + templ("accessCalldataTail", accessCalldataTailFunction(*_fromType.baseType())); + } + if (_toType.isDynamicallySized()) + templ("resizeArray", resizeDynamicArrayFunction(_toType)); + templ("arrayLength",arrayLengthFunction(_fromType)); + templ("isValueType", _fromType.baseType()->isValueType()); + templ("dstDataLocation", arrayDataAreaFunction(_toType)); + if (!fromCalldata || _fromType.baseType()->isValueType()) + templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata)); + templ("elementValues", suffixedVariableNameList( + "elementValue_", + 0, + _fromType.baseType()->stackItems().size() + )); + templ("updateStorageValue", updateStorageValueFunction(*_fromType.baseType(), *_toType.baseType())); + templ("stride", to_string(fromCalldata ? _fromType.calldataStride() : _fromType.memoryStride())); + templ("multipleItemsPerSlot", _toType.storageStride() <= 16); + templ("storageStride", to_string(_toType.storageStride())); + templ("storageSize", _toType.baseType()->storageSize().str()); + + return templ.render(); + }); +} + string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) { string functionName = "array_convert_length_to_size_" + _type.identifier(); @@ -1755,23 +1854,39 @@ string YulUtilFunctions::updateStorageValueFunction( else { auto const* toReferenceType = dynamic_cast(&_toType); - auto const* fromReferenceType = dynamic_cast(&_toType); + auto const* fromReferenceType = dynamic_cast(&_fromType); solAssert(fromReferenceType && toReferenceType, ""); solAssert(*toReferenceType->copyForLocation( fromReferenceType->location(), fromReferenceType->isPointer() ).get() == *fromReferenceType, ""); + solUnimplementedAssert(fromReferenceType->location() != DataLocation::Storage, ""); + solAssert(toReferenceType->category() == fromReferenceType->category(), ""); if (_toType.category() == Type::Category::Array) - solUnimplementedAssert(false, ""); + { + solAssert(_offset.value_or(0) == 0, ""); + + Whiskers templ(R"( + function (slot, ) { + (slot, ) + } + )"); + templ("functionName", functionName); + templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack())); + templ("copyArrayToStorage", copyArrayToStorage( + dynamic_cast(_fromType), + dynamic_cast(_toType) + )); + + return templ.render(); + } else if (_toType.category() == Type::Category::Struct) { - solAssert(_fromType.category() == Type::Category::Struct, ""); auto const& fromStructType = dynamic_cast(_fromType); auto const& toStructType = dynamic_cast(_toType); solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), ""); - solAssert(fromStructType.location() != DataLocation::Storage, ""); - solUnimplementedAssert(_offset.has_value() && _offset.value() == 0, ""); + solAssert(_offset.value_or(0) == 0, ""); Whiskers templ(R"( function (slot, value) { @@ -1785,6 +1900,7 @@ string YulUtilFunctions::updateStorageValueFunction( templ("functionName", functionName); MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr); + MemberList::MemberMap toStructMembers = toStructType.nativeMembers(nullptr); vector> memberParams(structMembers.size()); for (size_t i = 0; i < structMembers.size(); ++i) @@ -1792,31 +1908,65 @@ string YulUtilFunctions::updateStorageValueFunction( solAssert(structMembers[i].type->memoryHeadSize() == 32, ""); bool fromCalldata = fromStructType.location() == DataLocation::CallData; auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name); - memberParams[i]["updateMemberCall"] = Whiskers(R"( - let := (add(value, )) - (add(slot, ), , ) - )") - ("memberValues", suffixedVariableNameList( + + Whiskers t(R"( + let memberSlot := add(slot, ) + + + + let := (value, add(value, )) + + let := add(value, ) + + + + let := () + (memberSlot, , ) + + (memberSlot, ) + + + let memberMemoryOffset := add(value, ) + let := (memberMemoryOffset) + (memberSlot, , ) + + )"); + t("fromCalldata", fromCalldata); + if (fromCalldata) + { + t("memberCalldataOffset", suffixedVariableNameList( + "memberCalldataOffset_", + 0, + structMembers[i].type->stackItems().size() + )); + t("dynamicallyEncodedMember", structMembers[i].type->isDynamicallyEncoded()); + if (structMembers[i].type->isDynamicallySized()) + t("accessCalldataTail", accessCalldataTailFunction(*structMembers[i].type)); + } + t("isValueType", structMembers[i].type->isValueType()); + t("memberValues", suffixedVariableNameList( "memberValue_", 0, structMembers[i].type->stackItems().size() - )) - ("hasOffset", structMembers[i].type->isValueType()) - ( + )); + t("hasOffset", structMembers[i].type->isValueType()); + t( "updateMember", structMembers[i].type->isValueType() ? - updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type) : - updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type, offset) - ) - ("memberStorageSlotDiff", slotDiff.str()) - ("memberStorageOffset", to_string(offset)) - ("memberOffset", + updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type) : + updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type, offset) + ); + t("memberStorageSlotDiff", slotDiff.str()); + t("memberStorageOffset", to_string(offset)); + t( + "memberOffset", fromCalldata ? to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) : fromStructType.memoryOffsetOfMember(structMembers[i].name).str() - ) - ("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata)) - .render(); + ); + if (!fromCalldata || structMembers[i].type->isValueType()) + t("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata)); + memberParams[i]["updateMemberCall"] = t.render(); } templ("member", memberParams); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index ae0417329..8dc46da73 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -173,6 +173,10 @@ public: /// signature: (slot) -> std::string clearStorageArrayFunction(ArrayType const& _type); + /// @returns the name of a function that will copy array from calldata or memory to storage + /// signature (to_slot, from_ptr) -> + std::string copyArrayToStorage(ArrayType const& _fromType, ArrayType const& _toType); + /// 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. diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index a4a74f234..c9bcc1f5e 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -386,7 +386,11 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment) writeToLValue(*m_currentLValue, value); - if (m_currentLValue->type.category() != Type::Category::Struct && *_assignment.annotation().type != *TypeProvider::emptyTuple()) + if ( + m_currentLValue->type.category() != Type::Category::Struct && + m_currentLValue->type.category() != Type::Category::Array && + *_assignment.annotation().type != *TypeProvider::emptyTuple() + ) define(_assignment, value); m_currentLValue.reset();