From d9fb17a85ef0574e7da0de19068608e6674cae7b Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 12 Nov 2020 14:46:10 +0100 Subject: [PATCH] Copy value array from storage to storage. --- libsolidity/codegen/YulUtilFunctions.cpp | 113 ++++++++++++++++-- libsolidity/codegen/YulUtilFunctions.h | 17 ++- .../array_copy_storage_storage_dyn_dyn.sol | 2 + ...ay_copy_storage_storage_static_dynamic.sol | 2 + .../copying/copy_function_storage_array.sol | 2 + 5 files changed, 125 insertions(+), 11 deletions(-) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 075f9ca32..cbdca8524 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -449,6 +449,36 @@ string YulUtilFunctions::maskBytesFunctionDynamic() }); } +string YulUtilFunctions::maskLowerOrderBytesFunction(size_t _bytes) +{ + string functionName = "mask_lower_order_bytes_" + to_string(_bytes); + solAssert(_bytes <= 32, ""); + return m_functionCollector.createFunction(functionName, [&]() { + return Whiskers(R"( + function (data) -> result { + result := and(data, ) + })") + ("functionName", functionName) + ("mask", formatNumber((~u256(0)) >> (256 - 8 * _bytes))) + .render(); + }); +} + +string YulUtilFunctions::maskLowerOrderBytesFunctionDynamic() +{ + string functionName = "mask_lower_order_bytes_dynamic"; + return m_functionCollector.createFunction(functionName, [&]() { + return Whiskers(R"( + function (data, bytes) -> result { + let mask := not((mul(8, bytes), not(0))) + result := and(data, mask) + })") + ("functionName", functionName) + ("shl", shiftLeftFunctionDynamic()) + .render(); + }); +} + string YulUtilFunctions::roundUpFunction() { string functionName = "round_up_to_mul_of_32"; @@ -1470,23 +1500,20 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType, ); if (_fromType.isByteArray()) return copyByteArrayToStorageFunction(_fromType, _toType); - solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), ""); + if (_fromType.dataStoredIn(DataLocation::Storage) && _toType.baseType()->isValueType()) + return copyValueArrayStorageToStorageFunction(_fromType, _toType); string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier(); return m_functionCollector.createFunction(functionName, [&](){ Whiskers templ(R"( function (slot, value, len) { + if eq(slot, value) { leave } let length := (value, len) (slot, length) - let srcPtr := - - add(value, 0x20) - - value - + let srcPtr := (value) let elementSlot := (slot) let elementOffset := 0 @@ -1509,6 +1536,10 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType, let := (srcPtr) + + let := srcPtr + + (elementSlot, elementOffset, ) srcPtr := add(srcPtr, ) @@ -1526,12 +1557,17 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType, } } )"); + if (_fromType.dataStoredIn(DataLocation::Storage)) + solAssert(!_fromType.isValueType(), ""); templ("functionName", functionName); bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData); templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata); - templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory)); + templ("fromStorage", _fromType.dataStoredIn(DataLocation::Storage)); + bool fromMemory = _fromType.dataStoredIn(DataLocation::Memory); + templ("fromMemory", fromMemory); templ("fromCalldata", fromCalldata); templ("isToDynamic", _toType.isDynamicallySized()); + templ("srcDataLocation", arrayDataAreaFunction(_fromType)); templ("isFromMemoryDynamic", _fromType.isDynamicallySized() && _fromType.dataStoredIn(DataLocation::Memory)); if (fromCalldata) { @@ -1545,7 +1581,7 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType, templ("arrayLength",arrayLengthFunction(_fromType)); templ("isValueType", _fromType.baseType()->isValueType()); templ("dstDataLocation", arrayDataAreaFunction(_toType)); - if (!fromCalldata || _fromType.baseType()->isValueType()) + if (fromMemory || (fromCalldata && _fromType.baseType()->isValueType())) templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata)); templ("elementValues", suffixedVariableNameList( "elementValue_", @@ -1643,6 +1679,65 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy }); } + +string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType) +{ + solAssert( + *_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast(_toType), + "" + ); + solAssert(!_fromType.isByteArray(), ""); + solAssert(_fromType.dataStoredIn(DataLocation::Storage) && _toType.baseType()->isValueType(), ""); + solAssert(_toType.dataStoredIn(DataLocation::Storage), ""); + + string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier(); + return m_functionCollector.createFunction(functionName, [&](){ + Whiskers templ(R"( + function (dst, src) { + if eq(dst, src) { leave } + let length := (src) + // Make sure array length is sane + if gt(length, 0xffffffffffffffff) { () } + + (dst, length) + + + let srcPtr := (src) + + let dstPtr := (dst) + + let fullSlots := div(length, ) + let i := 0 + for { } lt(i, fullSlots) { i := add(i, 1) } { + sstore(add(dstPtr, i), (sload(add(srcPtr, i)))) + } + let spill := sub(length, mul(i, )) + if gt(spill, 0) { + sstore(add(dstPtr, i), (sload(add(srcPtr, i)), mul(spill, ))) + } + } + )"); + if (_fromType.dataStoredIn(DataLocation::Storage)) + solAssert(!_fromType.isValueType(), ""); + templ("functionName", functionName); + templ("isToDynamic", _toType.isDynamicallySized()); + if (_toType.isDynamicallySized()) + templ("resizeArray", resizeDynamicArrayFunction(_toType)); + templ("arrayLength",arrayLengthFunction(_fromType)); + templ("panic", panicFunction()); + templ("srcDataLocation", arrayDataAreaFunction(_fromType)); + templ("dstDataLocation", arrayDataAreaFunction(_toType)); + unsigned itemsPerSlot = 32 / _toType.storageStride(); + templ("itemsPerSlot", to_string(itemsPerSlot)); + templ("bytesPerItem", to_string(_toType.storageStride())); + templ("maskFull", maskLowerOrderBytesFunction(itemsPerSlot * _toType.storageStride())); + templ("maskBytes", maskLowerOrderBytesFunctionDynamic()); + + return templ.render(); + }); +} + + string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) { string functionName = "array_convert_length_to_size_" + _type.identifier(); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 161cdf64b..1a0cd9a83 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -104,6 +104,15 @@ public: /// signature: (value, bytes) -> result std::string maskBytesFunctionDynamic(); + /// Zeroes out all bytes above the first ``_bytes`` lower order bytes. + /// signature: (value) -> result + std::string maskLowerOrderBytesFunction(size_t _bytes); + + /// Zeroes out all bytes above the first ``bytes`` lower order bytes. + /// @note ``bytes`` has to be small enough not to overflow ``8 * bytes``. + /// signature: (value, bytes) -> result + std::string maskLowerOrderBytesFunctionDynamic(); + /// @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 @@ -186,14 +195,18 @@ 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 + /// @returns the name of a function that will copy an array to storage /// signature (to_slot, from_ptr) -> std::string copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType); - /// @returns the name of a function that will copy a byte array from calldata or memory to storage + /// @returns the name of a function that will copy a byte array to storage /// signature (to_slot, from_ptr) -> std::string copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType); + /// @returns the name of a function that will copy an array of value types from storage to storage. + /// signature (to_slot, from_slot) -> + std::string copyValueArrayStorageToStorageFunction(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/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dyn_dyn.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dyn_dyn.sol index c183d8a82..f2235bf0d 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dyn_dyn.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dyn_dyn.sol @@ -12,6 +12,8 @@ contract c { len = data2.length; if (index < len) val = data2[index]; } } +// ==== +// compileViaYul: also // ---- // setData1(uint256,uint256,uint256): 10, 5, 4 -> // copyStorageStorage() -> diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_dynamic.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_dynamic.sol index 4432776fb..9b3ba326e 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_dynamic.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_dynamic.sol @@ -10,5 +10,7 @@ contract c { } } +// ==== +// compileViaYul: also // ---- // test() -> 9, 4 diff --git a/test/libsolidity/semanticTests/array/copying/copy_function_storage_array.sol b/test/libsolidity/semanticTests/array/copying/copy_function_storage_array.sol index eba61ad1a..7b3022361 100644 --- a/test/libsolidity/semanticTests/array/copying/copy_function_storage_array.sol +++ b/test/libsolidity/semanticTests/array/copying/copy_function_storage_array.sol @@ -14,5 +14,7 @@ contract C { } } +// ==== +// compileViaYul: also // ---- // test() -> 7