Merge pull request #9738 from ethereum/arrayCopyingSol2Yul

[Sol->Yul] Implementing copying of arrays to storage
This commit is contained in:
chriseth 2020-10-14 12:22:09 +02:00 committed by GitHub
commit 56d6855222
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 464 additions and 44 deletions

View File

@ -918,7 +918,7 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
string functionName = "array_length_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(value) -> length {
function <functionName>(value<?dynamic><?calldata>, len</calldata></dynamic>) -> length {
<?dynamic>
<?memory>
length := mload(value)
@ -929,6 +929,9 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
length := <extractByteArrayLength>(length)
</byteArray>
</storage>
<?calldata>
length := len
</calldata>
<!dynamic>
length := <length>
</dynamic>
@ -940,17 +943,14 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
w("length", toCompactHexWithPrefix(_type.length()));
w("memory", _type.location() == DataLocation::Memory);
w("storage", _type.location() == DataLocation::Storage);
w("calldata", _type.location() == DataLocation::CallData);
if (_type.location() == DataLocation::Storage)
{
w("byteArray", _type.isByteArray());
if (_type.isByteArray())
w("extractByteArrayLength", extractByteArrayLengthFunction());
}
if (_type.isDynamicallySized())
solAssert(
_type.location() != DataLocation::CallData,
"called regular array length function on calldata array"
);
return w.render();
});
}
@ -1295,6 +1295,105 @@ string YulUtilFunctions::clearStorageStructFunction(StructType const& _type)
});
}
string YulUtilFunctions::copyArrayToStorage(ArrayType const& _fromType, ArrayType const& _toType)
{
solAssert(
*_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast<ReferenceType const&>(_toType),
""
);
solUnimplementedAssert(!_fromType.isByteArray(), "");
solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), "");
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"(
function <functionName>(slot, value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) {
let length := <arrayLength>(value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>)
<?isToDynamic>
<resizeArray>(slot, length)
</isToDynamic>
let srcPtr :=
<?isFromMemoryDynamic>
add(value, 0x20)
<!isFromMemoryDynamic>
value
</isFromMemoryDynamic>
let elementSlot := <dstDataLocation>(slot)
let elementOffset := 0
for { let i := 0 } lt(i, length) {i := add(i, 1)} {
<?fromCalldata>
let <elementValues> :=
<?dynamicallyEncodedBase>
<accessCalldataTail>(value, srcPtr)
<!dynamicallyEncodedBase>
srcPtr
</dynamicallyEncodedBase>
<?isValueType>
<elementValues> := <readFromCalldataOrMemory>(<elementValues>)
</isValueType>
</fromCalldata>
<?fromMemory>
let <elementValues> := <readFromCalldataOrMemory>(srcPtr)
</fromMemory>
<updateStorageValue>(elementSlot<?isValueType>, elementOffset</isValueType>, <elementValues>)
srcPtr := add(srcPtr, <stride>)
<?multipleItemsPerSlot>
elementOffset := add(elementOffset, <storageStride>)
if gt(elementOffset, sub(32, <storageStride>)) {
elementOffset := 0
elementSlot := add(elementSlot, 1)
}
<!multipleItemsPerSlot>
elementSlot := add(elementSlot, <storageSize>)
elementOffset := 0
</multipleItemsPerSlot>
}
}
)");
templ("functionName", functionName);
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata);
templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory));
templ("fromCalldata", fromCalldata);
templ("isToDynamic", _toType.isDynamicallySized());
templ("isFromMemoryDynamic", _fromType.isDynamicallySized() && _fromType.dataStoredIn(DataLocation::Memory));
if (fromCalldata)
{
templ("dynamicallySizedBase", _fromType.baseType()->isDynamicallySized());
templ("dynamicallyEncodedBase", _fromType.baseType()->isDynamicallyEncoded());
if (_fromType.baseType()->isDynamicallyEncoded())
templ("accessCalldataTail", accessCalldataTailFunction(*_fromType.baseType()));
}
if (_toType.isDynamicallySized())
templ("resizeArray", resizeDynamicArrayFunction(_toType));
templ("arrayLength",arrayLengthFunction(_fromType));
templ("isValueType", _fromType.baseType()->isValueType());
templ("dstDataLocation", arrayDataAreaFunction(_toType));
if (!fromCalldata || _fromType.baseType()->isValueType())
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata));
templ("elementValues", suffixedVariableNameList(
"elementValue_",
0,
_fromType.baseType()->stackItems().size()
));
templ("updateStorageValue", updateStorageValueFunction(*_fromType.baseType(), *_toType.baseType()));
templ("stride", to_string(fromCalldata ? _fromType.calldataStride() : _fromType.memoryStride()));
templ("multipleItemsPerSlot", _toType.storageStride() <= 16);
templ("storageStride", to_string(_toType.storageStride()));
templ("storageSize", _toType.baseType()->storageSize().str());
return templ.render();
});
}
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{
string functionName = "array_convert_length_to_size_" + _type.identifier();
@ -1865,23 +1964,39 @@ string YulUtilFunctions::updateStorageValueFunction(
else
{
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
solAssert(fromReferenceType && toReferenceType, "");
solAssert(*toReferenceType->copyForLocation(
fromReferenceType->location(),
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
solUnimplementedAssert(fromReferenceType->location() != DataLocation::Storage, "");
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
if (_toType.category() == Type::Category::Array)
solUnimplementedAssert(false, "");
{
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <value>) {
<copyArrayToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("copyArrayToStorage", copyArrayToStorage(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
return templ.render();
}
else if (_toType.category() == Type::Category::Struct)
{
solAssert(_fromType.category() == Type::Category::Struct, "");
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
auto const& toStructType = dynamic_cast<StructType const&>(_toType);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solAssert(fromStructType.location() != DataLocation::Storage, "");
solUnimplementedAssert(_offset.has_value() && _offset.value() == 0, "");
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, value) {
@ -1895,6 +2010,7 @@ string YulUtilFunctions::updateStorageValueFunction(
templ("functionName", functionName);
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)
@ -1902,31 +2018,65 @@ string YulUtilFunctions::updateStorageValueFunction(
solAssert(structMembers[i].type->memoryHeadSize() == 32, "");
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
memberParams[i]["updateMemberCall"] = Whiskers(R"(
let <memberValues> := <loadFromMemoryOrCalldata>(add(value, <memberOffset>))
<updateMember>(add(slot, <memberStorageSlotDiff>), <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
)")
("memberValues", suffixedVariableNameList(
Whiskers t(R"(
let memberSlot := add(slot, <memberStorageSlotDiff>)
<?fromCalldata>
<?dynamicallyEncodedMember>
let <memberCalldataOffset> := <accessCalldataTail>(value, add(value, <memberOffset>))
<!dynamicallyEncodedMember>
let <memberCalldataOffset> := add(value, <memberOffset>)
</dynamicallyEncodedMember>
<?isValueType>
let <memberValues> := <loadFromMemoryOrCalldata>(<memberCalldataOffset>)
<updateMember>(memberSlot, <memberStorageOffset>, <memberValues>)
<!isValueType>
<updateMember>(memberSlot, <memberCalldataOffset>)
</isValueType>
<!fromCalldata>
let memberMemoryOffset := add(value, <memberOffset>)
let <memberValues> := <loadFromMemoryOrCalldata>(memberMemoryOffset)
<updateMember>(memberSlot, <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
</fromCalldata>
)");
t("fromCalldata", fromCalldata);
if (fromCalldata)
{
t("memberCalldataOffset", suffixedVariableNameList(
"memberCalldataOffset_",
0,
structMembers[i].type->stackItems().size()
));
t("dynamicallyEncodedMember", structMembers[i].type->isDynamicallyEncoded());
if (structMembers[i].type->isDynamicallySized())
t("accessCalldataTail", accessCalldataTailFunction(*structMembers[i].type));
}
t("isValueType", structMembers[i].type->isValueType());
t("memberValues", suffixedVariableNameList(
"memberValue_",
0,
structMembers[i].type->stackItems().size()
))
("hasOffset", structMembers[i].type->isValueType())
(
));
t("hasOffset", structMembers[i].type->isValueType());
t(
"updateMember",
structMembers[i].type->isValueType() ?
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type) :
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type, offset)
)
("memberStorageSlotDiff", slotDiff.str())
("memberStorageOffset", to_string(offset))
("memberOffset",
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type) :
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type, offset)
);
t("memberStorageSlotDiff", slotDiff.str());
t("memberStorageOffset", to_string(offset));
t(
"memberOffset",
fromCalldata ?
to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) :
fromStructType.memoryOffsetOfMember(structMembers[i].name).str()
)
("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata))
.render();
);
if (!fromCalldata || structMembers[i].type->isValueType())
t("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata));
memberParams[i]["updateMemberCall"] = t.render();
}
templ("member", memberParams);

View File

@ -181,6 +181,10 @@ public:
/// signature: (slot) ->
std::string clearStorageArrayFunction(ArrayType const& _type);
/// @returns the name of a function that will copy array from calldata or memory to storage
/// signature (to_slot, from_ptr) ->
std::string copyArrayToStorage(ArrayType const& _fromType, ArrayType const& _toType);
/// Returns the name of a function that will convert a given length to the
/// size in memory (number of storage slots or calldata/memory bytes) it
/// will require.

View File

@ -387,7 +387,11 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
writeToLValue(*m_currentLValue, value);
if (m_currentLValue->type.category() != Type::Category::Struct && *_assignment.annotation().type != *TypeProvider::emptyTuple())
if (
m_currentLValue->type.category() != Type::Category::Struct &&
m_currentLValue->type.category() != Type::Category::Array &&
*_assignment.annotation().type != *TypeProvider::emptyTuple()
)
define(_assignment, value);
m_currentLValue.reset();

View File

@ -15,7 +15,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// EVMVersion: >homestead
// ----
// h(uint256[2][]) : 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324
// h(uint256[2][]): 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324
// i(uint256[2][2]): 123, 124, 223, 224 -> 32, 128, 123, 124, 223, 224

View File

@ -19,5 +19,7 @@ contract C {
return true;
}
}
// ====
// compileViaYul: also
// ----
// f() -> true

View File

@ -44,5 +44,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f() -> true

View File

@ -1,11 +0,0 @@
contract C {
uint[] a;
function f() public returns (uint, uint) {
uint[] memory b = new uint[](3);
b[0] = 1;
a = b;
return (a[0], a.length);
}
}
// ----
// f() -> 1, 3

View File

@ -10,6 +10,8 @@ contract Test {
return data;
}
}
// ====
// compileViaYul: also
// ----
// set(uint24[3][]): 0x20, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12 -> 0x06
// data(uint256,uint256): 0x02, 0x02 -> 0x09

View File

@ -14,6 +14,5 @@ contract c {
uint8(data[97]) == 97;
}
}
// ----
// test1() -> true

View File

@ -10,7 +10,6 @@ contract c {
bytes data;
}
// ----
// getLength() -> 0
// set(): 1, 2 -> true

View File

@ -7,6 +7,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// constructor(): 1, 2, 3 ->
// a(uint256): 0 -> 1

View File

@ -0,0 +1,17 @@
contract C {
uint256[] x;
function f() public returns(uint256) {
x.push(42); x.push(42); x.push(42); x.push(42);
uint256[] memory y = new uint256[](1);
y[0] = 23;
x = y;
assembly { sstore(x.slot, 4) }
assert(x[1] == 0);
assert(x[2] == 0);
return x[3];
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0

View File

@ -0,0 +1,46 @@
contract C {
uint128[] x;
uint64[] x1;
uint120[] x2;
function f() public returns(uint128) {
x.push(42); x.push(42); x.push(42); x.push(42);
uint128[] memory y = new uint128[](1);
y[0] = 23;
x = y;
assembly { sstore(x.slot, 4) }
assert(x[0] == 23);
assert(x[2] == 0);
assert(x[3] == 0);
return x[1];
}
function g() public returns(uint64) {
x1.push(42); x1.push(42); x1.push(42); x1.push(42);
uint64[] memory y = new uint64[](1);
y[0] = 23;
x1 = y;
assembly { sstore(x1.slot, 4) }
assert(x1[0] == 23);
assert(x1[2] == 0);
assert(x1[3] == 0);
return x1[1];
}
function h() public returns(uint120) {
x2.push(42); x2.push(42); x2.push(42); x2.push(42);
uint120[] memory y = new uint120[](1);
y[0] = 23;
x2 = y;
assembly { sstore(x2.slot, 4) }
assert(x2[0] == 23);
assert(x2[2] == 0);
assert(x2[3] == 0);
return x2[1];
}
}
// ====
// compileViaYul: true
// ----
// f() -> 0
// g() -> 0
// h() -> 0

View File

@ -0,0 +1,30 @@
contract C {
uint128[13] unused;
uint32[] a;
uint32[3] b;
function f() public returns (uint32, uint256) {
uint32[] memory m = new uint32[](3);
m[0] = 1;
m[1] = 2;
m[2] = 3;
a = m;
assert(a[0] == m[0]);
assert(a[1] == m[1]);
assert(a[2] == m[2]);
return (a[0], a.length);
}
function g() public returns (uint32, uint32, uint32) {
uint32[3] memory m;
m[0] = 1; m[1] = 2; m[2] = 3;
a = m;
b = m;
assert(a[0] == b[0] && a[1] == b[1] && a[2] == b[2]);
assert(a.length == b.length);
return (a[0], b[1], a[2]);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 1, 3
// g() -> 1, 2, 3

View File

@ -0,0 +1,16 @@
pragma experimental ABIEncoderV2;
contract c {
uint256[][] a;
function test(uint256[][] calldata d) external returns (uint256, uint256) {
a = d;
assert(a[0][0] == d[0][0]);
assert(a[0][1] == d[0][1]);
return (a.length, a[1][0] + a[1][1]);
}
}
// ====
// compileViaYul: true
// ----
// test(uint256[][]): 0x20, 2, 0x40, 0x40, 2, 23, 42 -> 2, 65

View File

@ -0,0 +1,45 @@
contract Test {
uint128[13] unused;
uint256[][] a;
uint256[4][] b;
uint256[2][3] c;
function test() external returns (uint256) {
uint256[][] memory m = new uint256[][](2);
m[0] = new uint256[](3);
m[0][0] = 7; m[0][1] = 8; m[0][2] = 9;
m[1] = new uint256[](4);
m[1][1] = 7; m[1][2] = 8; m[1][3] = 9;
a = m;
return a[0][0] + a[0][1] + a[1][3];
}
function test1() external returns (uint256) {
uint256[2][] memory m = new uint256[2][](1);
m[0][0] = 1; m[0][1] = 2;
b = m;
return b[0][0] + b[0][1];
}
function test2() external returns (uint256) {
uint256[2][2] memory m;
m[0][0] = 1; m[1][1] = 2; m[0][1] = 3;
c = m;
return c[0][0] + c[1][1] + c[0][1];
}
function test3() external returns (uint256) {
uint256[2][3] memory m;
m[0][0] = 7; m[1][0] = 8; m[2][0] = 9;
m[0][1] = 7; m[1][1] = 8; m[2][1] = 9;
a = m;
return a[0][0] + a[1][0] + a[2][1];
}
}
// ====
// compileViaYul: also
// ----
// test() -> 24
// test1() -> 3
// test2() -> 6
// test3() -> 24

View File

@ -0,0 +1,19 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint128 a;
uint64 b;
uint128 c;
}
uint128[137] unused;
S[] s;
function f(S[] calldata c) public returns (uint128, uint64, uint128) {
s = c;
return (s[2].a, s[1].b, s[0].c);
}
}
// ====
// compileViaYul: true
// ----
// f((uint128, uint64, uint128)[]): 0x20, 3, 0, 0, 12, 0, 11, 0, 10, 0, 0 -> 10, 11, 12

View File

@ -0,0 +1,21 @@
contract C {
struct S {
uint128 a;
uint64 b;
uint128 c;
}
uint128[137] unused;
S[] s;
function f() public returns (uint128, uint64, uint128) {
S[] memory m = new S[](3);
m[2].a = 10;
m[1].b = 11;
m[0].c = 12;
s = m;
return (s[2].a, s[1].b, s[0].c);
}
}
// ====
// compileViaYul: true
// ----
// f() -> 10, 11, 12

View File

@ -0,0 +1,18 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint256[] a;
}
S[] s;
function f(S[] calldata c) external returns (uint256, uint256) {
s = c;
return (s[1].a.length, s[1].a[0]);
}
}
// ====
// compileViaYul: true
// ----
// f((uint256[])[]): 0x20, 3, 0x60, 0x60, 0x60, 0x20, 3, 1, 2, 3 -> 3, 1

View File

@ -0,0 +1,28 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint136 p;
uint128[3] b;
uint128[] c;
}
S[] s;
function f() external returns (uint256, uint256, uint128, uint128) {
S[] memory m = new S[](3);
m[1] = S(0, [uint128(1), 2, 3], new uint128[](3));
m[1].c[0] = 1;
m[1].c[1] = 2;
m[1].c[2] = 3;
s = m;
assert(s.length == m.length);
assert(s[1].b[1] == m[1].b[1]);
assert(s[1].c[0] == m[1].c[0]);
return (s[1].b.length, s[1].c.length, s[1].b[2], s[1].c[0]);
}
}
// ====
// compileViaYul: true
// ----
// f() -> 3, 3, 3, 1

View File

@ -0,0 +1,13 @@
pragma experimental ABIEncoderV2;
contract C {
uint256[] s;
function f(uint256[] calldata data) external returns (uint) {
s = data;
return s[0];
}
}
// ====
// compileViaYul: also
// ----
// f(uint256[]): 0x20, 0x03, 0x1, 0x2, 0x3 -> 0x1

View File

@ -11,6 +11,8 @@ contract C {
return uint256(uint8(data[0][4]));
}
}
// ====
// compileViaYul: also
// ----
// f(bytes32): "789" -> "9"
// g(bytes32): "789" -> 0x35

View File

@ -11,5 +11,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f() -> 1, 2, 3, 4, 5

View File

@ -8,6 +8,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// constructor(): 1, 2, 3, 4 ->
// a() -> 1

View File

@ -22,6 +22,8 @@ contract test {
return ret;
}
}
// ====
// compileViaYul: also
// ----
// f(bool): true -> 1
// f(bool): false -> 2

View File

@ -22,6 +22,8 @@ contract test {
multiple_map[2][1][2].finalArray[3] = 5;
}
}
// ====
// compileViaYul: also
// ----
// data(uint256): 0 -> 8
// data(uint256): 8 -> FAILURE

View File

@ -19,6 +19,8 @@ contract Test {
return z;
}
}
// ====
// compileViaYul: also
// ----
// x(uint256): 0 -> -1
// x(uint256): 1 -> -2

View File

@ -26,5 +26,7 @@ contract C {
x2 = s.s3.x2;
}
}
// ====
// compileViaYul: also
// ----
// get() -> 0x01, 0x00, 0x09, 0x00, 0x04, 0x05

View File

@ -8,7 +8,6 @@ contract C {
bytes savedData;
}
// ----
// save() -> 24 # empty copy loop #
// save(): "abcdefg" -> 24