mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[Sol->Yul] Implementing arrays copying to storage.
Co-authored-by: chriseth <chris@ethereum.org>
This commit is contained in:
parent
bc97c3e1ce
commit
850a94bdc9
@ -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);
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user