Merge pull request #10211 from ethereum/copyArrayCalldata2MemSol2Yul

[Sol->Yul] Copying arrays from calldata to memory
This commit is contained in:
chriseth 2020-11-23 15:02:18 +01:00 committed by GitHub
commit f313668ef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 278 additions and 126 deletions

View File

@ -1079,8 +1079,6 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo
solAssert(!_fromMemory, "");
return abiDecodingFunctionCalldataArray(*arrayType);
}
else if (arrayType->isByteArray())
return abiDecodingFunctionByteArray(*arrayType, _fromMemory);
else
return abiDecodingFunctionArray(*arrayType, _fromMemory);
}
@ -1133,36 +1131,21 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM
string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
solAssert(!_type.isByteArray(), "");
string functionName =
"abi_decode_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "");
solAssert(!_type.dataStoredIn(DataLocation::Storage), "");
return createFunction(functionName, [&]() {
string load = _fromMemory ? "mload" : "calldataload";
bool dynamicBase = _type.baseType()->isDynamicallyEncoded();
Whiskers templ(
R"(
// <readableTypeName>
function <functionName>(offset, end) -> array {
if iszero(slt(add(offset, 0x1f), end)) { <revertString> }
let length := <retrieveLength>
array := <allocate>(<allocationSize>(length))
let dst := array
<storeLength> // might update offset and dst
let src := offset
<staticBoundsCheck>
for { let i := 0 } lt(i, length) { i := add(i, 1) }
{
let elementPos := <retrieveElementPos>
mstore(dst, <decodingFun>(elementPos, end))
dst := add(dst, 0x20)
src := add(src, <stride>)
}
array := <abiDecodeAvailableLen>(<offset>, length, end)
}
)"
);
@ -1170,18 +1153,56 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
templ("revertString", revertReasonIfDebug("ABI decoding: invalid calldata array offset"));
templ("functionName", functionName);
templ("readableTypeName", _type.toString(true));
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
templ("retrieveLength", _type.isDynamicallySized() ? (load + "(offset)") : toCompactHexWithPrefix(_type.length()));
templ("offset", _type.isDynamicallySized() ? "add(offset, 0x20)" : "offset");
templ("abiDecodeAvailableLen", abiDecodingFunctionArrayAvailableLength(_type, _fromMemory));
return templ.render();
});
}
string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _type, bool _fromMemory)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
if (_type.isByteArray())
return abiDecodingFunctionByteArrayAvailableLength(_type, _fromMemory);
string functionName =
"abi_decode_available_length_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "");
return createFunction(functionName, [&]() {
Whiskers templ(R"(
// <readableTypeName>
function <functionName>(offset, length, end) -> array {
array := <allocate>(<allocationSize>(length))
let dst := array
<storeLength>
let src := offset
<staticBoundsCheck>
for { let i := 0 } lt(i, length) { i := add(i, 1) }
{
let elementPos := <retrieveElementPos>
mstore(dst, <decodingFun>(elementPos, end))
dst := add(dst, 0x20)
src := add(src, <stride>)
}
}
)");
templ("functionName", functionName);
templ("readableTypeName", _type.toString(true));
templ("allocate", m_utils.allocationFunction());
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
string calldataStride = toCompactHexWithPrefix(_type.calldataStride());
templ("stride", calldataStride);
if (_type.isDynamicallySized())
templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)");
templ("storeLength", "mstore(array, length) dst := add(array, 0x20)");
else
templ("storeLength", "");
if (dynamicBase)
if (_type.baseType()->isDynamicallyEncoded())
{
templ("staticBoundsCheck", "");
string load = _fromMemory ? "mload" : "calldataload";
templ("retrieveElementPos", "add(offset, " + load + "(src))");
}
else
@ -1245,36 +1266,28 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
});
}
string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory)
string ABIFunctions::abiDecodingFunctionByteArrayAvailableLength(ArrayType const& _type, bool _fromMemory)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
solAssert(_type.isByteArray(), "");
string functionName =
"abi_decode_" +
"abi_decode_available_length_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "");
return createFunction(functionName, [&]() {
Whiskers templ(
R"(
function <functionName>(offset, end) -> array {
if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
let length := <load>(offset)
array := <allocate>(<allocationSize>(length))
mstore(array, length)
let src := add(offset, 0x20)
let dst := add(array, 0x20)
if gt(add(src, length), end) { <revertStringLength> }
<copyToMemFun>(src, dst, length)
}
)"
);
// TODO add test
templ("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid byte array offset"));
Whiskers templ(R"(
function <functionName>(src, length, end) -> array {
array := <allocate>(<allocationSize>(length))
mstore(array, length)
let dst := add(array, 0x20)
if gt(add(src, length), end) { <revertStringLength> }
<copyToMemFun>(src, dst, length)
}
)");
templ("revertStringLength", revertReasonIfDebug("ABI decoding: invalid byte array length"));
templ("functionName", functionName);
templ("load", _fromMemory ? "mload" : "calldataload");
templ("allocate", m_utils.allocationFunction());
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory));

