Merge pull request #10560 from ethereum/arrayPushStructSol2Yul

[Sol->Yul] Implementing missing array push
This commit is contained in:
chriseth 2021-01-11 18:51:30 +01:00 committed by GitHub
commit fda3a31930
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 140 deletions

View File

@ -1425,15 +1425,23 @@ string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type, Type const* _fromType)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
if (!_fromType)
_fromType = _type.baseType();
else if (_fromType->isValueType())
solUnimplementedAssert(*_fromType == *_type.baseType(), "");
string functionName = "array_push_" + _type.identifier();
string functionName =
string{"array_push_from_"} +
_fromType->identifier() +
"_to_" +
_type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, value) {
function <functionName>(array <values>) {
<?isByteArray>
let data := sload(array)
let oldLen := <extractByteArrayLength>(data)
@ -1441,7 +1449,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
switch gt(oldLen, 31)
case 0 {
value := byte(0, value)
let value := byte(0 <values>)
switch oldLen
case 31 {
// Here we have special case when array switches from short array to long array
@ -1464,23 +1472,24 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
default {
sstore(array, add(data, 2))
let slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset, value)
<storeValue>(slot, offset <values>)
}
<!isByteArray>
let oldLen := sload(array)
if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() }
sstore(array, add(oldLen, 1))
let slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset, value)
<storeValue>(slot, offset <values>)
</isByteArray>
})")
("functionName", functionName)
("values", _fromType->sizeOnStack() == 0 ? "" : ", " + suffixedVariableNameList("value", 0, _fromType->sizeOnStack()))
("panic", panicFunction(PanicCode::ResourceError))
("extractByteArrayLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "")
("dataAreaFunction", arrayDataAreaFunction(_type))
("isByteArray", _type.isByteArray())
("indexAccess", storageArrayIndexAccessFunction(_type))
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
("storeValue", updateStorageValueFunction(*_fromType, *_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
("shl", shiftLeftFunctionDynamic())
("shr", shiftRightFunction(248))
@ -2572,142 +2581,32 @@ string YulUtilFunctions::updateStorageValueFunction(
fromReferenceType->location(),
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
solAssert(_offset.value_or(0) == 0, "");
if (_toType.category() == Type::Category::Array)
{
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<copyArrayToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("dynamicOffset", !_offset.has_value());
templ("panic", panicFunction(PanicCode::Generic));
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("copyArrayToStorage", copyArrayToStorageFunction(
Whiskers templ(R"(
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<copyToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("dynamicOffset", !_offset.has_value());
templ("panic", panicFunction(PanicCode::Generic));
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
if (_fromType.category() == Type::Category::Array)
templ("copyToStorage", copyArrayToStorageFunction(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
return templ.render();
}
else
{
solAssert(_toType.category() == Type::Category::Struct, "");
templ("copyToStorage", copyStructToStorageFunction(
dynamic_cast<StructType const&>(_fromType),
dynamic_cast<StructType const&>(_toType)
));
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
auto const& toStructType = dynamic_cast<StructType const&>(_toType);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset>value) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<?fromStorage> if eq(slot, value) { leave } </fromStorage>
<#member>
{
<updateMemberCall>
}
</member>
}
)");
templ("functionName", functionName);
templ("dynamicOffset", !_offset.has_value());
templ("panic", panicFunction(PanicCode::Generic));
templ("fromStorage", fromStructType.dataStoredIn(DataLocation::Storage));
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
MemberList::MemberMap toStructMembers = toStructType.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
Type const& memberType = *structMembers[i].type;
solAssert(memberType.memoryHeadSize() == 32, "");
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
Whiskers t(R"(
let memberSlot := add(slot, <memberStorageSlotDiff>)
let memberSrcPtr := add(value, <memberOffset>)
<?fromCalldata>
let <memberValues> :=
<?dynamicallyEncodedMember>
<accessCalldataTail>(value, memberSrcPtr)
<!dynamicallyEncodedMember>
memberSrcPtr
</dynamicallyEncodedMember>
<?isValueType>
<memberValues> := <read>(<memberValues>)
</isValueType>
</fromCalldata>
<?fromMemory>
let <memberValues> := <read>(memberSrcPtr)
</fromMemory>
<?fromStorage>
let <memberValues> :=
<?isValueType>
<read>(memberSrcPtr)
<!isValueType>
memberSrcPtr
</isValueType>
</fromStorage>
<updateStorageValue>(memberSlot, <memberValues>)
)");
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
t("fromCalldata", fromCalldata);
bool fromMemory = fromStructType.location() == DataLocation::Memory;
t("fromMemory", fromMemory);
bool fromStorage = fromStructType.location() == DataLocation::Storage;
t("fromStorage", fromStorage);
t("isValueType", memberType.isValueType());
t("memberValues", suffixedVariableNameList("memberValue_", 0, memberType.stackItems().size()));
t("memberStorageSlotDiff", slotDiff.str());
if (fromCalldata)
{
t("memberOffset", to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)));
t("dynamicallyEncodedMember", memberType.isDynamicallyEncoded());
if (memberType.isDynamicallyEncoded())
t("accessCalldataTail", accessCalldataTailFunction(memberType));
if (memberType.isValueType())
t("read", readFromCalldata(memberType));
}
else if (fromMemory)
{
t("memberOffset", fromStructType.memoryOffsetOfMember(structMembers[i].name).str());
t("read", readFromMemory(memberType));
}
else if (fromStorage)
{
auto [srcSlotOffset, srcOffset] = fromStructType.storageOffsetsOfMember(structMembers[i].name);
t("memberOffset", formatNumber(srcSlotOffset));
if (memberType.isValueType())
t("read", readFromStorageValueType(memberType, srcOffset, false));
else
solAssert(srcOffset == 0, "");
}
t("memberStorageSlotOffset", to_string(offset));
t("updateStorageValue", updateStorageValueFunction(
memberType,
*toStructMembers[i].type,
optional<unsigned>{offset}
));
memberParams[i]["updateMemberCall"] = t.render();
}
templ("member", memberParams);
return templ.render();
}
return templ.render();
});
}
@ -3355,6 +3254,122 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
});
}
string YulUtilFunctions::copyStructToStorageFunction(StructType const& _from, StructType const& _to)
{
solAssert(_to.dataStoredIn(DataLocation::Storage), "");
solAssert(_from.structDefinition() == _to.structDefinition(), "");
string functionName =
"copy_struct_to_storage_from_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(slot, value) {
<?fromStorage> if iszero(eq(slot, value)) { </fromStorage>
<#member>
{
<updateMemberCall>
}
</member>
<?fromStorage> } </fromStorage>
}
)");
templ("functionName", functionName);
templ("fromStorage", _from.dataStoredIn(DataLocation::Storage));
MemberList::MemberMap structMembers = _from.nativeMembers(nullptr);
MemberList::MemberMap toStructMembers = _to.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
Type const& memberType = *structMembers[i].type;
solAssert(memberType.memoryHeadSize() == 32, "");
auto const&[slotDiff, offset] = _to.storageOffsetsOfMember(structMembers[i].name);
Whiskers t(R"(
let memberSlot := add(slot, <memberStorageSlotDiff>)
let memberSrcPtr := add(value, <memberOffset>)
<?fromCalldata>
let <memberValues> :=
<?dynamicallyEncodedMember>
<accessCalldataTail>(value, memberSrcPtr)
<!dynamicallyEncodedMember>
memberSrcPtr
</dynamicallyEncodedMember>
<?isValueType>
<memberValues> := <read>(<memberValues>)
</isValueType>
</fromCalldata>
<?fromMemory>
let <memberValues> := <read>(memberSrcPtr)
</fromMemory>
<?fromStorage>
let <memberValues> :=
<?isValueType>
<read>(memberSrcPtr)
<!isValueType>
memberSrcPtr
</isValueType>
</fromStorage>
<updateStorageValue>(memberSlot, <memberValues>)
)");
bool fromCalldata = _from.location() == DataLocation::CallData;
t("fromCalldata", fromCalldata);
bool fromMemory = _from.location() == DataLocation::Memory;
t("fromMemory", fromMemory);
bool fromStorage = _from.location() == DataLocation::Storage;
t("fromStorage", fromStorage);
t("isValueType", memberType.isValueType());
t("memberValues", suffixedVariableNameList("memberValue_", 0, memberType.stackItems().size()));
t("memberStorageSlotDiff", slotDiff.str());
if (fromCalldata)
{
t("memberOffset", to_string(_from.calldataOffsetOfMember(structMembers[i].name)));
t("dynamicallyEncodedMember", memberType.isDynamicallyEncoded());
if (memberType.isDynamicallyEncoded())
t("accessCalldataTail", accessCalldataTailFunction(memberType));
if (memberType.isValueType())
t("read", readFromCalldata(memberType));
}
else if (fromMemory)
{
t("memberOffset", _from.memoryOffsetOfMember(structMembers[i].name).str());
t("read", readFromMemory(memberType));
}
else if (fromStorage)
{
auto[srcSlotOffset, srcOffset] = _from.storageOffsetsOfMember(structMembers[i].name);
t("memberOffset", formatNumber(srcSlotOffset));
if (memberType.isValueType())
t("read", readFromStorageValueType(memberType, srcOffset, false));
else
solAssert(srcOffset == 0, "");
}
t("memberStorageSlotOffset", to_string(offset));
t("updateStorageValue", updateStorageValueFunction(
memberType,
*toStructMembers[i].type,
optional<unsigned>{offset}
));
memberParams[i]["updateMemberCall"] = t.render();
}
templ("member", memberParams);
return templ.render();
});
}
string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayType const& _to)
{
solAssert(_to.location() != DataLocation::CallData, "");

View File

@ -212,8 +212,10 @@ public:
std::string storageArrayPopFunction(ArrayType const& _type);
/// @returns the name of a function that pushes an element to a storage array
/// @param _fromType represents the type of the element being pushed.
/// If _fromType is ReferenceType the function will perform deep copy.
/// signature: (array, value)
std::string storageArrayPushFunction(ArrayType const& _type);
std::string storageArrayPushFunction(ArrayType const& _type, TypePointer _fromType = nullptr);
/// @returns the name of a function that pushes the base type's zero element to a storage array and returns storage slot and offset of the added element.
/// signature: (array) -> slot, offset
@ -483,6 +485,10 @@ public:
std::string externalCodeFunction();
private:
/// @returns the name of a function that copies a struct from calldata or memory to storage
/// signature: (slot, value) ->
std::string copyStructToStorageFunction(StructType const& _from, StructType const& _to);
/// Special case of conversion functions - handles all array conversions.
std::string arrayConversionFunction(ArrayType const& _from, ArrayType const& _to);

View File

@ -1316,9 +1316,13 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
else
{
IRVariable argument = convert(*arguments.front(), *arrayType.baseType());
IRVariable argument =
arrayType.baseType()->isValueType() ?
convert(*arguments.front(), *arrayType.baseType()) :
*arguments.front();
m_code <<
m_utils.storageArrayPushFunction(arrayType) <<
m_utils.storageArrayPushFunction(arrayType, &argument.type()) <<
"(" <<
IRVariable(_functionCall.expression()).commaSeparatedList() <<
", " <<

View File

@ -0,0 +1,16 @@
contract C {
uint8 b = 23;
uint120[][] s;
uint8 a = 17;
function f(uint120[] calldata c) public returns(uint120) {
s.push(c);
assert(s.length == 1);
assert(s[0].length == c.length);
assert(s[0].length > 0);
return s[0][0];
}
}
// ====
// compileViaYul: also
// ----
// f(uint120[]): 0x20, 3, 1, 2, 3 -> 1

View File

@ -0,0 +1,19 @@
contract C {
uint8 b = 23;
uint120[][] s;
uint8 a = 17;
function f() public returns(uint120) {
delete s;
uint120[] memory m = new uint120[](3);
m[0] = 1;
s.push(m);
assert(s.length == 1);
assert(s[0].length == m.length);
assert(s[0].length > 0);
return s[0][0];
}
}
// ====
// compileViaYul: also
// ----
// f() -> 1

View File

@ -18,6 +18,7 @@ contract c {
return (data[0].a, data[0].b, data[0].c[2], data[0].d[2]);
}
}
// ====
// compileViaYul: also
// ----
// test() -> 2, 3, 4, 5

View File

@ -0,0 +1,20 @@
pragma abicoder v2;
contract c {
struct S {
uint16 a;
uint16 b;
uint16[3] c;
uint16[] d;
}
S[] data;
function test(S calldata c) public returns (uint16, uint16, uint16, uint16) {
data.push(c);
return (data[0].a, data[0].b, data[0].c[2], data[0].d[2]);
}
}
// ====
// compileViaYul: also
// ----
// test((uint16, uint16, uint16[3], uint16[])): 0x20, 2, 3, 0, 0, 4, 0xC0, 4, 0, 0, 5, 0, 0 -> 2, 3, 4, 5