diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 602d1723f..075f9ca32 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -1571,12 +1571,13 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy ); solAssert(_fromType.isByteArray(), ""); solAssert(_toType.isByteArray(), ""); - solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), ""); string functionName = "copy_byte_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier(); return m_functionCollector.createFunction(functionName, [&](){ Whiskers templ(R"( function (slot, src, len) { + if eq(slot, src) { leave } + let newLen := (src, len) // Make sure array length is sane if gt(newLen, 0xffffffffffffffff) { () } @@ -1605,11 +1606,11 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy let dstPtr := dstDataArea let i := 0 for { } lt(i, loopEnd) { i := add(i, 32) } { - sstore(dstPtr, (add(src, i))) + sstore(dstPtr, (add(src, i))) dstPtr := add(dstPtr, 1) } if lt(loopEnd, newLen) { - let lastValue := (add(src, i)) + let lastValue := (add(src, i)) sstore(dstPtr, (lastValue, and(newLen, 0x1f))) } sstore(slot, add(mul(newLen, 2), 1)) @@ -1617,13 +1618,15 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy default { let value := 0 if newLen { - value := (src) + value := (src) } sstore(slot, (value, newLen)) } } )"); templ("functionName", functionName); + bool fromStorage = _fromType.dataStoredIn(DataLocation::Storage); + templ("fromStorage", fromStorage); bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData); templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory)); templ("fromCalldata", fromCalldata); @@ -1632,7 +1635,7 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy templ("byteArrayLength", extractByteArrayLengthFunction()); templ("dstDataLocation", arrayDataAreaFunction(_toType)); templ("clearStorageRange", clearStorageRangeFunction(*_toType.baseType())); - templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*TypeProvider::uint256(), fromCalldata)); + templ("read", fromStorage ? "sload" : fromCalldata ? "calldataload" : "mload"); templ("maskBytes", maskBytesFunctionDynamic()); templ("byteArrayCombineShort", shortByteArrayEncodeUsedAreaSetLengthFunction()); @@ -2200,132 +2203,130 @@ string YulUtilFunctions::updateStorageValueFunction( ("prepare", prepareStoreFunction(_toType)) .render(); } + + auto const* toReferenceType = dynamic_cast(&_toType); + auto const* fromReferenceType = dynamic_cast(&_fromType); + solAssert(fromReferenceType && toReferenceType, ""); + solAssert(*toReferenceType->copyForLocation( + fromReferenceType->location(), + fromReferenceType->isPointer() + ).get() == *fromReferenceType, ""); + solAssert(toReferenceType->category() == fromReferenceType->category(), ""); + + if (_toType.category() == Type::Category::Array) + { + solAssert(_offset.value_or(0) == 0, ""); + + Whiskers templ(R"( + function (slot, ) { + (slot, ) + } + )"); + templ("functionName", functionName); + templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack())); + templ("copyArrayToStorage", copyArrayToStorageFunction( + dynamic_cast(_fromType), + dynamic_cast(_toType) + )); + + return templ.render(); + } else { - auto const* toReferenceType = dynamic_cast(&_toType); - auto const* fromReferenceType = dynamic_cast(&_fromType); - solAssert(fromReferenceType && toReferenceType, ""); - solAssert(*toReferenceType->copyForLocation( - fromReferenceType->location(), - fromReferenceType->isPointer() - ).get() == *fromReferenceType, ""); + solAssert(_toType.category() == Type::Category::Struct, ""); + solUnimplementedAssert( fromReferenceType->location() != DataLocation::Storage, "Copying from storage to storage is not yet implemented." ); - solAssert(toReferenceType->category() == fromReferenceType->category(), ""); + auto const& fromStructType = dynamic_cast(_fromType); + auto const& toStructType = dynamic_cast(_toType); + solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), ""); + solAssert(_offset.value_or(0) == 0, ""); - if (_toType.category() == Type::Category::Array) - { - solAssert(_offset.value_or(0) == 0, ""); - - Whiskers templ(R"( - function (slot, ) { - (slot, ) - } - )"); - templ("functionName", functionName); - templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack())); - templ("copyArrayToStorage", copyArrayToStorageFunction( - dynamic_cast(_fromType), - dynamic_cast(_toType) - )); - - return templ.render(); - } - else if (_toType.category() == Type::Category::Struct) - { - auto const& fromStructType = dynamic_cast(_fromType); - auto const& toStructType = dynamic_cast(_toType); - solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), ""); - solAssert(_offset.value_or(0) == 0, ""); - - Whiskers templ(R"( - function (slot, value) { - <#member> - { - - } - - } - )"); - 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) - { - solAssert(structMembers[i].type->memoryHeadSize() == 32, ""); - bool fromCalldata = fromStructType.location() == DataLocation::CallData; - auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name); - - 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) + Whiskers templ(R"( + function (slot, value) { + <#member> { - t("memberCalldataOffset", suffixedVariableNameList( - "memberCalldataOffset_", - 0, - structMembers[i].type->stackItems().size() - )); - t("dynamicallyEncodedMember", structMembers[i].type->isDynamicallyEncoded()); - if (structMembers[i].type->isDynamicallyEncoded()) - t("accessCalldataTail", accessCalldataTailFunction(*structMembers[i].type)); + } - t("isValueType", structMembers[i].type->isValueType()); - t("memberValues", suffixedVariableNameList( - "memberValue_", + + } + )"); + 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) + { + solAssert(structMembers[i].type->memoryHeadSize() == 32, ""); + bool fromCalldata = fromStructType.location() == DataLocation::CallData; + auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name); + + 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("hasOffset", structMembers[i].type->isValueType()); - t( - "updateMember", - structMembers[i].type->isValueType() ? - 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() - ); - if (!fromCalldata || structMembers[i].type->isValueType()) - t("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata)); - memberParams[i]["updateMemberCall"] = t.render(); + t("dynamicallyEncodedMember", structMembers[i].type->isDynamicallyEncoded()); + if (structMembers[i].type->isDynamicallyEncoded()) + t("accessCalldataTail", accessCalldataTailFunction(*structMembers[i].type)); } - templ("member", memberParams); - - return templ.render(); + t("isValueType", structMembers[i].type->isValueType()); + t("memberValues", suffixedVariableNameList( + "memberValue_", + 0, + structMembers[i].type->stackItems().size() + )); + t("hasOffset", structMembers[i].type->isValueType()); + t( + "updateMember", + structMembers[i].type->isValueType() ? + 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() + ); + if (!fromCalldata || structMembers[i].type->isValueType()) + t("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata)); + memberParams[i]["updateMemberCall"] = t.render(); } - else - solAssert(false, "Invalid non-value type for assignment."); + templ("member", memberParams); + + return templ.render(); } }); } diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index aa70e6d47..4e6051fc2 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -128,7 +128,7 @@ function test_solc_behaviour() sed -i.bak -e '/^Warning (3805): This is a pre-release compiler version, please do not use it in production./d' "$stderr_path" sed -i.bak -e 's/\(^[ ]*auxdata: \)0x[0-9a-f]*$/\1/' "$stdout_path" sed -i.bak -e 's/ Consider adding "pragma .*$//' "$stderr_path" - sed -i.bak -e 's/\(Unimplemented feature error: .* in \).*$/\1/' "$stderr_path" + sed -i.bak -e 's/\(Unimplemented feature error.* in \).*$/\1/' "$stderr_path" sed -i.bak -e 's/"version": "[^"]*"/"version": ""/' "$stdout_path" # Remove bytecode (but not linker references). Since non-JSON output is unstructured, diff --git a/test/cmdlineTests/yul_unimplemented/err b/test/cmdlineTests/yul_unimplemented/err index 908cf4411..dd3572497 100644 --- a/test/cmdlineTests/yul_unimplemented/err +++ b/test/cmdlineTests/yul_unimplemented/err @@ -1,5 +1,5 @@ -Error (1834): Unimplemented feature error: Copying from storage to storage is not yet implemented. in - --> yul_unimplemented/input.sol:7:9: +Error (1834): Unimplemented feature error in + --> yul_unimplemented/input.sol:8:9: | -7 | a = b; +8 | x.f(); | ^^^^^ diff --git a/test/cmdlineTests/yul_unimplemented/input.sol b/test/cmdlineTests/yul_unimplemented/input.sol index 1ed933ade..811f3e4a0 100644 --- a/test/cmdlineTests/yul_unimplemented/input.sol +++ b/test/cmdlineTests/yul_unimplemented/input.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.0; +library L { function f(uint) public {} } contract test { - bytes a; - bytes b; + using L for uint; function f() public { - a = b; + uint x; + x.f(); } } \ No newline at end of file diff --git a/test/libsolidity/semanticTests/array/copying/bytes_inside_mappings.sol b/test/libsolidity/semanticTests/array/copying/bytes_inside_mappings.sol index 21b5e9c5d..2decb1562 100644 --- a/test/libsolidity/semanticTests/array/copying/bytes_inside_mappings.sol +++ b/test/libsolidity/semanticTests/array/copying/bytes_inside_mappings.sol @@ -3,6 +3,8 @@ contract c { function copy(uint from, uint to) public returns (bool) { data[to] = data[from]; return true; } mapping(uint => bytes) data; } +// ==== +// compileViaYul: also // ---- // set(uint256): 1, 2 -> true // set(uint256): 2, 2, 3, 4, 5 -> true diff --git a/test/libsolidity/semanticTests/array/copying/copy_removes_bytes_data.sol b/test/libsolidity/semanticTests/array/copying/copy_removes_bytes_data.sol index bb97ae2c1..ace34e872 100644 --- a/test/libsolidity/semanticTests/array/copying/copy_removes_bytes_data.sol +++ b/test/libsolidity/semanticTests/array/copying/copy_removes_bytes_data.sol @@ -5,6 +5,8 @@ contract c { bytes data1; bytes data2; } +// ==== +// compileViaYul: also // ---- // set(): 1, 2, 3, 4, 5 -> true // storage: nonempty diff --git a/test/libsolidity/semanticTests/array/string_bytes_conversion.sol b/test/libsolidity/semanticTests/array/string_bytes_conversion.sol index 9578fc4f7..5f6e73422 100644 --- a/test/libsolidity/semanticTests/array/string_bytes_conversion.sol +++ b/test/libsolidity/semanticTests/array/string_bytes_conversion.sol @@ -12,6 +12,8 @@ contract Test { return bytes(s).length; } } +// ==== +// compileViaYul: also // ---- // f(string,uint256): 0x40, 0x02, 0x06, "abcdef" -> "c" // l() -> 0x06