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.location() == DataLocation::Storage, "");
|
||||||
solAssert(_type.isDynamicallySized(), "");
|
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 m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array, value) {
|
function <functionName>(array <values>) {
|
||||||
<?isByteArray>
|
<?isByteArray>
|
||||||
let data := sload(array)
|
let data := sload(array)
|
||||||
let oldLen := <extractByteArrayLength>(data)
|
let oldLen := <extractByteArrayLength>(data)
|
||||||
@ -1441,7 +1449,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
|
|||||||
|
|
||||||
switch gt(oldLen, 31)
|
switch gt(oldLen, 31)
|
||||||
case 0 {
|
case 0 {
|
||||||
value := byte(0, value)
|
let value := byte(0 <values>)
|
||||||
switch oldLen
|
switch oldLen
|
||||||
case 31 {
|
case 31 {
|
||||||
// Here we have special case when array switches from short array to long array
|
// 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 {
|
default {
|
||||||
sstore(array, add(data, 2))
|
sstore(array, add(data, 2))
|
||||||
let slot, offset := <indexAccess>(array, oldLen)
|
let slot, offset := <indexAccess>(array, oldLen)
|
||||||
<storeValue>(slot, offset, value)
|
<storeValue>(slot, offset <values>)
|
||||||
}
|
}
|
||||||
<!isByteArray>
|
<!isByteArray>
|
||||||
let oldLen := sload(array)
|
let oldLen := sload(array)
|
||||||
if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() }
|
if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() }
|
||||||
sstore(array, add(oldLen, 1))
|
sstore(array, add(oldLen, 1))
|
||||||
let slot, offset := <indexAccess>(array, oldLen)
|
let slot, offset := <indexAccess>(array, oldLen)
|
||||||
<storeValue>(slot, offset, value)
|
<storeValue>(slot, offset <values>)
|
||||||
</isByteArray>
|
</isByteArray>
|
||||||
})")
|
})")
|
||||||
("functionName", functionName)
|
("functionName", functionName)
|
||||||
|
("values", _fromType->sizeOnStack() == 0 ? "" : ", " + suffixedVariableNameList("value", 0, _fromType->sizeOnStack()))
|
||||||
("panic", panicFunction(PanicCode::ResourceError))
|
("panic", panicFunction(PanicCode::ResourceError))
|
||||||
("extractByteArrayLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "")
|
("extractByteArrayLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "")
|
||||||
("dataAreaFunction", arrayDataAreaFunction(_type))
|
("dataAreaFunction", arrayDataAreaFunction(_type))
|
||||||
("isByteArray", _type.isByteArray())
|
("isByteArray", _type.isByteArray())
|
||||||
("indexAccess", storageArrayIndexAccessFunction(_type))
|
("indexAccess", storageArrayIndexAccessFunction(_type))
|
||||||
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
|
("storeValue", updateStorageValueFunction(*_fromType, *_type.baseType()))
|
||||||
("maxArrayLength", (u256(1) << 64).str())
|
("maxArrayLength", (u256(1) << 64).str())
|
||||||
("shl", shiftLeftFunctionDynamic())
|
("shl", shiftLeftFunctionDynamic())
|
||||||
("shr", shiftRightFunction(248))
|
("shr", shiftRightFunction(248))
|
||||||
@ -2572,49 +2581,32 @@ string YulUtilFunctions::updateStorageValueFunction(
|
|||||||
fromReferenceType->location(),
|
fromReferenceType->location(),
|
||||||
fromReferenceType->isPointer()
|
fromReferenceType->isPointer()
|
||||||
).get() == *fromReferenceType, "");
|
).get() == *fromReferenceType, "");
|
||||||
|
|
||||||
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
|
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
|
||||||
|
solAssert(_offset.value_or(0) == 0, "");
|
||||||
|
|
||||||
if (_toType.category() == Type::Category::Array)
|
Whiskers templ(R"(
|
||||||
{
|
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) {
|
||||||
solAssert(_offset.value_or(0) == 0, "");
|
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||||
|
<copyToStorage>(slot, <value>)
|
||||||
Whiskers templ(R"(
|
}
|
||||||
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) {
|
)");
|
||||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
templ("functionName", functionName);
|
||||||
<copyArrayToStorage>(slot, <value>)
|
templ("dynamicOffset", !_offset.has_value());
|
||||||
}
|
templ("panic", panicFunction(PanicCode::Generic));
|
||||||
)");
|
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
|
||||||
templ("functionName", functionName);
|
if (_fromType.category() == Type::Category::Array)
|
||||||
templ("dynamicOffset", !_offset.has_value());
|
templ("copyToStorage", copyArrayToStorageFunction(
|
||||||
templ("panic", panicFunction(PanicCode::Generic));
|
|
||||||
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
|
|
||||||
templ("copyArrayToStorage", copyArrayToStorageFunction(
|
|
||||||
dynamic_cast<ArrayType const&>(_fromType),
|
dynamic_cast<ArrayType const&>(_fromType),
|
||||||
dynamic_cast<ArrayType const&>(_toType)
|
dynamic_cast<ArrayType const&>(_toType)
|
||||||
));
|
));
|
||||||
|
|
||||||
return templ.render();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
templ("copyToStorage", copyStructToStorageFunction(
|
||||||
solAssert(_toType.category() == Type::Category::Struct, "");
|
dynamic_cast<StructType const&>(_fromType),
|
||||||
|
dynamic_cast<StructType const&>(_toType)
|
||||||
|
));
|
||||||
|
|
||||||
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
|
return templ.render();
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3276,16 +3268,16 @@ string YulUtilFunctions::copyStructToStorageFunction(StructType const& _from, St
|
|||||||
return m_functionCollector.createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(slot, value) {
|
function <functionName>(slot, value) {
|
||||||
<?fromStorage> if eq(slot, value) { leave } </fromStorage>
|
<?fromStorage> if iszero(eq(slot, value)) { </fromStorage>
|
||||||
<#member>
|
<#member>
|
||||||
{
|
{
|
||||||
<updateMemberCall>
|
<updateMemberCall>
|
||||||
}
|
}
|
||||||
</member>
|
</member>
|
||||||
|
<?fromStorage> } </fromStorage>
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
templ("functionName", functionName);
|
templ("functionName", functionName);
|
||||||
templ("panic", panicFunction());
|
|
||||||
templ("fromStorage", _from.dataStoredIn(DataLocation::Storage));
|
templ("fromStorage", _from.dataStoredIn(DataLocation::Storage));
|
||||||
|
|
||||||
MemberList::MemberMap structMembers = _from.nativeMembers(nullptr);
|
MemberList::MemberMap structMembers = _from.nativeMembers(nullptr);
|
||||||
|
@ -212,8 +212,10 @@ public:
|
|||||||
std::string storageArrayPopFunction(ArrayType const& _type);
|
std::string storageArrayPopFunction(ArrayType const& _type);
|
||||||
|
|
||||||
/// @returns the name of a function that pushes an element to a storage array
|
/// @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)
|
/// 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.
|
/// @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
|
/// signature: (array) -> slot, offset
|
||||||
@ -483,7 +485,7 @@ public:
|
|||||||
std::string externalCodeFunction();
|
std::string externalCodeFunction();
|
||||||
|
|
||||||
private:
|
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) ->
|
/// signature: (slot, value) ->
|
||||||
std::string copyStructToStorageFunction(StructType const& _from, StructType const& _to);
|
std::string copyStructToStorageFunction(StructType const& _from, StructType const& _to);
|
||||||
|
|
||||||
|
@ -1316,9 +1316,13 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IRVariable argument = convert(*arguments.front(), *arrayType.baseType());
|
IRVariable argument =
|
||||||
|
arrayType.baseType()->isValueType() ?
|
||||||
|
convert(*arguments.front(), *arrayType.baseType()) :
|
||||||
|
*arguments.front();
|
||||||
|
|
||||||
m_code <<
|
m_code <<
|
||||||
m_utils.storageArrayPushFunction(arrayType) <<
|
m_utils.storageArrayPushFunction(arrayType, &argument.type()) <<
|
||||||
"(" <<
|
"(" <<
|
||||||
IRVariable(_functionCall.expression()).commaSeparatedList() <<
|
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]);
|
return (data[0].a, data[0].b, data[0].c[2], data[0].d[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
// ----
|
// ----
|
||||||
// test() -> 2, 3, 4, 5
|
// 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