mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6940 from ethereum/sol-yul-storage-array-index
[Sol->Yul] Implement index access for storage arrays
This commit is contained in:
commit
350631ae0d
@ -724,6 +724,36 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
||||||
|
{
|
||||||
|
solUnimplementedAssert(_type.baseType()->storageBytes() > 16, "");
|
||||||
|
|
||||||
|
string functionName = "storage_array_index_access_" + _type.identifier();
|
||||||
|
return m_functionCollector->createFunction(functionName, [&]() {
|
||||||
|
return Whiskers(R"(
|
||||||
|
function <functionName>(array, index) -> slot, offset {
|
||||||
|
if iszero(lt(index, <arrayLen>(array))) {
|
||||||
|
invalid()
|
||||||
|
}
|
||||||
|
|
||||||
|
let data := <dataAreaFunc>(array)
|
||||||
|
<?multipleItemsPerSlot>
|
||||||
|
|
||||||
|
<!multipleItemsPerSlot>
|
||||||
|
slot := add(data, mul(index, <storageSize>))
|
||||||
|
offset := 0
|
||||||
|
</multipleItemsPerSlot>
|
||||||
|
}
|
||||||
|
)")
|
||||||
|
("functionName", functionName)
|
||||||
|
("arrayLen", arrayLengthFunction(_type))
|
||||||
|
("dataAreaFunc", arrayDataAreaFunction(_type))
|
||||||
|
("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16)
|
||||||
|
("storageSize", _type.baseType()->storageSize().str())
|
||||||
|
.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
solAssert(!_type.isByteArray(), "");
|
solAssert(!_type.isByteArray(), "");
|
||||||
|
@ -140,6 +140,12 @@ public:
|
|||||||
/// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
|
/// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
|
||||||
/// for the data position of an array which is stored in that slot / memory area / calldata area.
|
/// for the data position of an array which is stored in that slot / memory area / calldata area.
|
||||||
std::string arrayDataAreaFunction(ArrayType const& _type);
|
std::string arrayDataAreaFunction(ArrayType const& _type);
|
||||||
|
|
||||||
|
/// @returns the name of a function that returns the slot and offset for the
|
||||||
|
/// given array and index
|
||||||
|
/// signature: (array, index) -> slot, offset
|
||||||
|
std::string storageArrayIndexAccessFunction(ArrayType const& _type);
|
||||||
|
|
||||||
/// @returns the name of a function that advances an array data pointer to the next element.
|
/// @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.
|
/// 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);
|
std::string nextArrayElementFunction(ArrayType const& _type);
|
||||||
|
@ -56,6 +56,35 @@ struct CopyTranslate: public yul::ASTCopier
|
|||||||
|
|
||||||
using ASTCopier::operator();
|
using ASTCopier::operator();
|
||||||
|
|
||||||
|
yul::Expression operator()(yul::Identifier const& _identifier) override
|
||||||
|
{
|
||||||
|
if (m_references.count(&_identifier))
|
||||||
|
{
|
||||||
|
auto const& reference = m_references.at(&_identifier);
|
||||||
|
auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
|
||||||
|
solUnimplementedAssert(varDecl, "");
|
||||||
|
|
||||||
|
if (reference.isOffset || reference.isSlot)
|
||||||
|
{
|
||||||
|
solAssert(reference.isOffset != reference.isSlot, "");
|
||||||
|
|
||||||
|
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(*varDecl);
|
||||||
|
|
||||||
|
string const value = reference.isSlot ?
|
||||||
|
slot_offset.first.str() :
|
||||||
|
to_string(slot_offset.second);
|
||||||
|
|
||||||
|
return yul::Literal{
|
||||||
|
_identifier.location,
|
||||||
|
yul::LiteralKind::Number,
|
||||||
|
yul::YulString{value},
|
||||||
|
yul::YulString{"uint256"}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ASTCopier::operator()(_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
yul::YulString translateIdentifier(yul::YulString _name) override
|
yul::YulString translateIdentifier(yul::YulString _name) override
|
||||||
{
|
{
|
||||||
// Strictly, the dialect used by inline assembly (m_dialect) could be different
|
// Strictly, the dialect used by inline assembly (m_dialect) could be different
|
||||||
@ -76,9 +105,10 @@ struct CopyTranslate: public yul::ASTCopier
|
|||||||
auto const& reference = m_references.at(&_identifier);
|
auto const& reference = m_references.at(&_identifier);
|
||||||
auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
|
auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
|
||||||
solUnimplementedAssert(varDecl, "");
|
solUnimplementedAssert(varDecl, "");
|
||||||
solUnimplementedAssert(
|
|
||||||
|
solAssert(
|
||||||
reference.isOffset == false && reference.isSlot == false,
|
reference.isOffset == false && reference.isSlot == false,
|
||||||
""
|
"Should not be called for offset/slot"
|
||||||
);
|
);
|
||||||
|
|
||||||
return yul::Identifier{
|
return yul::Identifier{
|
||||||
@ -790,7 +820,45 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
else if (baseType.category() == Type::Category::Array)
|
else if (baseType.category() == Type::Category::Array)
|
||||||
solUnimplementedAssert(false, "");
|
{
|
||||||
|
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
|
||||||
|
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
||||||
|
|
||||||
|
switch (arrayType.location())
|
||||||
|
{
|
||||||
|
case DataLocation::Storage:
|
||||||
|
{
|
||||||
|
string slot = m_context.newYulVariable();
|
||||||
|
string offset = m_context.newYulVariable();
|
||||||
|
|
||||||
|
m_code << Whiskers(R"(
|
||||||
|
let <slot>, <offset> := <indexFunc>(<array>, <index>)
|
||||||
|
)")
|
||||||
|
("slot", slot)
|
||||||
|
("offset", offset)
|
||||||
|
("indexFunc", m_utils.storageArrayIndexAccessFunction(arrayType))
|
||||||
|
("array", m_context.variable(_indexAccess.baseExpression()))
|
||||||
|
("index", m_context.variable(*_indexAccess.indexExpression()))
|
||||||
|
.render();
|
||||||
|
|
||||||
|
setLValue(_indexAccess, make_unique<IRStorageItem>(
|
||||||
|
m_context,
|
||||||
|
slot,
|
||||||
|
offset,
|
||||||
|
*_indexAccess.annotation().type
|
||||||
|
));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DataLocation::Memory:
|
||||||
|
solUnimplementedAssert(false, "");
|
||||||
|
break;
|
||||||
|
case DataLocation::CallData:
|
||||||
|
solUnimplementedAssert(false, "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
else if (baseType.category() == Type::Category::FixedBytes)
|
else if (baseType.category() == Type::Category::FixedBytes)
|
||||||
solUnimplementedAssert(false, "");
|
solUnimplementedAssert(false, "");
|
||||||
else if (baseType.category() == Type::Category::TypeType)
|
else if (baseType.category() == Type::Category::TypeType)
|
||||||
|
@ -135,7 +135,8 @@ string IRStorageItem::storeValue(string const& _value, Type const& _sourceType)
|
|||||||
|
|
||||||
string IRStorageItem::setToZero() const
|
string IRStorageItem::setToZero() const
|
||||||
{
|
{
|
||||||
solUnimplemented("Delete for storage location not yet implemented");
|
solUnimplementedAssert(m_type->isValueType(), "");
|
||||||
|
return storeValue(m_context.utils().zeroValueFunction(*m_type) + "()", *m_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string _slot, Type const& _type, ArrayType const& _arrayType):
|
IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string _slot, Type const& _type, ArrayType const& _arrayType):
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
contract C {
|
||||||
|
uint[] storageArray;
|
||||||
|
function test_indicies(uint256 len) public
|
||||||
|
{
|
||||||
|
storageArray.length = len;
|
||||||
|
|
||||||
|
for (uint i = 0; i < len; i++)
|
||||||
|
storageArray[i] = i + 1;
|
||||||
|
|
||||||
|
for (uint i = 0; i < len; i++)
|
||||||
|
require(storageArray[i] == i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: true
|
||||||
|
// ----
|
||||||
|
// test_indicies(uint256): 1 ->
|
||||||
|
// test_indicies(uint256): 129 ->
|
||||||
|
// test_indicies(uint256): 5 ->
|
||||||
|
// test_indicies(uint256): 10 ->
|
||||||
|
// test_indicies(uint256): 15 ->
|
||||||
|
// test_indicies(uint256): 0xFF ->
|
||||||
|
// test_indicies(uint256): 1000 ->
|
||||||
|
// test_indicies(uint256): 129 ->
|
||||||
|
// test_indicies(uint256): 128 ->
|
||||||
|
// test_indicies(uint256): 1 ->
|
@ -0,0 +1,21 @@
|
|||||||
|
contract C {
|
||||||
|
uint[] storageArray;
|
||||||
|
function test_boundery_check(uint256 len, uint256 access) public returns
|
||||||
|
(uint256)
|
||||||
|
{
|
||||||
|
storageArray.length = len;
|
||||||
|
return storageArray[access];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: true
|
||||||
|
// ----
|
||||||
|
// test_boundery_check(uint256, uint256): 10, 11 -> FAILURE
|
||||||
|
// test_boundery_check(uint256, uint256): 10, 9 -> 0
|
||||||
|
// test_boundery_check(uint256, uint256): 1, 9 -> FAILURE
|
||||||
|
// test_boundery_check(uint256, uint256): 1, 1 -> FAILURE
|
||||||
|
// test_boundery_check(uint256, uint256): 10, 10 -> FAILURE
|
||||||
|
// test_boundery_check(uint256, uint256): 256, 256 -> FAILURE
|
||||||
|
// test_boundery_check(uint256, uint256): 256, 255 -> 0
|
||||||
|
// test_boundery_check(uint256, uint256): 256, 0xFFFF -> FAILURE
|
||||||
|
// test_boundery_check(uint256, uint256): 256, 2 -> 0
|
@ -0,0 +1,51 @@
|
|||||||
|
contract C {
|
||||||
|
uint[] storageArray;
|
||||||
|
function test_zeroed_indicies(uint256 len) public
|
||||||
|
{
|
||||||
|
storageArray.length = len;
|
||||||
|
|
||||||
|
for (uint i = 0; i < len; i++)
|
||||||
|
storageArray[i] = i + 1;
|
||||||
|
|
||||||
|
if (len > 3)
|
||||||
|
{
|
||||||
|
storageArray.length = 3;
|
||||||
|
|
||||||
|
for (uint i = 3; i < len; i++)
|
||||||
|
{
|
||||||
|
assembly {
|
||||||
|
mstore(0, storageArray_slot)
|
||||||
|
let pos := add(keccak256(0, 0x20), i)
|
||||||
|
|
||||||
|
if iszero(eq(sload(pos), 0)) {
|
||||||
|
revert(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
storageArray.length = 0;
|
||||||
|
storageArray.length = len;
|
||||||
|
|
||||||
|
for (uint i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
require(storageArray[i] == 0);
|
||||||
|
|
||||||
|
uint256 val = storageArray[i];
|
||||||
|
uint256 check;
|
||||||
|
|
||||||
|
assembly { check := iszero(val) }
|
||||||
|
|
||||||
|
require(check == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: true
|
||||||
|
// ----
|
||||||
|
// test_zeroed_indicies(uint256): 1 ->
|
||||||
|
// test_zeroed_indicies(uint256): 5 ->
|
||||||
|
// test_zeroed_indicies(uint256): 10 ->
|
||||||
|
// test_zeroed_indicies(uint256): 15 ->
|
||||||
|
// test_zeroed_indicies(uint256): 0xFF ->
|
Loading…
Reference in New Issue
Block a user