[Sol->Yul] Implementing arrays copying to storage.

Co-authored-by: chriseth <chris@ethereum.org>
This commit is contained in:
Djordje Mijovic 2020-10-08 20:31:13 +02:00
parent bc97c3e1ce
commit 850a94bdc9
3 changed files with 187 additions and 29 deletions

View File

@ -808,7 +808,7 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
string functionName = "array_length_" + _type.identifier(); string functionName = "array_length_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"( Whiskers w(R"(
function <functionName>(value) -> length { function <functionName>(value<?dynamic><?calldata>, len</calldata></dynamic>) -> length {
<?dynamic> <?dynamic>
<?memory> <?memory>
length := mload(value) length := mload(value)
@ -819,6 +819,9 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
length := <extractByteArrayLength>(length) length := <extractByteArrayLength>(length)
</byteArray> </byteArray>
</storage> </storage>
<?calldata>
length := len
</calldata>
<!dynamic> <!dynamic>
length := <length> length := <length>
</dynamic> </dynamic>
@ -830,17 +833,14 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
w("length", toCompactHexWithPrefix(_type.length())); w("length", toCompactHexWithPrefix(_type.length()));
w("memory", _type.location() == DataLocation::Memory); w("memory", _type.location() == DataLocation::Memory);
w("storage", _type.location() == DataLocation::Storage); w("storage", _type.location() == DataLocation::Storage);
w("calldata", _type.location() == DataLocation::CallData);
if (_type.location() == DataLocation::Storage) if (_type.location() == DataLocation::Storage)
{ {
w("byteArray", _type.isByteArray()); w("byteArray", _type.isByteArray());
if (_type.isByteArray()) if (_type.isByteArray())
w("extractByteArrayLength", extractByteArrayLengthFunction()); w("extractByteArrayLength", extractByteArrayLengthFunction());
} }
if (_type.isDynamicallySized())
solAssert(
_type.location() != DataLocation::CallData,
"called regular array length function on calldata array"
);
return w.render(); 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<ReferenceType const&>(_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 <functionName>(slot, value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) {
let length := <arrayLength>(value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>)
<?isToDynamic>
<resizeArray>(slot, length)
</isToDynamic>
let srcPtr :=
<?isFromMemoryDynamic>
add(value, 0x20)
<!isFromMemoryDynamic>
value
</isFromMemoryDynamic>
let elementSlot := <dstDataLocation>(slot)
let elementOffset := 0
for { let i := 0 } lt(i, length) {i := add(i, 1)} {
<?fromCalldata>
let <elementValues> :=
<?dynamicallyEncodedBase>
<accessCalldataTail>(value, srcPtr)
<!dynamicallyEncodedBase>
srcPtr
</dynamicallyEncodedBase>
<?isValueType>
<elementValues> := <readFromCalldataOrMemory>(<elementValues>)
</isValueType>
</fromCalldata>
<?fromMemory>
let <elementValues> := <readFromCalldataOrMemory>(srcPtr)
</fromMemory>
<updateStorageValue>(elementSlot<?isValueType>, elementOffset</isValueType>, <elementValues>)
srcPtr := add(srcPtr, <stride>)
<?multipleItemsPerSlot>
elementOffset := add(elementOffset, <storageStride>)
if gt(elementOffset, sub(32, <storageStride>)) {
elementOffset := 0
elementSlot := add(elementSlot, 1)
}
<!multipleItemsPerSlot>
elementSlot := add(elementSlot, <storageSize>)
elementOffset := 0
</multipleItemsPerSlot>
}
}
)");
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 YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{ {
string functionName = "array_convert_length_to_size_" + _type.identifier(); string functionName = "array_convert_length_to_size_" + _type.identifier();
@ -1755,23 +1854,39 @@ string YulUtilFunctions::updateStorageValueFunction(
else else
{ {
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType); auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_toType); auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
solAssert(fromReferenceType && toReferenceType, ""); solAssert(fromReferenceType && toReferenceType, "");
solAssert(*toReferenceType->copyForLocation( solAssert(*toReferenceType->copyForLocation(
fromReferenceType->location(), fromReferenceType->location(),
fromReferenceType->isPointer() fromReferenceType->isPointer()
).get() == *fromReferenceType, ""); ).get() == *fromReferenceType, "");
solUnimplementedAssert(fromReferenceType->location() != DataLocation::Storage, "");
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
if (_toType.category() == Type::Category::Array) if (_toType.category() == Type::Category::Array)
solUnimplementedAssert(false, ""); {
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <value>) {
<copyArrayToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("copyArrayToStorage", copyArrayToStorage(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
return templ.render();
}
else if (_toType.category() == Type::Category::Struct) else if (_toType.category() == Type::Category::Struct)
{ {
solAssert(_fromType.category() == Type::Category::Struct, "");
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType); auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
auto const& toStructType = dynamic_cast<StructType const&>(_toType); auto const& toStructType = dynamic_cast<StructType const&>(_toType);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), ""); solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solAssert(fromStructType.location() != DataLocation::Storage, ""); solAssert(_offset.value_or(0) == 0, "");
solUnimplementedAssert(_offset.has_value() && _offset.value() == 0, "");
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(slot, value) { function <functionName>(slot, value) {
@ -1785,6 +1900,7 @@ string YulUtilFunctions::updateStorageValueFunction(
templ("functionName", functionName); templ("functionName", functionName);
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr); MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
MemberList::MemberMap toStructMembers = toStructType.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size()); vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i) for (size_t i = 0; i < structMembers.size(); ++i)
@ -1792,31 +1908,65 @@ string YulUtilFunctions::updateStorageValueFunction(
solAssert(structMembers[i].type->memoryHeadSize() == 32, ""); solAssert(structMembers[i].type->memoryHeadSize() == 32, "");
bool fromCalldata = fromStructType.location() == DataLocation::CallData; bool fromCalldata = fromStructType.location() == DataLocation::CallData;
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name); auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
memberParams[i]["updateMemberCall"] = Whiskers(R"(
let <memberValues> := <loadFromMemoryOrCalldata>(add(value, <memberOffset>)) Whiskers t(R"(
<updateMember>(add(slot, <memberStorageSlotDiff>), <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>) let memberSlot := add(slot, <memberStorageSlotDiff>)
)")
("memberValues", suffixedVariableNameList( <?fromCalldata>
<?dynamicallyEncodedMember>
let <memberCalldataOffset> := <accessCalldataTail>(value, add(value, <memberOffset>))
<!dynamicallyEncodedMember>
let <memberCalldataOffset> := add(value, <memberOffset>)
</dynamicallyEncodedMember>
<?isValueType>
let <memberValues> := <loadFromMemoryOrCalldata>(<memberCalldataOffset>)
<updateMember>(memberSlot, <memberStorageOffset>, <memberValues>)
<!isValueType>
<updateMember>(memberSlot, <memberCalldataOffset>)
</isValueType>
<!fromCalldata>
let memberMemoryOffset := add(value, <memberOffset>)
let <memberValues> := <loadFromMemoryOrCalldata>(memberMemoryOffset)
<updateMember>(memberSlot, <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
</fromCalldata>
)");
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_", "memberValue_",
0, 0,
structMembers[i].type->stackItems().size() structMembers[i].type->stackItems().size()
)) ));
("hasOffset", structMembers[i].type->isValueType()) t("hasOffset", structMembers[i].type->isValueType());
( t(
"updateMember", "updateMember",
structMembers[i].type->isValueType() ? structMembers[i].type->isValueType() ?
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type) : updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type) :
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type, offset) updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type, offset)
) );
("memberStorageSlotDiff", slotDiff.str()) t("memberStorageSlotDiff", slotDiff.str());
("memberStorageOffset", to_string(offset)) t("memberStorageOffset", to_string(offset));
("memberOffset", t(
"memberOffset",
fromCalldata ? fromCalldata ?
to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) : to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) :
fromStructType.memoryOffsetOfMember(structMembers[i].name).str() fromStructType.memoryOffsetOfMember(structMembers[i].name).str()
) );
("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata)) if (!fromCalldata || structMembers[i].type->isValueType())
.render(); t("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata));
memberParams[i]["updateMemberCall"] = t.render();
} }
templ("member", memberParams); templ("member", memberParams);

View File

@ -173,6 +173,10 @@ public:
/// signature: (slot) -> /// signature: (slot) ->
std::string clearStorageArrayFunction(ArrayType const& _type); 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 /// 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 /// size in memory (number of storage slots or calldata/memory bytes) it
/// will require. /// will require.

View File

@ -386,7 +386,11 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
writeToLValue(*m_currentLValue, value); 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); define(_assignment, value);
m_currentLValue.reset(); m_currentLValue.reset();