Merge pull request #8474 from mijovic/sol2YulCallDataIndexRangeAccess

[Sol->Yul] Adding slicing for call data arrays
This commit is contained in:
Daniel Kirchner 2020-03-13 16:33:32 +01:00 committed by GitHub
commit 362c2175bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 168 additions and 20 deletions

View File

@ -1800,6 +1800,7 @@ bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _indexAccess); CompilerContext::LocationSetter locationSetter(m_context, _indexAccess);
_indexAccess.baseExpression().accept(*this); _indexAccess.baseExpression().accept(*this);
// stack: offset length
Type const& baseType = *_indexAccess.baseExpression().annotation().type; Type const& baseType = *_indexAccess.baseExpression().annotation().type;
@ -1815,27 +1816,21 @@ bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess)
acceptAndConvert(*_indexAccess.startExpression(), *TypeProvider::uint256()); acceptAndConvert(*_indexAccess.startExpression(), *TypeProvider::uint256());
else else
m_context << u256(0); m_context << u256(0);
// stack: offset length sliceStart
m_context << Instruction::SWAP1;
// stack: offset sliceStart length
if (_indexAccess.endExpression()) if (_indexAccess.endExpression())
acceptAndConvert(*_indexAccess.endExpression(), *TypeProvider::uint256()); acceptAndConvert(*_indexAccess.endExpression(), *TypeProvider::uint256());
else else
m_context << Instruction::DUP2; m_context << Instruction::DUP1;
// stack: offset sliceStart length sliceEnd
m_context.appendInlineAssembly( m_context << Instruction::SWAP3;
Whiskers(R"({ // stack: sliceEnd sliceStart length offset
if gt(sliceStart, sliceEnd) { <revertStringStartEnd> }
if gt(sliceEnd, length) { <revertStringEndLength> }
offset := add(offset, mul(sliceStart, <stride>)) m_context.callYulFunction(m_context.utilFunctions().calldataArrayIndexRangeAccess(*arrayType), 4, 2);
length := sub(sliceEnd, sliceStart)
})")
("stride", toString(arrayType->calldataStride()))
("revertStringStartEnd", m_context.revertReasonIfDebug("Slice starts after end"))
("revertStringEndLength", m_context.revertReasonIfDebug("Slice is greater than length"))
.render(),
{"offset", "length", "sliceStart", "sliceEnd"}
);
m_context << Instruction::POP << Instruction::POP;
return false; return false;
} }

View File

@ -933,6 +933,28 @@ string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type
}); });
} }
string YulUtilFunctions::calldataArrayIndexRangeAccess(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
solAssert(_type.isDynamicallySized(), "");
string functionName = "calldata_array_index_range_access_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(offset, length, startIndex, endIndex) -> offsetOut, lengthOut {
if gt(startIndex, endIndex) { <revertSliceStartAfterEnd> }
if gt(endIndex, length) { <revertSliceGreaterThanLength> }
offsetOut := add(offset, mul(startIndex, <stride>))
lengthOut := sub(endIndex, startIndex)
}
)")
("functionName", functionName)
("stride", to_string(_type.calldataStride()))
("revertSliceStartAfterEnd", revertReasonIfDebug("Slice starts after end"))
("revertSliceGreaterThanLength", revertReasonIfDebug("Slice is greater than length"))
.render();
});
}
string YulUtilFunctions::accessCalldataTailFunction(Type const& _type) string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
{ {
solAssert(_type.isDynamicallyEncoded(), ""); solAssert(_type.isDynamicallyEncoded(), "");
@ -1365,6 +1387,37 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
}); });
} }
if (_from.category() == Type::Category::ArraySlice)
{
solAssert(_from.isDynamicallySized(), "");
solAssert(_from.dataStoredIn(DataLocation::CallData), "");
solAssert(_to.category() == Type::Category::Array, "");
ArraySliceType const& fromType = dynamic_cast<ArraySliceType const&>(_from);
ArrayType const& targetType = dynamic_cast<ArrayType const&>(_to);
solAssert(
*fromType.arrayType().baseType() == *targetType.baseType(),
"Converting arrays of different type is not possible"
);
string const functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(offset, length) -> outOffset, outLength {
outOffset := offset
outLength := length
}
)")
("functionName", functionName)
.render();
});
}
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1) if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
return conversionFunctionSpecial(_from, _to); return conversionFunctionSpecial(_from, _to);

