Skip to content

Commit bed4d98

Browse files
authored
Merge pull request #13118 from ethereum/improvedCopyValueArrayToStorageFunction
Improved copy value-type-array to storage function
2 parents dc0a9bd + 384bb8b commit bed4d98

30 files changed

+205
-107
lines changed

Changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Language Features:
55

66
Compiler Features:
77
* TypeChecker: Support using library constants in initializers of other constants.
8+
* Yul IR Code Generation: Improved copy routines for arrays with packed storage layout.
9+
810

911
Bugfixes:
1012
* Commandline Interface: Disallow the following options outside of the compiler mode: ``--via-ir``,``--metadata-literal``, ``--metadata-hash``, ``--model-checker-show-unproved``, ``--model-checker-div-mod-no-slacks``, ``--model-checker-engine``, ``--model-checker-invariants``, ``--model-checker-solvers``, ``--model-checker-timeout``, ``--model-checker-contracts``, ``--model-checker-targets``.
@@ -30,6 +32,7 @@ Compiler Features:
3032
Bugfixes:
3133
* ABI Encoder: When encoding an empty string coming from storage do not add a superfluous empty slot for data.
3234
* Common Subexpression Eliminator: Process assembly items in chunks with maximum size of 2000. It helps to avoid extremely time-consuming searches during code optimization.
35+
* Yul IR Code Generation: More robust cleanup in corner cases during memory to storage copies.
3336
* Yul Optimizer: Do not remove ``returndatacopy`` in cases in which it might perform out-of-bounds reads that unconditionally revert as out-of-gas. Previously, any ``returndatacopy`` that wrote to memory that was never read from was removed without accounting for the out-of-bounds condition.
3437

3538

libsolidity/codegen/YulUtilFunctions.cpp

Lines changed: 87 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,8 +1797,11 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
17971797

17981798
if (_fromType.isByteArrayOrString())
17991799
return copyByteArrayToStorageFunction(_fromType, _toType);
1800-
if (_fromType.dataStoredIn(DataLocation::Storage) && _toType.baseType()->isValueType())
1801-
return copyValueArrayStorageToStorageFunction(_fromType, _toType);
1800+
if (_toType.baseType()->isValueType())
1801+
return copyValueArrayToStorageFunction(_fromType, _toType);
1802+
1803+
solAssert(_toType.storageStride() == 32);
1804+
solAssert(!_fromType.baseType()->isValueType());
18021805

