mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Implement calldata arrays for Yul IR
This commit is contained in:
parent
b580a7a35d
commit
df0873d138
@ -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>
|
||||
}
|
||||
)")
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
Loading…
Reference in New Issue
Block a user