View File

@ -175,6 +175,11 @@ public:
/// signature: (baseRef, index) -> offset[, length] /// signature: (baseRef, index) -> offset[, length]
std::string calldataArrayIndexAccessFunction(ArrayType const& _type); std::string calldataArrayIndexAccessFunction(ArrayType const& _type);
/// @returns the name of a function that returns offset and length for array slice
/// for the given array offset, length and start and end indices for slice
/// signature: (arrayOffset, arrayLength, sliceStart, sliceEnd) -> offset, length
std::string calldataArrayIndexRangeAccess(ArrayType const& _type);
/// @returns the name of a function that follows a calldata tail while performing /// @returns the name of a function that follows a calldata tail while performing
/// bounds checks. /// bounds checks.
/// signature: (baseRef, tailPointer) -> offset[, length] /// signature: (baseRef, tailPointer) -> offset[, length]

View File

@ -1001,9 +1001,16 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
} }
}); });
} }
else if (baseType.category() == Type::Category::Array) else if (baseType.category() == Type::Category::Array || baseType.category() == Type::Category::ArraySlice)
{ {
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType); ArrayType const& arrayType =
baseType.category() == Type::Category::Array ?
dynamic_cast<ArrayType const&>(baseType) :
dynamic_cast<ArraySliceType const&>(baseType).arrayType();
if (baseType.category() == Type::Category::ArraySlice)
solAssert(arrayType.dataStoredIn(DataLocation::CallData) && arrayType.isDynamicallySized(), "");
solAssert(_indexAccess.indexExpression(), "Index expression expected."); solAssert(_indexAccess.indexExpression(), "Index expression expected.");
switch (arrayType.location()) switch (arrayType.location())
@ -1086,9 +1093,50 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
solAssert(false, "Index access only allowed for mappings or arrays."); solAssert(false, "Index access only allowed for mappings or arrays.");
} }
void IRGeneratorForStatements::endVisit(IndexRangeAccess const&) void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAccess)
{ {
solUnimplementedAssert(false, "Index range accesses not yet implemented."); Type const& baseType = *_indexRangeAccess.baseExpression().annotation().type;
solAssert(
baseType.category() == Type::Category::Array || baseType.category() == Type::Category::ArraySlice,
"Index range accesses is available only on arrays and array slices."
);
ArrayType const& arrayType =
baseType.category() == Type::Category::Array ?
dynamic_cast<ArrayType const &>(baseType) :
dynamic_cast<ArraySliceType const &>(baseType).arrayType();
switch (arrayType.location())
{
case DataLocation::CallData:
{
solAssert(baseType.isDynamicallySized(), "");
IRVariable sliceStart{m_context.newYulVariable(), *TypeProvider::uint256()};
if (_indexRangeAccess.startExpression())
define(sliceStart, IRVariable{*_indexRangeAccess.startExpression()});
else
define(sliceStart) << u256(0) << "\n";
IRVariable sliceEnd{
m_context.newYulVariable(),
*TypeProvider::uint256()
};
if (_indexRangeAccess.endExpression())
define(sliceEnd, IRVariable{*_indexRangeAccess.endExpression()});
else
define(sliceEnd, IRVariable{_indexRangeAccess.baseExpression()}.part("length"));
IRVariable range{_indexRangeAccess};
define(range) <<
m_utils.calldataArrayIndexRangeAccess(arrayType) << "(" <<
IRVariable{_indexRangeAccess.baseExpression()}.commaSeparatedList() << ", " <<
sliceStart.name() << ", " <<
sliceEnd.name() << ")\n";
break;
}
default:
solUnimplementedAssert(false, "Index range accesses is implemented only on calldata arrays.");
}
} }
void IRGeneratorForStatements::endVisit(Identifier const& _identifier) void IRGeneratorForStatements::endVisit(Identifier const& _identifier)

