[Sol->Yul] Implementing dynamic array push for arrays of structs.

This commit is contained in:
Djordje Mijovic 2020-12-10 10:13:16 +01:00 committed by chriseth
parent b06936b11c
commit 85b8325f0b
7 changed files with 104 additions and 50 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,49 +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(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset>value) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<copyStructToStorage>(slot, value)
}
)");
templ("functionName", functionName);
templ("dynamicOffset", !_offset.has_value());
templ("panic", panicFunction(util::PanicCode::Generic));
templ("copyStructToStorage", copyStructToStorageFunction(fromStructType, toStructType));
return templ.render();
}
return templ.render();
});
}
@ -3276,16 +3268,16 @@ string YulUtilFunctions::copyStructToStorageFunction(StructType const& _from, St
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(slot, value) {
<?fromStorage> if eq(slot, value) { leave } </fromStorage>
<?fromStorage> if iszero(eq(slot, value)) { </fromStorage>
<#member>
{
<updateMemberCall>
}
</member>
<?fromStorage> } </fromStorage>
}
)");
templ("functionName", functionName);
templ("panic", panicFunction());
templ("fromStorage", _from.dataStoredIn(DataLocation::Storage));
MemberList::MemberMap structMembers = _from.nativeMembers(nullptr);

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,7 +485,7 @@ public:
std::string externalCodeFunction();
private:
/// @returns function that copies struct to storage
/// @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);

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