mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[Sol->Yul] Implementing dynamic array push for arrays of structs.
This commit is contained in:
parent
b06936b11c
commit
85b8325f0b
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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() <<
|
||||
", " <<
|
||||
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user