View File

@ -165,6 +165,11 @@ public:
EncodingOptions const& _options
);
/// Decodes array in case of dynamic arrays with offset pointing to
/// data and length already on stack
/// signature: (dataOffset, length, dataEnd) -> decodedArray
std::string abiDecodingFunctionArrayAvailableLength(ArrayType const& _type, bool _fromMemory);
private:
/// Part of @a abiEncodingFunction for array target type and given calldata array.
/// Uses calldatacopy and does not perform cleanup or validation and can therefore only
@ -234,15 +239,14 @@ private:
std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory);
/// Part of @a abiDecodingFunction for calldata array types.
std::string abiDecodingFunctionCalldataArray(ArrayType const& _type);
/// Part of @a abiDecodingFunction for byte array types.
std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory);
/// Part of @a abiDecodingFunctionArrayWithAvailableLength
std::string abiDecodingFunctionByteArrayAvailableLength(ArrayType const& _type, bool _fromMemory);
/// Part of @a abiDecodingFunction for calldata struct types.
std::string abiDecodingFunctionCalldataStruct(StructType const& _type);
/// Part of @a abiDecodingFunction for struct types.
std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory);
/// Part of @a abiDecodingFunction for array types.
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
/// Part of @a abiDecodingFunction for struct types.
std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory);
/// @returns the name of a function that retrieves an element from calldata.
std::string calldataAccessFunction(Type const& _type);

View File

@ -3090,7 +3090,8 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayType const& _to)
{
solUnimplementedAssert(_to.location() != DataLocation::CallData, "Conversion of calldata types not yet implemented.");
solAssert(_to.location() != DataLocation::CallData, "");
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
if (_to.location() == DataLocation::Storage)
solAssert(
@ -3098,12 +3099,6 @@ string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayTy
_from.location() == DataLocation::Storage,
"Invalid conversion to storage type."
);
if (_to.location() == DataLocation::Memory && _from.location() == DataLocation::CallData)
{
solUnimplementedAssert(_from.isDynamicallySized(), "");
solUnimplementedAssert(!_from.baseType()->isDynamicallyEncoded(), "");
solUnimplementedAssert(_from.isByteArray() && _to.isByteArray() && _to.isDynamicallySized(), "");
}
string functionName =
"convert_array_" +
@ -3131,19 +3126,31 @@ string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayTy
"body",
Whiskers(R"(
// Copy the array to a free position in memory
converted :=
<?fromStorage>
converted := <arrayStorageToMem>(value)
<arrayStorageToMem>(value)
</fromStorage>
<?fromCalldata>
converted := <allocateMemoryArray>(length)
<copyToMemory>(value, add(converted, 0x20), length)
<abiDecode>(value, <length>, calldatasize())
</fromCalldata>
)")
("fromStorage", _from.dataStoredIn(DataLocation::Storage))
("fromCalldata", _from.dataStoredIn(DataLocation::CallData))
("allocateMemoryArray", _from.dataStoredIn(DataLocation::CallData) ? allocateMemoryArrayFunction(_to) : "")
("copyToMemory", _from.dataStoredIn(DataLocation::CallData) ? copyToMemoryFunction(true) : "")
("arrayStorageToMem", _from.dataStoredIn(DataLocation::Storage) ? copyArrayFromStorageToMemoryFunction(_from, _to) : "")
("length", _from.isDynamicallySized() ? "length" : _from.length().str())
(
"abiDecode",
_from.dataStoredIn(DataLocation::CallData) ?
ABIFunctions(
m_evmVersion,
m_revertStrings,
m_functionCollector
).abiDecodingFunctionArrayAvailableLength(_to, false) :
""
)
(
"arrayStorageToMem",
_from.dataStoredIn(DataLocation::Storage) ? copyArrayFromStorageToMemoryFunction(_from, _to) : ""
)
.render()
);
else

View File

