mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6923 from ethereum/sol-yul-storage-array
[Sol->Yul] Implement .length for storage arrays
This commit is contained in:
commit
fc64de6d90
@ -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)
|
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||||
|
@ -78,9 +78,10 @@ public:
|
|||||||
std::string shiftRightFunction(size_t _numBits);
|
std::string shiftRightFunction(size_t _numBits);
|
||||||
std::string shiftRightFunctionDynamic();
|
std::string shiftRightFunctionDynamic();
|
||||||
|
|
||||||
/// @returns the name of a function f(value, toInsert) -> newValue which replaces the
|
/// @returns the name of a function which replaces the
|
||||||
/// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant
|
/// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant
|
||||||
/// byte) by the _numBytes least significant bytes of `toInsert`.
|
/// byte) by the _numBytes least significant bytes of `toInsert`.
|
||||||
|
/// signature: (value, toInsert) -> result
|
||||||
std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes);
|
std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes);
|
||||||
|
|
||||||
/// signature: (value, shiftBytes, toInsert) -> result
|
/// signature: (value, shiftBytes, toInsert) -> result
|
||||||
@ -88,10 +89,13 @@ public:
|
|||||||
|
|
||||||
/// @returns the name of a function that rounds its input to the next multiple
|
/// @returns the name of a function that rounds its input to the next multiple
|
||||||
/// of 32 or the input if it is a multiple of 32.
|
/// of 32 or the input if it is a multiple of 32.
|
||||||
|
/// signature: (value) -> result
|
||||||
std::string roundUpFunction();
|
std::string roundUpFunction();
|
||||||
|
|
||||||
|
/// signature: (x, y) -> sum
|
||||||
std::string overflowCheckedUIntAddFunction(size_t _bits);
|
std::string overflowCheckedUIntAddFunction(size_t _bits);
|
||||||
|
|
||||||
|
/// signature: (x, y) -> product
|
||||||
std::string overflowCheckedUIntMulFunction(size_t _bits);
|
std::string overflowCheckedUIntMulFunction(size_t _bits);
|
||||||
|
|
||||||
/// @returns name of function to perform division on integers.
|
/// @returns name of function to perform division on integers.
|
||||||
@ -101,9 +105,29 @@ public:
|
|||||||
|
|
||||||
/// @returns computes the difference between two values.
|
/// @returns computes the difference between two values.
|
||||||
/// Assumes the input to be in range for the type.
|
/// Assumes the input to be in range for the type.
|
||||||
|
/// signature: (x, y) -> diff
|
||||||
std::string overflowCheckedUIntSubFunction();
|
std::string overflowCheckedUIntSubFunction();
|
||||||
|
|
||||||
|
/// @returns the name of a function that fetches the length of the given
|
||||||
|
/// array
|
||||||
|
/// signature: (array) -> length
|
||||||
std::string arrayLengthFunction(ArrayType const& _type);
|
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
|
/// @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).
|
/// to store an array in memory given its length (internally encoded, not ABI encoded).
|
||||||
/// The function reverts for too large lengths.
|
/// The function reverts for too large lengths.
|
||||||
|
@ -54,7 +54,7 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
|
|||||||
string errorMessage;
|
string errorMessage;
|
||||||
for (auto const& error: asmStack.errors())
|
for (auto const& error: asmStack.errors())
|
||||||
errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error);
|
errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error);
|
||||||
solAssert(false, "Invalid IR generated:\n" + errorMessage + "\n" + ir);
|
solAssert(false, ir + "\n\nInvalid IR generated:\n" + errorMessage + "\n");
|
||||||
}
|
}
|
||||||
asmStack.optimize();
|
asmStack.optimize();
|
||||||
|
|
||||||
|
@ -703,7 +703,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
|||||||
}
|
}
|
||||||
case Type::Category::Array:
|
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:
|
case Type::Category::FixedBytes:
|
||||||
{
|
{
|
||||||
|
@ -137,3 +137,31 @@ string IRStorageItem::setToZero() const
|
|||||||
{
|
{
|
||||||
solUnimplemented("Delete for storage location not yet implemented");
|
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 VariableDeclaration;
|
||||||
class IRGenerationContext;
|
class IRGenerationContext;
|
||||||
class Type;
|
class Type;
|
||||||
|
class ArrayType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class used to retrieve, delete and store data in LValues.
|
* Abstract class used to retrieve, delete and store data in LValues.
|
||||||
@ -107,6 +108,24 @@ private:
|
|||||||
boost::variant<std::string, unsigned> const m_offset;
|
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