Skip to content

Commit 3a48be5

Browse files
authored
Merge pull request #9602 from ethereum/structMemToStorageSol2Yul
[Sol->Yul] Implementing various copying of structs
2 parents 840791a + a740cb6 commit 3a48be5

13 files changed

+394
-70
lines changed

Diff for: Changelog.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Language Features:
44
* Allow function definitions outside of contracts, behaving much like internal library functions.
5-
5+
* Code generator: Implementing copying structs from calldata to storage.
66

77
Compiler Features:
88
* SMTChecker: Add underflow and overflow as verification conditions in the CHC engine.

Diff for: libsolidity/codegen/LValue.cpp

+37-28
Original file line numberDiff line numberDiff line change
@@ -357,38 +357,47 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
357357
"Struct assignment with conversion."
358358
);
359359
solAssert(!structType.containsNestedMapping(), "");
360-
solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported.");
361-
for (auto const& member: structType.members(nullptr))
360+
if (sourceType.location() == DataLocation::CallData)
362361
{
363-
// assign each member that can live outside of storage
364-
TypePointer const& memberType = member.type;
365-
solAssert(memberType->nameable(), "");
366-
TypePointer sourceMemberType = sourceType.memberType(member.name);
367-
if (sourceType.location() == DataLocation::Storage)
362+
solAssert(sourceType.sizeOnStack() == 1, "");
363+
solAssert(structType.sizeOnStack() == 1, "");
364+
m_context << Instruction::DUP2 << Instruction::DUP2;
365+
m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, 0), 2, 0);
366+
}
367+
else
368+
{
369+
for (auto const& member: structType.members(nullptr))
368370
{
369-
// stack layout: source_ref target_ref
370-
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
371-
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
371+
// assign each member that can live outside of storage
372+
TypePointer const& memberType = member.type;
373+
solAssert(memberType->nameable(), "");
374+
TypePointer sourceMemberType = sourceType.memberType(member.name);
375+
if (sourceType.location() == DataLocation::Storage)
376+
{
377+
// stack layout: source_ref target_ref
378+
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
379+
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
380+
m_context << u256(offsets.second);
381+
// stack: source_ref target_ref source_member_ref source_member_off
382+
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
383+
// stack: source_ref target_ref source_value...
384+
}
385+
else
386+
{
387+
solAssert(sourceType.location() == DataLocation::Memory, "");
388+
// stack layout: source_ref target_ref
389+
m_context << sourceType.memoryOffsetOfMember(member.name);
390+
m_context << Instruction::DUP3 << Instruction::ADD;
391+
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
392+
// stack layout: source_ref target_ref source_value...
393+
}
394+
unsigned stackSize = sourceMemberType->sizeOnStack();
395+
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
396+
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
372397
m_context << u256(offsets.second);
373-
// stack: source_ref target_ref source_member_ref source_member_off
374-
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
375-
// stack: source_ref target_ref source_value...
376-
}
377-
else
378-
{
379-
solAssert(sourceType.location() == DataLocation::Memory, "");
380-
// stack layout: source_ref target_ref
381-
m_context << sourceType.memoryOffsetOfMember(member.name);
382-
m_context << Instruction::DUP3 << Instruction::ADD;
383-
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
384-
// stack layout: source_ref target_ref source_value...
398+
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
399+
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
385400
}
386-
unsigned stackSize = sourceMemberType->sizeOnStack();
387-
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
388-
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
389-
m_context << u256(offsets.second);
390-
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
391-
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
392401
}
393402
// stack layout: source_ref target_ref
394403
solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");

Diff for: libsolidity/codegen/YulUtilFunctions.cpp

+187-32
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
964964
("dataAreaFunction", arrayDataAreaFunction(_type))
965965
("isByteArray", _type.isByteArray())
966966
("indexAccess", storageArrayIndexAccessFunction(_type))
967-
("storeValue", updateStorageValueFunction(*_type.baseType()))
967+
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
968968
("maxArrayLength", (u256(1) << 64).str())
969969
("shl", shiftLeftFunctionDynamic())
970970
("shr", shiftRightFunction(248))
@@ -994,7 +994,7 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
994994
("functionName", functionName)
995995
("fetchLength", arrayLengthFunction(_type))
996996
("indexAccess", storageArrayIndexAccessFunction(_type))
997-
("storeValue", updateStorageValueFunction(*_type.baseType()))
997+
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
998998
("maxArrayLength", (u256(1) << 64).str())
999999
("zeroValueFunction", zeroValueFunction(*_type.baseType()))
10001000
.render();
@@ -1404,34 +1404,55 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
14041404