@ -19,52 +19,49 @@ object "C_56" {
object "C_56_deployed" {
code {
{
mstore(64, 128)
let _1 := 64
mstore(_1, 128)
if iszero(lt(calldatasize(), 4))
{
let _1 := 0
if eq(0xf8eddcc6, shr(224, calldataload(_1)))
let _2 := 0
if eq(0xf8eddcc6, shr(224, calldataload(_2)))
{
if callvalue() { revert(_1, _1) }
let _2 := 32
if slt(add(calldatasize(), not(3)), _2) { revert(_1, _1) }
if callvalue() { revert(_2, _2) }
let _3 := 32
if slt(add(calldatasize(), not(3)), _3) { revert(_2, _2) }
let offset := calldataload(4)
let _3 := 0xffffffffffffffff
if gt(offset, _3) { revert(_1, _1) }
if iszero(slt(add(offset, 35), calldatasize())) { revert(_1, _1) }
let length := calldataload(add(4, offset))
if gt(length, _3) { invalid() }
let _4 := mul(length, _2)
let dst := allocateMemory(add(_4, _2))
let _4 := 0xffffffffffffffff
if gt(offset, _4) { revert(_2, _2) }
if iszero(slt(add(offset, 35), calldatasize())) { revert(_2, _2) }
let _5 := calldataload(add(4, offset))
if gt(_5, _4) { invalid() }
let _6 := mul(_5, _3)
let dst := allocateMemory(add(_6, _3))
let dst_1 := dst
mstore(dst, length)
dst := add(dst, _2)
mstore(dst, _5)
dst := add(dst, _3)
let src := add(offset, 36)
if gt(add(add(offset, _4), 36), calldatasize()) { revert(_1, _1) }
let i := _1
for { } lt(i, length) { i := add(i, 1) }
if gt(add(add(offset, _6), 36), calldatasize()) { revert(_2, _2) }
let i := _2
for { } lt(i, _5) { i := add(i, 1) }
{
mstore(dst, abi_decode_t_struct$_S(src, calldatasize()))
dst := add(dst, _2)
src := add(src, _2)
if slt(sub(calldatasize(), src), _3) { revert(_2, _2) }
let memPtr := mload(_1)
let newFreePtr := add(memPtr, _3)
if or(gt(newFreePtr, _4), lt(newFreePtr, memPtr)) { invalid() }
mstore(_1, newFreePtr)
mstore(memPtr, calldataload(src))
mstore(dst, memPtr)
dst := add(dst, _3)
src := add(src, _3)
}
let ret, ret_1 := fun_sumArray_55(dst_1)
let memPos := allocateMemory(_1)
let memPos := allocateMemory(_2)
return(memPos, sub(abi_encode_uint256_t_string(memPos, ret, ret_1), memPos))
}
}
revert(0, 0)
}
function abi_decode_t_struct$_S(headStart, end) -> value
{
if slt(sub(end, headStart), 0x20) { revert(value, value) }
let memPtr := mload(64)
let newFreePtr := add(memPtr, 0x20)
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { invalid() }
mstore(64, newFreePtr)
value := memPtr
mstore(memPtr, calldataload(headStart))
}
function abi_encode_uint256_t_string(headStart, value0, value1) -> tail
{
mstore(headStart, value0)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,9 +14,9 @@ contract C {
}
// ----
// creation:
// codeDepositCost: 1107400
// executionCost: 1154
// totalCost: 1108554
// codeDepositCost: 1164600
// executionCost: 1207
// totalCost: 1165807
// external:
// a(): 1130
// b(uint256): infinite

View File

@ -17,9 +17,9 @@ contract C {
// optimize-yul: true
// ----
// creation:
// codeDepositCost: 605000
// executionCost: 638
// totalCost: 605638
// codeDepositCost: 578200
// executionCost: 613
// totalCost: 578813
// external:
// a(): 1029
// b(uint256): 2084

View File

@ -15,6 +15,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// EVMVersion: >homestead
// ----
// f(uint256[][1]): 32, 32, 0 -> true

View File

@ -0,0 +1,38 @@
pragma experimental ABIEncoderV2;
contract c {
function test1(uint256[][] calldata c) external returns (uint256, uint256) {
uint256[][] memory a1 = c;
assert(a1[0][0] == c[0][0]);
assert(a1[0][1] == c[0][1]);
return (a1.length, a1[0][0] + a1[1][1]);
}
function test2(uint256[][2] calldata c) external returns (uint256, uint256) {
uint256[][2] memory a2 = c;
assert(a2[0][0] == c[0][0]);
assert(a2[0][1] == c[0][1]);
return (a2[0].length, a2[0][0] + a2[1][1]);
}
function test3(uint256[2][] calldata c) external returns (uint256, uint256) {
uint256[2][] memory a3 = c;
assert(a3[0][0] == c[0][0]);
assert(a3[0][1] == c[0][1]);
return (a3.length, a3[0][0] + a3[1][1]);
}
function test4(uint256[2][2] calldata c) external returns (uint256) {
uint256[2][2] memory a4 = c;
assert(a4[0][0] == c[0][0]);
assert(a4[0][1] == c[0][1]);
return (a4[0][0] + a4[1][1]);
}
}
// ====
// compileViaYul: true
// ----
// test1(uint256[][]): 0x20, 2, 0x40, 0x40, 2, 23, 42 -> 2, 65
// test2(uint256[][2]): 0x20, 0x40, 0x40, 2, 23, 42 -> 2, 65
// test3(uint256[2][]): 0x20, 2, 23, 42, 23, 42 -> 2, 65
// test4(uint256[2][2]): 23, 42, 23, 42 -> 65

View File

@ -10,28 +10,28 @@ contract c {
a1 = c;
assert(a1[0][0] == c[0][0]);
assert(a1[0][1] == c[0][1]);
return (a1.length, a1[1][0] + a1[1][1]);
return (a1.length, a1[0][0] + a1[1][1]);
}
function test2(uint256[][2] calldata c) external returns (uint256, uint256) {
a2 = c;
assert(a2[0][0] == c[0][0]);
assert(a2[0][1] == c[0][1]);
return (a2[0].length, a2[1][0] + a2[1][1]);
return (a2[0].length, a2[0][0] + a2[1][1]);
}
function test3(uint256[2][] calldata c) external returns (uint256, uint256) {
a3 = c;
assert(a3[0][0] == c[0][0]);
assert(a3[0][1] == c[0][1]);
return (a3.length, a3[1][0] + a3[1][1]);
return (a3.length, a3[0][0] + a3[1][1]);
}
function test4(uint256[2][2] calldata c) external returns (uint256) {
a4 = c;
assert(a4[0][0] == c[0][0]);
assert(a4[0][1] == c[0][1]);
return (a4[1][0] + a4[1][1]);
return (a4[0][0] + a4[1][1]);
}
}
// ====
@ -39,5 +39,5 @@ contract c {
// ----
// test1(uint256[][]): 0x20, 2, 0x40, 0x40, 2, 23, 42 -> 2, 65
// test2(uint256[][2]): 0x20, 0x40, 0x40, 2, 23, 42 -> 2, 65
// test3(uint256[2][]): 0x20, 2, 0x40, 0x40, 23, 42 -> 2, 65
// test3(uint256[2][]): 0x20, 2, 23, 42, 23, 42 -> 2, 65
// test4(uint256[2][2]): 23, 42, 23, 42 -> 65

View File

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

View File

@ -0,0 +1,23 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint256[] a;
}
function f(S[] calldata c) external returns (uint256, uint256) {
S[] memory s = c;
assert(s.length == c.length);
for (uint i = 0; i < s.length; i++) {
assert(s[i].a.length == c[i].a.length);
for (uint j = 0; j < s[i].a.length; j++) {
assert(s[i].a[j] == c[i].a[j]);
}
}
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

@ -9,6 +9,13 @@ contract C {
function f(S[] calldata c) external returns (uint256, uint256) {
s = c;
assert(s.length == c.length);
for (uint i = 0; i < s.length; i++) {
assert(s[i].a.length == c[i].a.length);
for (uint j = 0; j < s[i].a.length; j++) {
assert(s[i].a[j] == c[i].a[j]);
}
}
return (s[1].a.length, s[1].a[0]);
}
}

View File

@ -21,5 +21,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f((uint256,uint256)[]): 0x20, 0x2, 0x1, 0x2, 0x3, 0x4 -> 2, 1, 2, 3, 4

View File

@ -0,0 +1,10 @@
contract C {
function f(uint256[2] calldata c) public returns (uint256, uint256) {
uint256[2] memory m1 = c;
return (m1[0], m1[1]);
}
}
// ====
// compileViaYul: also
// ----
// f(uint256[2]): 43, 57 -> 43, 57

View File

@ -11,5 +11,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f(uint256[][]): 0x20, 0x1, 0x20, 0x2, 0x17, 0x2a -> 0x1, 0x40, 0x2, 0x17, 0x2a

View File

@ -4,5 +4,7 @@ contract C {
r = x;
}
}
// ====
// compileViaYul: also
// ----
// f(uint256[2][]): 0x0, 1, 8, 7, 6, 5 -> 0x20, 2, 8, 7, 6, 5

View File

@ -0,0 +1,23 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint256 a;
uint256[2] b;
uint256 c;
}
function f(S calldata c)
external
pure
returns (uint256, uint256, uint256, uint256)
{
S memory m = c;
return (m.a, m.b[0], m.b[1], m.c);
}
}
// ====
// compileViaYul: also
// ----
// f((uint256,uint256[2],uint256)): 42, 1, 2, 23 -> 42, 1, 2, 23