mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[Sol->Yul] Implement .length for storage arrays
This commit is contained in:
parent
c5b50039d2
commit
910cb8d329
@ -517,6 +517,120 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||
});
|
||||
}
|
||||
|
||||
std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(_type.location() == DataLocation::Storage, "");
|
||||
solAssert(_type.isDynamicallySized(), "");
|
||||
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
|
||||
solUnimplementedAssert(_type.baseType()->isValueType(), "...");
|
||||
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "...");
|
||||
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
|
||||
|
||||
string functionName = "resize_array_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(array, newLen) {
|
||||
if gt(newLen, <maxArrayLength>) {
|
||||
invalid()
|
||||
}
|
||||
|
||||
let oldLen := <fetchLength>(array)
|
||||
|
||||
// Store new length
|
||||
sstore(array, newLen)
|
||||
|
||||
// Size was reduced, clear end of array
|
||||
if lt(newLen, oldLen) {
|
||||
let oldSlotCount := <convertToSize>(oldLen)
|
||||
let newSlotCount := <convertToSize>(newLen)
|
||||
let arrayDataStart := <dataPosition>(array)
|
||||
let deleteStart := add(arrayDataStart, newSlotCount)
|
||||
let deleteEnd := add(arrayDataStart, oldSlotCount)
|
||||
<clearStorageRange>(deleteStart, deleteEnd)
|
||||
}
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("fetchLength", arrayLengthFunction(_type))
|
||||
("convertToSize", arrayConvertLengthToSize(_type))
|
||||
("dataPosition", arrayDataAreaFunction(_type))
|
||||
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
|
||||
("maxArrayLength", (u256(1) << 64).str())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
|
||||
{
|
||||
string functionName = "clear_storage_range_" + _type.identifier();
|
||||
|
||||
solUnimplementedAssert(_type.isValueType(), "...");
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(start, end) {
|
||||
for {} lt(start, end) { start := add(start, 1) }
|
||||
{
|
||||
sstore(start, 0)
|
||||
}
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_convert_length_to_size_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Type const& baseType = *_type.baseType();
|
||||
|
||||
switch (_type.location())
|
||||
{
|
||||
case DataLocation::Storage:
|
||||
{
|
||||
unsigned const baseStorageBytes = baseType.storageBytes();
|
||||
solAssert(baseStorageBytes > 0, "");
|
||||
solAssert(32 / baseStorageBytes > 0, "");
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(length) -> size {
|
||||
size := length
|
||||
<?multiSlot>
|
||||
size := <mul>(<storageSize>, length)
|
||||
<!multiSlot>
|
||||
// Number of slots rounded up
|
||||
size := div(add(length, sub(<itemsPerSlot>, 1)), <itemsPerSlot>)
|
||||
</multiSlot>
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("multiSlot", baseType.storageSize() > 1)
|
||||
("itemsPerSlot", to_string(32 / baseStorageBytes))
|
||||
("storageSize", baseType.storageSize().str())
|
||||
("mul", overflowCheckedUIntMulFunction(TypeProvider::uint256()->numBits()))
|
||||
.render();
|
||||
}
|
||||
case DataLocation::CallData: // fallthrough
|
||||
case DataLocation::Memory:
|
||||
return Whiskers(R"(
|
||||
function <functionName>(length) -> size {
|
||||
<?byteArray>
|
||||
size := length
|
||||
<!byteArray>
|
||||
size := <mul>(length, <elementSize>)
|
||||
</byteArray>
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("elementSize", _type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize())
|
||||
("byteArray", _type.isByteArray())
|
||||
("mul", overflowCheckedUIntMulFunction(TypeProvider::uint256()->numBits()))
|
||||
.render();
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||
|
@ -104,6 +104,22 @@ public:
|
||||
std::string overflowCheckedUIntSubFunction();
|
||||
|
||||
std::string arrayLengthFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that resizes a storage array
|
||||
/// signature: (array, newLen)
|
||||
std::string resizeDynamicArrayFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that will clear the storage area given
|
||||
/// by the start and end (exclusive) parameters (slots). Only works for value types.
|
||||
/// signature: (start, end)
|
||||
std::string clearStorageRangeFunction(Type const& _type);
|
||||
|
||||
/// Returns the name of a function that will convert a given length to the
|
||||
/// size in memory (number of storage slots or calldata/memory bytes) it
|
||||
/// will require.
|
||||
/// signature: (length) -> size
|
||||
std::string arrayConvertLengthToSize(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that computes the number of bytes required
|
||||
/// to store an array in memory given its length (internally encoded, not ABI encoded).
|
||||
/// The function reverts for too large lengths.
|
||||
|
@ -703,7 +703,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
}
|
||||
case Type::Category::Array:
|
||||
{
|
||||
solUnimplementedAssert(false, "");
|
||||
auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
|
||||
|
||||
solAssert(member == "length", "");
|
||||
|
||||
if (!type.isDynamicallySized())
|
||||
defineExpression(_memberAccess) << type.length() << "\n";
|
||||
else
|
||||
switch (type.location())
|
||||
{
|
||||
case DataLocation::CallData:
|
||||
solUnimplementedAssert(false, "");
|
||||
//m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
setLValue(_memberAccess, make_unique<IRStorageArrayLength>(
|
||||
m_context,
|
||||
m_context.variable(_memberAccess.expression()),
|
||||
*_memberAccess.annotation().type,
|
||||
type
|
||||
));
|
||||
|
||||
break;
|
||||
case DataLocation::Memory:
|
||||
solUnimplementedAssert(false, "");
|
||||
//m_context << Instruction::MLOAD;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Type::Category::FixedBytes:
|
||||
{
|
||||
|
@ -137,3 +137,31 @@ string IRStorageItem::setToZero() const
|
||||
{
|
||||
solUnimplemented("Delete for storage location not yet implemented");
|
||||
}
|
||||
|
||||
IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string _slot, Type const& _type, ArrayType const& _arrayType):
|
||||
IRLValue(_context, &_type), m_arrayType(_arrayType), m_slot(move(_slot))
|
||||
{
|
||||
solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!");
|
||||
}
|
||||
|
||||
string IRStorageArrayLength::retrieveValue() const
|
||||
{
|
||||
return m_context.utils().arrayLengthFunction(m_arrayType) + "(" + m_slot + ")\n";
|
||||
}
|
||||
|
||||
string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const
|
||||
{
|
||||
solAssert(_type == *m_type, "Different type, but might not be an error.");
|
||||
|
||||
return m_context.utils().resizeDynamicArrayFunction(m_arrayType) +
|
||||
"(" +
|
||||
m_slot +
|
||||
", " +
|
||||
_value +
|
||||
")\n";
|
||||
}
|
||||
|
||||
string IRStorageArrayLength::setToZero() const
|
||||
{
|
||||
return storeValue("0", *TypeProvider::uint256());
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ namespace solidity
|
||||
class VariableDeclaration;
|
||||
class IRGenerationContext;
|
||||
class Type;
|
||||
class ArrayType;
|
||||
|
||||
/**
|
||||
* Abstract class used to retrieve, delete and store data in LValues.
|
||||
@ -107,6 +108,24 @@ private:
|
||||
boost::variant<std::string, unsigned> const m_offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reference to the "length" member of a dynamically-sized storage array. This is an LValue with special
|
||||
* semantics since assignments to it might reduce its length and thus the array's members have to be
|
||||
* deleted.
|
||||
*/
|
||||
class IRStorageArrayLength: public IRLValue
|
||||
{
|
||||
public:
|
||||
IRStorageArrayLength(IRGenerationContext& _context, std::string _slot, Type const& _type, ArrayType const& _arrayType);
|
||||
|
||||
std::string retrieveValue() const override;
|
||||
std::string storeValue(std::string const& _value, Type const& _type) const override;
|
||||
std::string setToZero() const override;
|
||||
|
||||
private:
|
||||
ArrayType const& m_arrayType;
|
||||
std::string const m_slot;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
contract C {
|
||||
uint[] storageArray;
|
||||
function set_get_length(uint256 len) public returns (uint256)
|
||||
{
|
||||
storageArray.length = len;
|
||||
return storageArray.length;
|
||||
}
|
||||
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: true
|
||||
// ----
|
||||
// set_get_length(uint256): 0 -> 0
|
||||
// set_get_length(uint256): 1 -> 1
|
||||
// set_get_length(uint256): 10 -> 10
|
||||
// set_get_length(uint256): 20 -> 20
|
||||
// set_get_length(uint256): 0 -> 0
|
||||
// set_get_length(uint256): 0xFF -> 0xFF
|
||||
// set_get_length(uint256): 0xFFFF -> 0xFFFF
|
Loading…
Reference in New Issue
Block a user