18031806
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
18041807
return m_functionCollector.createFunction(functionName, [&](){
@@ -1812,43 +1815,30 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
18121815
let srcPtr := <srcDataLocation>(value)
18131816
18141817
let elementSlot := <dstDataLocation>(slot)
1815-
let elementOffset := 0
18161818
18171819
for { let i := 0 } lt(i, length) {i := add(i, 1)} {
18181820
<?fromCalldata>
1819-
let <elementValues> :=
1821+
let <stackItems> :=
18201822
<?dynamicallyEncodedBase>
18211823
<accessCalldataTail>(value, srcPtr)
18221824
<!dynamicallyEncodedBase>
18231825
srcPtr
18241826
</dynamicallyEncodedBase>
1825-
1826-
<?isValueType>
1827-
<elementValues> := <readFromCalldataOrMemory>(<elementValues>)
1828-
</isValueType>
18291827
</fromCalldata>
18301828
18311829
<?fromMemory>
1832-
let <elementValues> := <readFromCalldataOrMemory>(srcPtr)
1830+
let <stackItems> := <readFromMemoryOrCalldata>(srcPtr)
18331831
</fromMemory>
18341832
18351833
<?fromStorage>
1836-
let <elementValues> := srcPtr
1834+
let <stackItems> := srcPtr
18371835
</fromStorage>
18381836
1839-
<updateStorageValue>(elementSlot, elementOffset, <elementValues>)
1837+
<updateStorageValue>(elementSlot, <stackItems>)
18401838
18411839
srcPtr := add(srcPtr, <srcStride>)
18421840
1843-
<?multipleItemsPerSlot>
1844-
elementOffset := add(elementOffset, <storageStride>)
1845-
if gt(elementOffset, sub(32, <storageStride>)) {
1846-
elementOffset := 0
1847-
elementSlot := add(elementSlot, 1)
1848-
}
1849-
<!multipleItemsPerSlot>
1850-
elementSlot := add(elementSlot, <storageSize>)
1851-
</multipleItemsPerSlot>
1841+
elementSlot := add(elementSlot, <storageSize>)
18521842
}
18531843
}
18541844
)");
@@ -1870,25 +1860,22 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
18701860
}
18711861
templ("resizeArray", resizeArrayFunction(_toType));
18721862
templ("arrayLength",arrayLengthFunction(_fromType));
1873-
templ("isValueType", _fromType.baseType()->isValueType());
18741863
templ("dstDataLocation", arrayDataAreaFunction(_toType));
18751864
if (fromMemory || (fromCalldata && _fromType.baseType()->isValueType()))
1876-
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata));
1877-
templ("elementValues", suffixedVariableNameList(
1878-
"elementValue_",
1865+
templ("readFromMemoryOrCalldata", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata));
1866+
templ("stackItems", suffixedVariableNameList(
1867+
"stackItem_",
18791868
0,
18801869
_fromType.baseType()->stackItems().size()
18811870
));
1882-
templ("updateStorageValue", updateStorageValueFunction(*_fromType.baseType(), *_toType.baseType()));
1871+
templ("updateStorageValue", updateStorageValueFunction(*_fromType.baseType(), *_toType.baseType(), 0));
18831872
templ("srcStride",
18841873
fromCalldata ?
18851874
to_string(_fromType.calldataStride()) :
18861875
fromMemory ?
18871876
to_string(_fromType.memoryStride()) :
18881877
formatNumber(_fromType.baseType()->storageSize())
18891878
);
1890-
templ("multipleItemsPerSlot", _toType.storageStride() <= 16);
1891-
templ("storageStride", to_string(_toType.storageStride()));
18921879
templ("storageSize", _toType.baseType()->storageSize().str());
18931880

18941881
return templ.render();
@@ -1973,16 +1960,14 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
19731960
});
19741961
}
19751962

