Implement calldata arrays for Yul IR

This commit is contained in:
Daniel Kirchner 2020-02-11 17:43:43 +01:00
parent b580a7a35d
commit df0873d138
7 changed files with 178 additions and 7 deletions

View File

@ -908,6 +908,58 @@ string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "calldata_array_index_access_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() }
addr := add(base_ref, mul(index, <stride>))
<?dynamicallyEncodedBase>
addr<?dynamicallySizedBase>, len</dynamicallySizedBase> := <accessCalldataTail>(base_ref, addr)
</dynamicallyEncodedBase>
}
)")
("functionName", functionName)
("stride", to_string(_type.calldataStride()))
("dynamicallySized", _type.isDynamicallySized())
("dynamicallyEncodedBase", _type.baseType()->isDynamicallyEncoded())
("dynamicallySizedBase", _type.baseType()->isDynamicallySized())
("arrayLen", toCompactHexWithPrefix(_type.length()))
("accessCalldataTail", _type.baseType()->isDynamicallyEncoded() ? accessCalldataTailFunction(*_type.baseType()): "")
.render();
});
}
string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
{
solAssert(_type.isDynamicallyEncoded(), "");
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "access_calldata_tail_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
addr := add(base_ref, rel_offset_of_tail)
<?dynamicallySized>
length := calldataload(addr)
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
addr := add(addr, 32)
</dynamicallySized>
}
)")
("functionName", functionName)
("dynamicallySized", _type.isDynamicallySized())
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0))
.render();
});
}
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
{
solAssert(!_type.isByteArray(), "");
@ -1932,7 +1984,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
function <functionName>(memPtr) -> value {
value := <load>(memPtr)
<?needsValidation>
value := <validate>(value)
<validate>(value)
</needsValidation>
}
)")

View File

@ -172,9 +172,14 @@ public:
/// @returns the name of a function that returns the calldata address for the
/// given array base ref and index.
/// signature: (baseRef, index) -> address
/// signature: (baseRef, index) -> offset[, length]
std::string calldataArrayIndexAccessFunction(ArrayType const& _type);
/// @returns the name of a function that follows a calldata tail while performing
/// bounds checks.
/// signature: (baseRef, tailPointer) -> offset[, length]
std::string accessCalldataTailFunction(Type const& _type);
/// @returns the name of a function that advances an array data pointer to the next element.
/// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots.
std::string nextArrayElementFunction(ArrayType const& _type);

View File

@ -815,7 +815,12 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
else if (member == "gasprice")
define(_memberAccess) << "gasprice()\n";
else if (member == "data")
solUnimplementedAssert(false, "");
{
IRVariable var(_memberAccess);
declare(var);
define(var.part("offset")) << "0\n";
define(var.part("length")) << "calldatasize()\n";
}
else if (member == "sig")
define(_memberAccess) <<
"and(calldataload(0), " <<
@ -862,8 +867,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
switch (type.location())
{
case DataLocation::CallData:
solUnimplementedAssert(false, "");
//m_context << Instruction::SWAP1 << Instruction::POP;
define(_memberAccess, IRVariable(_memberAccess.expression()).part("length"));
break;
case DataLocation::Storage:
{
@ -997,8 +1001,29 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
}
case DataLocation::CallData:
{
solUnimplemented("calldata not yet implemented!");
IRVariable var(m_context.newYulVariable(), *arrayType.baseType());
define(var) <<
m_utils.calldataArrayIndexAccessFunction(arrayType) <<
"(" <<
IRVariable(_indexAccess.baseExpression()).commaSeparatedList() <<
", " <<
expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) <<
")\n";
if (arrayType.isByteArray())
define(_indexAccess) <<
m_utils.cleanupFunction(*arrayType.baseType()) <<
"(calldataload(" <<
var.name() <<
"))\n";
else if (arrayType.baseType()->isValueType())
define(_indexAccess) <<
m_utils.readFromCalldata(*arrayType.baseType()) <<
"(" <<
var.commaSeparatedList() <<
")\n";
else
define(_indexAccess, var);
break;
}
}
}
@ -1534,7 +1559,10 @@ void IRGeneratorForStatements::setLValue(Expression const& _expression, IRLValue
solAssert(!m_currentLValue, "");
if (_expression.annotation().lValueRequested)
{
m_currentLValue.emplace(std::move(_lvalue));
solAssert(!_lvalue.type.dataStoredIn(DataLocation::CallData), "");
}
else
// Only define the expression, if it will not be written to.
define(_expression, readFromLValue(_lvalue));

View File

@ -0,0 +1,20 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[] calldata x, uint256 i) external returns (uint256) {
return x[i];
}
function f(uint256[][] calldata x, uint256 i, uint256 j) external returns (uint256) {
return x[i][j];
}
}
// ====
// compileViaYul: also
// ----
// f(uint256[],uint256): 0x40, 0, 0 -> FAILURE
// f(uint256[],uint256): 0x40, 0, 1, 23 -> 23
// f(uint256[],uint256): 0x40, 1, 1, 23 -> FAILURE
// f(uint256[],uint256): 0x40, 0, 2, 23, 42 -> 23
// f(uint256[],uint256): 0x40, 1, 2, 23, 42 -> 42
// f(uint256[],uint256): 0x40, 2, 2, 23, 42 -> FAILURE
// f(uint256[][],uint256,uint256): 0x60, 0, 0 -> FAILURE
// f(uint256[][],uint256,uint256): 0x60, 0, 0, 1, 0x20, 1, 23 -> 23