View File

@ -6,6 +6,8 @@ contract C {
return (x[start:end][index], x[start:][0:end-start][index], x[:end][start:][index]); return (x[start:end][index], x[start:][0:end-start][index], x[:end][start:][index]);
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f(uint256[],uint256,uint256): 0x80, 0, 0, 0, 1, 42 -> // f(uint256[],uint256,uint256): 0x80, 0, 0, 0, 1, 42 ->
// f(uint256[],uint256,uint256): 0x80, 0, 1, 0, 1, 42 -> // f(uint256[],uint256,uint256): 0x80, 0, 1, 0, 1, 42 ->

View File

@ -0,0 +1,45 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[] calldata x, uint256 s, uint256 e) external returns (uint256) {
return uint256[](x[s:e]).length;
}
function f(uint256[] calldata x, uint256 s, uint256 e, uint256 ss, uint256 ee) external returns (uint256) {
return uint256[](x[s:e][ss:ee]).length;
}
function f_s_only(uint256[] calldata x, uint256 s) external returns (uint256) {
return uint256[](x[s:]).length;
}
function f_e_only(uint256[] calldata x, uint256 e) external returns (uint256) {
return uint256[](x[:e]).length;
}
function g(uint256[] calldata x, uint256 s, uint256 e, uint256 idx) external returns (uint256) {
return uint256[](x[s:e])[idx];
}
function gg(uint256[] calldata x, uint256 s, uint256 e, uint256 idx) external returns (uint256) {
return x[s:e][idx];
}
function gg_s_only(uint256[] calldata x, uint256 s, uint256 idx) external returns (uint256) {
return x[s:][idx];
}
function gg_e_only(uint256[] calldata x, uint256 e, uint256 idx) external returns (uint256) {
return x[:e][idx];
}
}
// ====
// compileViaYul: also
// ----
// f(uint256[],uint256,uint256): 0x60, 2, 4, 5, 1, 2, 3, 4, 5 -> 2
// f(uint256[],uint256,uint256): 0x60, 2, 6, 5, 1, 2, 3, 4, 5 -> FAILURE
// f(uint256[],uint256,uint256): 0x60, 3, 3, 5, 1, 2, 3, 4, 5 -> 0
// f(uint256[],uint256,uint256): 0x60, 4, 3, 5, 1, 2, 3, 4, 5 -> FAILURE
// f(uint256[],uint256,uint256): 0x60, 0, 3, 5, 1, 2, 3, 4, 5 -> 3
// f(uint256[],uint256,uint256,uint256,uint256): 0xA0, 1, 3, 1, 2, 5, 1, 2, 3, 4, 5 -> 1
// f(uint256[],uint256,uint256,uint256,uint256): 0xA0, 1, 3, 1, 4, 5, 1, 2, 3, 4, 5 -> FAILURE
// f_s_only(uint256[],uint256): 0x40, 2, 5, 1, 2, 3, 4, 5 -> 3
// f_s_only(uint256[],uint256): 0x40, 6, 5, 1, 2, 3, 4, 5 -> FAILURE
// f_e_only(uint256[],uint256): 0x40, 3, 5, 1, 2, 3, 4, 5 -> 3
// f_e_only(uint256[],uint256): 0x40, 6, 5, 1, 2, 3, 4, 5 -> FAILURE
// g(uint256[],uint256,uint256,uint256): 0x80, 2, 4, 1, 5, 1, 2, 3, 4, 5 -> 4
// g(uint256[],uint256,uint256,uint256): 0x80, 2, 4, 3, 5, 1, 2, 3, 4, 5 -> FAILURE
// gg(uint256[],uint256,uint256,uint256): 0x80, 2, 4, 1, 5, 1, 2, 3, 4, 5 -> 4
// gg(uint256[],uint256,uint256,uint256): 0x80, 2, 4, 3, 5, 1, 2, 3, 4, 5 -> FAILURE