14051405
string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
14061406
{
1407+
if (_type.isValueType())
1408+
return readFromStorageValueType(_type, _offset, _splitFunctionTypes);
1409+
else
1410+
{
1411+
solAssert(_offset == 0, "");
1412+
return readFromStorageReferenceType(_type);
1413+
}
1414+
}
1415+
1416+
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
1417+
{
1418+
solAssert(_type.isValueType(), "");
1419+
return readFromStorageValueTypeDynamic(_type, _splitFunctionTypes);
1420+
}
1421+
1422+
string YulUtilFunctions::readFromStorageValueType(Type const& _type, size_t _offset, bool _splitFunctionTypes)
1423+
{
1424+
solAssert(_type.isValueType(), "");
1425+
14071426
if (_type.category() == Type::Category::Function)
14081427
solUnimplementedAssert(!_splitFunctionTypes, "");
14091428
string functionName =
1410-
"read_from_storage_" +
1411-
string(_splitFunctionTypes ? "split_" : "") +
1412-
"offset_" +
1413-
to_string(_offset) +
1414-
"_" +
1415-
_type.identifier();
1429+
"read_from_storage_" +
1430+
string(_splitFunctionTypes ? "split_" : "") +
1431+
"offset_" +
1432+
to_string(_offset) +
1433+
"_" +
1434+
_type.identifier();
1435+
14161436
return m_functionCollector.createFunction(functionName, [&] {
14171437
solAssert(_type.sizeOnStack() == 1, "");
14181438
return Whiskers(R"(
14191439
function <functionName>(slot) -> value {
14201440
value := <extract>(sload(slot))
14211441
}
14221442
)")
1423-
("functionName", functionName)
1424-
("extract", extractFromStorageValue(_type, _offset, false))
1425-
.render();
1443+
("functionName", functionName)
1444+
("extract", extractFromStorageValue(_type, _offset, false))
1445+
.render();
14261446
});
14271447
}
1428-
1429-
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
1448+
string YulUtilFunctions::readFromStorageValueTypeDynamic(Type const& _type, bool _splitFunctionTypes)
14301449
{
1450+
solAssert(_type.isValueType(), "");
14311451
if (_type.category() == Type::Category::Function)
14321452
solUnimplementedAssert(!_splitFunctionTypes, "");
1453+
14331454
string functionName =
1434-
"read_from_storage_dynamic" +
1455+
"read_from_storage_value_type_dynamic" +
14351456
string(_splitFunctionTypes ? "split_" : "") +
14361457
"_" +
14371458
_type.identifier();
@@ -1447,6 +1468,55 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
14471468
.render();
14481469
});
14491470
}
1471+
string YulUtilFunctions::readFromStorageReferenceType(Type const& _type)
1472+
{
1473+
solUnimplementedAssert(_type.category() == Type::Category::Struct, "");
1474+
1475+
string functionName = "read_from_storage_reference_type_" + _type.identifier();
1476+
1477+
auto const& structType = dynamic_cast<StructType const&>(_type);
1478+
solAssert(structType.location() == DataLocation::Memory, "");
1479+
MemberList::MemberMap structMembers = structType.nativeMembers(nullptr);
1480+
vector<map<string, string>> memberSetValues(structMembers.size());
1481+
for (size_t i = 0; i < structMembers.size(); ++i)
1482+
{
1483+
auto const& [memberSlotDiff, memberStorageOffset] = structType.storageOffsetsOfMember(structMembers[i].name);
1484+
1485+
memberSetValues[i]["setMember"] = Whiskers(R"(
1486+
{
1487+
let <memberValues> := <readFromStorage>(add(slot, <memberSlotDiff>)<?hasOffset>, <memberStorageOffset></hasOffset>)
1488+
<writeToMemory>(add(value, <memberMemoryOffset>), <memberValues>)
1489+
}
1490+
)")
1491+
("memberValues", suffixedVariableNameList("memberValue_", 0, structMembers[i].type->stackItems().size()))
1492+
("memberMemoryOffset", structType.memoryOffsetOfMember(structMembers[i].name).str())
1493+
("memberSlotDiff", memberSlotDiff.str())
1494+
("memberStorageOffset", to_string(memberStorageOffset))
1495+
("readFromStorage",
1496+
structMembers[i].type->isValueType() ?
1497+
readFromStorageDynamic(*structMembers[i].type, true) :
1498+
readFromStorage(*structMembers[i].type, memberStorageOffset, true)
1499+
)
1500+
("writeToMemory", writeToMemoryFunction(*structMembers[i].type))
1501+
("hasOffset", structMembers[i].type->isValueType())
1502+
.render();
1503+
}
1504+
1505+
return m_functionCollector.createFunction(functionName, [&] {
1506+
return Whiskers(R"(
1507+
function <functionName>(slot) -> value {
1508+
value := <allocStruct>()
1509+
<#member>
1510+
<setMember>
1511+
</member>
1512+
}
1513+
)")
1514+
("functionName", functionName)
1515+
("allocStruct", allocateMemoryStructFunction(structType))
1516+
("member", memberSetValues)
1517+
.render();
1518+
});
1519+
}
14501520