1976-
1977-
string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
1963+
string YulUtilFunctions::copyValueArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
19781964
{
19791965
solAssert(_fromType.baseType()->isValueType(), "");
19801966
solAssert(_toType.baseType()->isValueType(), "");
19811967
solAssert(_fromType.baseType()->isImplicitlyConvertibleTo(*_toType.baseType()), "");
19821968

19831969
solAssert(!_fromType.isByteArrayOrString(), "");
19841970
solAssert(!_toType.isByteArrayOrString(), "");
1985-
solAssert(_fromType.dataStoredIn(DataLocation::Storage), "");
19861971
solAssert(_toType.dataStoredIn(DataLocation::Storage), "");
19871972

19881973
solAssert(_fromType.storageStride() <= _toType.storageStride(), "");
@@ -1991,42 +1976,51 @@ string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const&
19911976
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
19921977
return m_functionCollector.createFunction(functionName, [&](){
19931978
Whiskers templ(R"(
1994-
function <functionName>(dst, src) {
1979+
function <functionName>(dst, src<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) {
1980+
<?isFromStorage>
19951981
if eq(dst, src) { leave }
1996-
let length := <arrayLength>(src)
1982+
</isFromStorage>
1983+
let length := <arrayLength>(src<?isFromDynamicCalldata>, len</isFromDynamicCalldata>)
19971984
// Make sure array length is sane
19981985
if gt(length, 0xffffffffffffffff) { <panic>() }
19991986
<resizeArray>(dst, length)
20001987
2001-
let srcSlot := <srcDataLocation>(src)
1988+
let srcPtr := <srcDataLocation>(src)
20021989
let dstSlot := <dstDataLocation>(dst)
20031990
20041991
let fullSlots := div(length, <itemsPerSlot>)
20051992
2006-
let srcSlotValue := sload(srcSlot)
1993+
<?isFromStorage>
1994+
let srcSlotValue := sload(srcPtr)
20071995
let srcItemIndexInSlot := 0
1996+
</isFromStorage>
1997+
20081998
for { let i := 0 } lt(i, fullSlots) { i := add(i, 1) } {
20091999
let dstSlotValue := 0
2010-
<?sameType>
2000+
<?sameTypeFromStorage>
20112001
dstSlotValue := <maskFull>(srcSlotValue)
2012-
<updateSrcSlotValue>
2013-
<!sameType>
2002+
<updateSrcPtr>
2003+
<!sameTypeFromStorage>
20142004
<?multipleItemsPerSlotDst>for { let j := 0 } lt(j, <itemsPerSlot>) { j := add(j, 1) } </multipleItemsPerSlotDst>
20152005
{
2016-
let itemValue := <convert>(
2006+
<?isFromStorage>
2007+
let <stackItems> := <convert>(
20172008
<extractFromSlot>(srcSlotValue, mul(<srcStride>, srcItemIndexInSlot))
20182009
)
2019-
itemValue := <prepareStore>(itemValue)
2010+
<!isFromStorage>
2011+
let <stackItems> := <readFromMemoryOrCalldata>(srcPtr)
2012+
</isFromStorage>
2013+
let itemValue := <prepareStore>(<stackItems>)
20202014
dstSlotValue :=
20212015
<?multipleItemsPerSlotDst>
20222016
<updateByteSlice>(dstSlotValue, mul(<dstStride>, j), itemValue)
20232017
<!multipleItemsPerSlotDst>
20242018
itemValue
20252019
</multipleItemsPerSlotDst>
20262020
2027-
<updateSrcSlotValue>
2021+
<updateSrcPtr>
20282022
}
2029-
</sameType>
2023+
</sameTypeFromStorage>
20302024
20312025
sstore(add(dstSlot, i), dstSlotValue)
20322026
}
@@ -2035,47 +2029,62 @@ string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const&
20352029
let spill := sub(length, mul(fullSlots, <itemsPerSlot>))
20362030
if gt(spill, 0) {
20372031
let dstSlotValue := 0
2038-
<?sameType>
2032+
<?sameTypeFromStorage>
20392033
dstSlotValue := <maskBytes>(srcSlotValue, mul(spill, <srcStride>))
2040-
<updateSrcSlotValue>
2041-
<!sameType>
2034+
<updateSrcPtr>
2035+
<!sameTypeFromStorage>
20422036
for { let j := 0 } lt(j, spill) { j := add(j, 1) } {
2043-
let itemValue := <convert>(
2037+
<?isFromStorage>
2038+
let <stackItems> := <convert>(
20442039
<extractFromSlot>(srcSlotValue, mul(<srcStride>, srcItemIndexInSlot))
20452040
)
2046-
itemValue := <prepareStore>(itemValue)
2041+
<!isFromStorage>
2042+
let <stackItems> := <readFromMemoryOrCalldata>(srcPtr)
2043+
</isFromStorage>
2044+
let itemValue := <prepareStore>(<stackItems>)
20472045
dstSlotValue := <updateByteSlice>(dstSlotValue, mul(<dstStride>, j), itemValue)
20482046
2049-
<updateSrcSlotValue>
2047+
<updateSrcPtr>
20502048
}
2051-
</sameType>
2049+
</sameTypeFromStorage>
20522050
sstore(add(dstSlot, fullSlots), dstSlotValue)
20532051
}
20542052
</multipleItemsPerSlotDst>
20552053
}
20562054
)");
20572055
if (_fromType.dataStoredIn(DataLocation::Storage))
20582056
solAssert(!_fromType.isValueType(), "");
2057+
2058+
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
2059+
bool fromStorage = _fromType.dataStoredIn(DataLocation::Storage);
20592060
templ("functionName", functionName);
20602061
templ("resizeArray", resizeArrayFunction(_toType));
20612062
templ("arrayLength", arrayLengthFunction(_fromType));
20622063
templ("panic", panicFunction(PanicCode::ResourceError));
2064+
templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata);
2065+
templ("isFromStorage", fromStorage);
2066+
templ("readFromMemoryOrCalldata", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata));
20632067
templ("srcDataLocation", arrayDataAreaFunction(_fromType));
20642068
templ("dstDataLocation", arrayDataAreaFunction(_toType));
20652069
templ("srcStride", to_string(_fromType.storageStride()));
2070+
templ("stackItems", suffixedVariableNameList(
2071+
"stackItem_",
2072+
0,
2073+
_fromType.baseType()->stackItems().size()
2074+
));
20662075
unsigned itemsPerSlot = 32 / _toType.storageStride();
20672076
templ("itemsPerSlot", to_string(itemsPerSlot));
20682077
templ("multipleItemsPerSlotDst", itemsPerSlot > 1);
2069-
bool sameType = *_fromType.baseType() == *_toType.baseType();
2078+
bool sameTypeFromStorage = fromStorage && (*_fromType.baseType() == *_toType.baseType());
20702079
if (auto functionType = dynamic_cast<FunctionType const*>(_fromType.baseType()))
20712080
{
20722081
solAssert(functionType->equalExcludingStateMutability(
20732082
dynamic_cast<FunctionType const&>(*_toType.baseType())
20742083
));
2075-
sameType = true;
2084+
sameTypeFromStorage = fromStorage;
20762085
}
2077-
templ("sameType", sameType);
2078-
if (sameType)
2086+
templ("sameTypeFromStorage", sameTypeFromStorage);
2087+
if (sameTypeFromStorage)
20792088
{
20802089
templ("maskFull", maskLowerOrderBytesFunction(itemsPerSlot * _toType.storageStride()));
20812090
templ("maskBytes", maskLowerOrderBytesFunctionDynamic());
@@ -2088,24 +2097,32 @@ string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const&
20882097
templ("convert", conversionFunction(*_fromType.baseType(), *_toType.baseType()));
20892098
templ("prepareStore", prepareStoreFunction(*_toType.baseType()));
20902099
}
2091-
templ("updateSrcSlotValue", Whiskers(R"(
2092-
<?srcReadMultiPerSlot>
2093-
srcItemIndexInSlot := add(srcItemIndexInSlot, 1)
2094-
if eq(srcItemIndexInSlot, <srcItemsPerSlot>) {
2095-
// here we are done with this slot, we need to read next one
2096-
srcSlot := add(srcSlot, 1)
2097-
srcSlotValue := sload(srcSlot)
2098-
srcItemIndexInSlot := 0
2099-
}
2100-
<!srcReadMultiPerSlot>
2101-
srcSlot := add(srcSlot, 1)
2102-
srcSlotValue := sload(srcSlot)
2103-
</srcReadMultiPerSlot>
2104-
)")
2105-
("srcReadMultiPerSlot", !sameType && _fromType.storageStride() <= 16)
2106-
("srcItemsPerSlot", to_string(32 / _fromType.storageStride()))
2107-
.render()
2108-
);
2100+
if (fromStorage)
2101+
templ("updateSrcPtr", Whiskers(R"(
2102+
<?srcReadMultiPerSlot>
2103+
srcItemIndexInSlot := add(srcItemIndexInSlot, 1)
2104+
if eq(srcItemIndexInSlot, <srcItemsPerSlot>) {
2105+
// here we are done with this slot, we need to read next one
2106+
srcPtr := add(srcPtr, 1)
2107+
srcSlotValue := sload(srcPtr)
2108+
srcItemIndexInSlot := 0
2109+
}
2110+
<!srcReadMultiPerSlot>
2111+
srcPtr := add(srcPtr, 1)
2112+
srcSlotValue := sload(srcPtr)
2113+
</srcReadMultiPerSlot>
2114+
)")
2115+
("srcReadMultiPerSlot", !sameTypeFromStorage && _fromType.storageStride() <= 16)
2116+
("srcItemsPerSlot", to_string(32 / _fromType.storageStride()))
2117+
.render()
2118+
);
2119+
else
2120+
templ("updateSrcPtr", Whiskers(R"(
2121+
srcPtr := add(srcPtr, <srcStride>)
2122+
)")
2123+
("srcStride", fromCalldata ? to_string(_fromType.calldataStride()) : to_string(_fromType.memoryStride()))
2124+
.render()
2125+
);
21092126

21102127
return templ.render();
21112128
});

libsolidity/codegen/YulUtilFunctions.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,9 @@ class YulUtilFunctions
258258
/// signature (to_slot, from_ptr) ->
259259
std::string copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
260260

261-
/// @returns the name of a function that will copy an array of value types from storage to storage.
262-
/// signature (to_slot, from_slot) ->
263-
std::string copyValueArrayStorageToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
261+
/// @returns the name of a function that will copy an array of value types to storage.
262+
/// signature (to_slot, from_ptr[, from_length]) ->
263+
std::string copyValueArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
264264

265265
/// Returns the name of a function that will convert a given length to the
266266
/// size in memory (number of storage slots or calldata/memory bytes) it

test/libsolidity/semanticTests/abiEncoderV1/struct/struct_storage_ptr.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ contract C {
2424
// ----
2525
// library: L
2626
// f() -> 8, 7, 1, 2, 7, 12
27-
// gas irOptimized: 166606
27+
// gas irOptimized: 166525
2828
// gas legacy: 169347
2929
// gas legacyOptimized: 167269

test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ contract C {
1818
// EVMVersion: >homestead
1919
// ----
2020
// h(uint256[2][]): 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324
21-
// gas irOptimized: 180882
21+
// gas irOptimized: 180768
2222
// gas legacy: 184929
2323
// gas legacyOptimized: 181504
2424
// i(uint256[2][2]): 123, 124, 223, 224 -> 32, 128, 123, 124, 223, 224
25-
// gas irOptimized: 112533
25+
// gas irOptimized: 112471
2626
// gas legacy: 115468
2727
// gas legacyOptimized: 112988

test/libsolidity/semanticTests/array/arrays_complex_from_and_to_storage.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ contract Test {
1212
}
1313
// ----
1414
// set(uint24[3][]): 0x20, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12 -> 0x06
15-
// gas irOptimized: 189640
15+
// gas irOptimized: 186766
1616
// gas legacy: 211149
1717
// gas legacyOptimized: 206054
1818
// data(uint256,uint256): 0x02, 0x02 -> 0x09

test/libsolidity/semanticTests/array/constant_var_as_array_length.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ contract C {
99

1010
// ----
1111
// constructor(): 1, 2, 3 ->
12-
// gas irOptimized: 141700
12+
// gas irOptimized: 141581
1313
// gas legacy: 183490
1414
// gas legacyOptimized: 151938
1515
// a(uint256): 0 -> 1

test/libsolidity/semanticTests/array/copying/array_copy_calldata_storage.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ contract c {
2020
}
2121
// ----
2222
// store(uint256[9],uint8[3][]): 21, 22, 23, 24, 25, 26, 27, 28, 29, 0x140, 4, 1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33 -> 32
23-
// gas irOptimized: 650748
23+
// gas irOptimized: 648324
2424
// gas legacy: 694515
2525
// gas legacyOptimized: 694013
2626
// retrieve() -> 9, 28, 9, 28, 4, 3, 32

test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint40.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ contract C {
4646
}
4747
// ----
4848
// f() -> true
49-
// gas irOptimized: 146913
49+
// gas irOptimized: 146936
5050
// gas legacy: 155961
5151
// gas legacyOptimized: 153588

test/libsolidity/semanticTests/array/copying/array_copy_clear_storage.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ contract C {
1313
}
1414
// ----
1515
// f() -> 0
16-
// gas irOptimized: 134352
16+
// gas irOptimized: 134365
1717
// gas legacy: 135313
1818
// gas legacyOptimized: 134548

0 commit comments

Comments
 (0)