[Sol->Yul] Adding cleanUpArrayEnd util function.

Co-authored-by: chriseth <chris@ethereum.org>

Co-authored-by: Alex Beregszaszi <alex@rtfs.hu>

Co-authored-by: Kamil Śliwak <kamil.sliwak@codepoets.it>

Co-authored-by: Alex Beregszaszi <alex@rtfs.hu>

Co-authored-by: Kamil Śliwak <kamil.sliwak@codepoets.it>
This commit is contained in:
Djordje Mijovic 2021-04-22 09:47:12 +02:00
parent 1d1175c292
commit 3e5f5fccf9
2 changed files with 82 additions and 38 deletions

View File

@ -1168,21 +1168,7 @@ std::string YulUtilFunctions::resizeArrayFunction(ArrayType const& _type)
</isDynamic> </isDynamic>
<?needsClearing> <?needsClearing>
// Size was reduced, clear end of array <cleanUpArrayEnd>(array, oldLen, newLen)
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)
<?packed>
// if we are dealing with packed array and offset is greater than zero
// we have to partially clear last slot that is still used, so decreasing start by one
let offset := mul(mod(newLen, <itemsPerSlot>), <storageBytes>)
if gt(offset, 0) { <partialClearStorageSlot>(sub(deleteStart, 1), offset) }
</packed>
<clearStorageRange>(deleteStart, deleteEnd)
}
</needsClearing> </needsClearing>
})"); })");
templ("functionName", functionName); templ("functionName", functionName);
@ -1193,19 +1179,49 @@ std::string YulUtilFunctions::resizeArrayFunction(ArrayType const& _type)
bool isMappingBase = _type.baseType()->category() == Type::Category::Mapping; bool isMappingBase = _type.baseType()->category() == Type::Category::Mapping;
templ("needsClearing", !isMappingBase); templ("needsClearing", !isMappingBase);
if (!isMappingBase) if (!isMappingBase)
{ templ("cleanUpArrayEnd", cleanUpStorageArrayEndFunction(_type));
templ("convertToSize", arrayConvertLengthToSize(_type));
templ("dataPosition", arrayDataAreaFunction(_type));
templ("clearStorageRange", clearStorageRangeFunction(*_type.baseType()));
templ("packed", _type.baseType()->storageBytes() <= 16);
templ("itemsPerSlot", to_string(32 / _type.baseType()->storageBytes()));
templ("storageBytes", to_string(_type.baseType()->storageBytes()));
templ("partialClearStorageSlot", partialClearStorageSlotFunction());
}
return templ.render(); return templ.render();
}); });
} }
string YulUtilFunctions::cleanUpStorageArrayEndFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.baseType()->category() != Type::Category::Mapping, "");
solAssert(!_type.isByteArray(), "");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "");
string functionName = "cleanup_storage_array_end_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](vector<string>& _args, vector<string>&) {
_args = {"array", "len", "startIndex"};
return Whiskers(R"(
if lt(startIndex, len) {
// Size was reduced, clear end of array
let oldSlotCount := <convertToSize>(len)
let newSlotCount := <convertToSize>(startIndex)
let arrayDataStart := <dataPosition>(array)
let deleteStart := add(arrayDataStart, newSlotCount)
let deleteEnd := add(arrayDataStart, oldSlotCount)
<?packed>
// if we are dealing with packed array and offset is greater than zero
// we have to partially clear last slot that is still used, so decreasing start by one
let offset := mul(mod(startIndex, <itemsPerSlot>), <storageBytes>)
if gt(offset, 0) { <partialClearStorageSlot>(sub(deleteStart, 1), offset) }
</packed>
<clearStorageRange>(deleteStart, deleteEnd)
}
)")
("convertToSize", arrayConvertLengthToSize(_type))
("dataPosition", arrayDataAreaFunction(_type))
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
("packed", _type.baseType()->storageBytes() <= 16)
("itemsPerSlot", to_string(32 / _type.baseType()->storageBytes()))
("storageBytes", to_string(_type.baseType()->storageBytes()))
("partialClearStorageSlot", partialClearStorageSlotFunction())
.render();
});
}
string YulUtilFunctions::resizeDynamicByteArrayFunction(ArrayType const& _type) string YulUtilFunctions::resizeDynamicByteArrayFunction(ArrayType const& _type)
{ {
string functionName = "resize_array_" + _type.identifier(); string functionName = "resize_array_" + _type.identifier();
@ -1230,6 +1246,30 @@ string YulUtilFunctions::resizeDynamicByteArrayFunction(ArrayType const& _type)
}); });
} }
string YulUtilFunctions::cleanUpDynamicByteArrayEndSlotsFunction(ArrayType const& _type)
{
solAssert(_type.isByteArray(), "");
solAssert(_type.isDynamicallySized(), "");
string functionName = "clean_up_bytearray_end_slots_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](vector<string>& _args, vector<string>&) {
_args = {"array", "len", "startIndex"};
return Whiskers(R"(
if gt(len, 31) {
let dataArea := <dataLocation>(array)
let deleteStart := add(dataArea, div(<roundUp>(startIndex), 32))
// If we are clearing array to be short byte array, we want to clear only data starting from array data area.
if lt(startIndex, 32) { deleteStart := dataArea }
<clearStorageRange>(deleteStart, add(dataArea, div(add(len, 31), 32)))
}
)")
("dataLocation", arrayDataAreaFunction(_type))
("roundUp", roundUpFunction())
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
.render();
});
}
string YulUtilFunctions::decreaseByteArraySizeFunction(ArrayType const& _type) string YulUtilFunctions::decreaseByteArraySizeFunction(ArrayType const& _type)
{ {
string functionName = "byte_array_decrease_size_" + _type.identifier(); string functionName = "byte_array_decrease_size_" + _type.identifier();
@ -1805,28 +1845,19 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
let oldLen := <byteArrayLength>(sload(slot)) let oldLen := <byteArrayLength>(sload(slot))
// potentially truncate data
<cleanUpEndArray>(slot, oldLen, newLen)
let srcOffset := 0 let srcOffset := 0
<?fromMemory> <?fromMemory>
srcOffset := 0x20 srcOffset := 0x20
</fromMemory> </fromMemory>
// This is not needed in all branches.
let dstDataArea
if or(gt(oldLen, 31), gt(newLen, 31)) {
dstDataArea := <dstDataLocation>(slot)
}
if gt(oldLen, 31) {
// potentially truncate data
let deleteStart := add(dstDataArea, div(add(newLen, 31), 32))
if lt(newLen, 32) { deleteStart := dstDataArea }
<clearStorageRange>(deleteStart, add(dstDataArea, div(add(oldLen, 31), 32)))
}
switch gt(newLen, 31) switch gt(newLen, 31)
case 1 { case 1 {
let loopEnd := and(newLen, not(0x1f)) let loopEnd := and(newLen, not(0x1f))
<?fromStorage> src := <srcDataLocation>(src) </fromStorage> <?fromStorage> src := <srcDataLocation>(src) </fromStorage>
let dstPtr := dstDataArea let dstPtr := <dstDataLocation>(slot)
let i := 0 let i := 0
for { } lt(i, loopEnd) { i := add(i, 0x20) } { for { } lt(i, loopEnd) { i := add(i, 0x20) } {
sstore(dstPtr, <read>(add(src, srcOffset))) sstore(dstPtr, <read>(add(src, srcOffset)))
@ -1860,7 +1891,7 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
templ("dstDataLocation", arrayDataAreaFunction(_toType)); templ("dstDataLocation", arrayDataAreaFunction(_toType));
if (fromStorage) if (fromStorage)
templ("srcDataLocation", arrayDataAreaFunction(_fromType)); templ("srcDataLocation", arrayDataAreaFunction(_fromType));
templ("clearStorageRange", clearStorageRangeFunction(*_toType.baseType())); templ("cleanUpEndArray", cleanUpDynamicByteArrayEndSlotsFunction(_toType));
templ("srcIncrement", to_string(fromStorage ? 1 : 0x20)); templ("srcIncrement", to_string(fromStorage ? 1 : 0x20));
templ("read", fromStorage ? "sload" : fromCalldata ? "calldataload" : "mload"); templ("read", fromStorage ? "sload" : fromCalldata ? "calldataload" : "mload");
templ("maskBytes", maskBytesFunctionDynamic()); templ("maskBytes", maskBytesFunctionDynamic());

View File

@ -207,6 +207,11 @@ public:
/// signature: (array, newLen) /// signature: (array, newLen)
std::string resizeArrayFunction(ArrayType const& _type); std::string resizeArrayFunction(ArrayType const& _type);
/// @returns the name of a function that zeroes all storage array elements from `startIndex` to `len` (excluding).
/// Assumes that `len` is the array length. Does nothing if `startIndex >= len`. Does not modify the stored length.
/// signature: (array, len, startIndex)
std::string cleanUpStorageArrayEndFunction(ArrayType const& _type);
/// @returns the name of a function that reduces the size of a storage array by one element /// @returns the name of a function that reduces the size of a storage array by one element
/// signature: (array) /// signature: (array)
std::string storageArrayPopFunction(ArrayType const& _type); std::string storageArrayPopFunction(ArrayType const& _type);
@ -543,6 +548,14 @@ private:
/// signature: (array, newLen) /// signature: (array, newLen)
std::string resizeDynamicByteArrayFunction(ArrayType const& _type); std::string resizeDynamicByteArrayFunction(ArrayType const& _type);
/// @returns the name of a function that cleans up elements of a storage byte array starting from startIndex.
/// It will not copy elements in case of transformation to short byte array, and will not change array length.
/// In case of startIndex is greater than len, doesn't do anything.
/// In case of short byte array (< 32 bytes) doesn't do anything.
/// If the first slot to be cleaned up is partially occupied, does not touch it. Cleans up only completely unused slots.
/// signature: (array, len, startIndex)
std::string cleanUpDynamicByteArrayEndSlotsFunction(ArrayType const& _type);
/// @returns the name of a function that increases size of byte array /// @returns the name of a function that increases size of byte array
/// when we resize byte array frextractUsedSetLenom < 32 elements to >= 32 elements or we push to byte array of size 31 copying of data will occur /// when we resize byte array frextractUsedSetLenom < 32 elements to >= 32 elements or we push to byte array of size 31 copying of data will occur
/// signature: (array, data, oldLen, newLen) /// signature: (array, data, oldLen, newLen)