14511521
string YulUtilFunctions::readFromMemory(Type const& _type)
14521522
{
@@ -1458,18 +1528,25 @@ string YulUtilFunctions::readFromCalldata(Type const& _type)
14581528
return readFromMemoryOrCalldata(_type, true);
14591529
}
14601530

1461-
string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::optional<unsigned> const& _offset)
1531+
string YulUtilFunctions::updateStorageValueFunction(
1532+
Type const& _fromType,
1533+
Type const& _toType,
1534+
std::optional<unsigned> const& _offset
1535+
)
14621536
{
14631537
string const functionName =
14641538
"update_storage_value_" +
14651539
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
1466-
_type.identifier();
1540+
_fromType.identifier() +
1541+
"_to_" +
1542+
_toType.identifier();
14671543

14681544
return m_functionCollector.createFunction(functionName, [&] {
1469-
if (_type.isValueType())
1545+
if (_toType.isValueType())
14701546
{
1471-
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
1472-
solAssert(_type.storageBytes() > 0, "Invalid storage bytes size.");
1547+
solAssert(_fromType.isImplicitlyConvertibleTo(_toType), "");
1548+
solAssert(_toType.storageBytes() <= 32, "Invalid storage bytes size.");
1549+
solAssert(_toType.storageBytes() > 0, "Invalid storage bytes size.");
14731550

14741551
return Whiskers(R"(
14751552
function <functionName>(slot, <offset>value) {
@@ -1480,19 +1557,83 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::opti
14801557
("functionName", functionName)
14811558
("update",
14821559
_offset.has_value() ?
1483-
updateByteSliceFunction(_type.storageBytes(), *_offset) :
1484-
updateByteSliceFunctionDynamic(_type.storageBytes())
1560+
updateByteSliceFunction(_toType.storageBytes(), *_offset) :
1561+
updateByteSliceFunctionDynamic(_toType.storageBytes())
14851562
)
14861563
("offset", _offset.has_value() ? "" : "offset, ")
1487-
("prepare", prepareStoreFunction(_type))
1564+
("prepare", prepareStoreFunction(_toType))
14881565
.render();
14891566
}
14901567
else
14911568
{
1492-
if (_type.category() == Type::Category::Array)
1493-
solUnimplementedAssert(false, "");
1494-
else if (_type.category() == Type::Category::Struct)
1569+
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
1570+
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
1571+
solAssert(fromReferenceType && toReferenceType, "");
1572+
solAssert(*toReferenceType->copyForLocation(
1573+
fromReferenceType->location(),
1574+
fromReferenceType->isPointer()
1575+
).get() == *fromReferenceType, "");
1576+
1577+
if (_toType.category() == Type::Category::Array)
14951578
solUnimplementedAssert(false, "");
1579+
else if (_toType.category() == Type::Category::Struct)
1580+
{
1581+
solAssert(_fromType.category() == Type::Category::Struct, "");
1582+
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
1583+
auto const& toStructType = dynamic_cast<StructType const&>(_toType);
1584+
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
1585+
solAssert(fromStructType.location() != DataLocation::Storage, "");
1586+
solUnimplementedAssert(_offset.has_value() && _offset.value() == 0, "");
1587+
1588+
Whiskers templ(R"(
1589+
function <functionName>(slot, value) {
1590+
<#member>
1591+
{
1592+
<updateMemberCall>
1593+
}
1594+
</member>
1595+
}
1596+
)");
1597+
templ("functionName", functionName);
1598+
1599+
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
1600+
1601+
vector<map<string, string>> memberParams(structMembers.size());
1602+
for (size_t i = 0; i < structMembers.size(); ++i)
1603+
{
1604+
solAssert(structMembers[i].type->memoryHeadSize() == 32, "");
1605+
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
1606+
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
1607+
memberParams[i]["updateMemberCall"] = Whiskers(R"(
1608+
let <memberValues> := <loadFromMemoryOrCalldata>(add(value, <memberOffset>))
1609+
<updateMember>(add(slot, <memberStorageSlotDiff>), <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
1610+
)")
1611+
("memberValues", suffixedVariableNameList(
1612+
"memberValue_",
1613+
0,
1614+
structMembers[i].type->stackItems().size()
1615+
))
1616+
("hasOffset", structMembers[i].type->isValueType())
1617+
(
1618+
"updateMember",
1619+
structMembers[i].type->isValueType() ?
1620+
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type) :
1621+
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type, offset)
1622+
)
1623+
("memberStorageSlotDiff", slotDiff.str())
1624+
("memberStorageOffset", to_string(offset))
1625+
("memberOffset",
1626+
fromCalldata ?
1627+
to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) :
1628+
fromStructType.memoryOffsetOfMember(structMembers[i].name).str()
1629+
)
1630+
("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata))
1631+
.render();
1632+
}
1633+
templ("member", memberParams);
1634+
1635+
return templ.render();
1636+
}
14961637
else
14971638
solAssert(false, "Invalid non-value type for assignment.");
14981639
}
@@ -2052,13 +2193,27 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
20522193

20532194
solUnimplementedAssert(!fromStructType.isDynamicallyEncoded(), "");
20542195
solUnimplementedAssert(toStructType.location() == DataLocation::Memory, "");
2055-
solUnimplementedAssert(fromStructType.location() == DataLocation::CallData, "");
2196+
solUnimplementedAssert(fromStructType.location() != DataLocation::Memory, "");
2197+
2198+
if (fromStructType.location() == DataLocation::CallData)
2199+
{
2200+
body = Whiskers(R"(
2201+
converted := <abiDecode>(value, calldatasize())
2202+
)")("abiDecode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleDecoder(
2203+
{&toStructType}
2204+
)).render();
2205+
}
2206+
else
2207+
{
2208+
solAssert(fromStructType.location() == DataLocation::Storage, "");
2209+
2210+
body = Whiskers(R"(
2211+
converted := <readFromStorage>(value)
2212+
)")
2213+
("readFromStorage", readFromStorage(toStructType, 0, true))
2214+
.render();
2215+
}
20562216

2057-
body = Whiskers(R"(
2058-
converted := <abiDecode>(value, calldatasize())
2059-
)")("abiDecode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleDecoder(
2060-
{&toStructType}
2061-
)).render();
20622217
break;
20632218
}
20642219
case Type::Category::FixedBytes:
@@ -2490,7 +2645,7 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
24902645
}
24912646
)")
24922647
("functionName", functionName)
2493-
("store", updateStorageValueFunction(_type))
2648+
("store", updateStorageValueFunction(_type, _type))
24942649
("zeroValue", zeroValueFunction(_type))
24952650
.render();
24962651
else if (_type.category() == Type::Category::Array)

0 commit comments

Comments
 (0)