View File

@ -0,0 +1,34 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[] calldata x) external returns (uint256) {
return x.length;
}
function f(uint256[][] calldata x) external returns (uint256 l1, uint256 l2, uint256 l3) {
l1 = x.length;
if (l1 > 0) l2 = x[0].length;
if (l1 > 1) l3 = x[1].length;
}
function f(uint256[2] calldata x) external returns (uint256) {
return x.length;
}
}
// ====
// compileViaYul: also
// ----
// f(uint256[]): 0x20, 0 -> 0
// f(uint256[]): 0x20, 1, 23 -> 1
// f(uint256[]): 0x20, 2, 23, 42 -> 2
// f(uint256[]): 0x20, 3, 23, 42, 17 -> 3
// f(uint256[2]): 23, 42 -> 2
// f(uint256[][]): 0x20, 0 -> 0, 0, 0
// f(uint256[][]): 0x20, 1, 0x20, 0 -> 1, 0, 0
// f(uint256[][]): 0x20, 1, 0x00 -> 1, 0, 0
// f(uint256[][]): 0x20, 1, 0x20, 1, 23 -> 1, 1, 0
// f(uint256[][]): 0x20, 1, 0x20, 2, 23, 42 -> 1, 2, 0
// f(uint256[][]): 0x20, 1, 0x40, 0, 2, 23, 42 -> 1, 2, 0
// f(uint256[][]): 0x20, 1, -32 -> 1, 1, 0
// f(uint256[][]): 0x20, 2, 0x40, 0x40, 2, 23, 42 -> 2, 2, 2
// f(uint256[][]): 0x20, 2, 0x40, 0xa0, 2, 23, 42, 0 -> 2, 2, 0
// f(uint256[][]): 0x20, 2, 0xA0, 0x40, 2, 23, 42, 0 -> 2, 0, 2
// f(uint256[][]): 0x20, 2, 0x40, 0xA0, 2, 23, 42, 1, 17 -> 2, 2, 1
// f(uint256[][]): 0x20, 2, 0x40, 0xA0, 2, 23, 42, 2, 17, 13 -> 2, 2, 2

View File

@ -0,0 +1,20 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[][2][] calldata x, uint256 i, uint256 j, uint256 k) external returns (uint256 a, uint256 b, uint256 c, uint256 d) {
a = x.length;
b = x[i].length;
c = x[i][j].length;
d = x[i][j][k];
}
}
// ====
// compileViaYul: also
// ----
// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 0, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> 1, 2, 1, 42
// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 1, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> 1, 2, 1, 23
// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 1, 0, 1, 0x20, 0x40, 0x80, 1, 42, 2, 23, 17 -> 1, 2, 2, 23
// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 1, 1, 1, 0x20, 0x40, 0x80, 1, 42, 2, 23, 17 -> 1, 2, 2, 17
// f(uint256[][2][],uint256,uint256,uint256): 0x80, 1, 0, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> FAILURE
// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 2, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> FAILURE
// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 2, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> FAILURE

View File

@ -0,0 +1,12 @@
pragma experimental ABIEncoderV2;
contract C {
function f(bytes[] calldata a, uint256 i) external returns (uint) {
return uint8(a[0][i]);
}
}
// ====
// compileViaYul: also
// ----
// f(bytes[],uint256): 0x40, 0, 1, 0x20, 2, hex"6162" -> 0x61
// f(bytes[],uint256): 0x40, 1, 1, 0x20, 2, hex"6162" -> 0x62
// f(bytes[],uint256): 0x40, 2, 1, 0x20, 2, hex"6162" -> FAILURE