mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[Sol->Yul] Implement uint256[] memory arrays
This commit is contained in:
parent
035fde2932
commit
0f24fcea17
@ -754,6 +754,36 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "memory_array_index_access_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(baseRef, index) -> addr {
|
||||
if iszero(lt(index, <arrayLen>(baseRef))) {
|
||||
invalid()
|
||||
}
|
||||
|
||||
let offset := mul(index, <stride>)
|
||||
<?dynamicallySized>
|
||||
offset := add(offset, 32)
|
||||
</dynamicallySized>
|
||||
addr := add(baseRef, offset)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("arrayLen", arrayLengthFunction(_type))
|
||||
("stride", to_string(_type.memoryStride()))
|
||||
("dynamicallySized", _type.isDynamicallySized())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& /*_type*/)
|
||||
{
|
||||
solUnimplemented("Calldata arrays not yet implemented!");
|
||||
}
|
||||
|
||||
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(!_type.isByteArray(), "");
|
||||
@ -881,6 +911,16 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromMemory(Type const& _type)
|
||||
{
|
||||
return readFromMemoryOrCalldata(_type, false);
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromCalldata(Type const& _type)
|
||||
{
|
||||
return readFromMemoryOrCalldata(_type, true);
|
||||
}
|
||||
|
||||
string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const _offset)
|
||||
{
|
||||
string const functionName =
|
||||
@ -922,6 +962,64 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::op
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
|
||||
{
|
||||
string const functionName =
|
||||
string("write_to_memory_") +
|
||||
_type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
solAssert(!dynamic_cast<StringLiteralType const*>(&_type), "");
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
||||
{
|
||||
solAssert(
|
||||
ref->location() == DataLocation::Memory,
|
||||
"Can only update types with location memory."
|
||||
);
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr, value) {
|
||||
mstore(memPtr, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else if (
|
||||
_type.category() == Type::Category::Function &&
|
||||
dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
|
||||
)
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr, addr, selector) {
|
||||
mstore(memPtr, <combine>(addr, selector))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("combine", combineExternalFunctionIdFunction())
|
||||
.render();
|
||||
}
|
||||
else if (_type.isValueType())
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr, value) {
|
||||
mstore(memPtr, <cleanup>(value))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("cleanup", cleanupFunction(_type))
|
||||
.render();
|
||||
}
|
||||
else // Should never happen
|
||||
{
|
||||
solAssert(
|
||||
false,
|
||||
"Memory store of type " + _type.toString(true) + " not allowed."
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
@ -1038,6 +1136,28 @@ string YulUtilFunctions::allocationFunction()
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
|
||||
{
|
||||
solUnimplementedAssert(!_type.isByteArray(), "");
|
||||
|
||||
string functionName = "allocate_memory_array_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(length) -> memPtr {
|
||||
memPtr := <alloc>(<allocSize>(length))
|
||||
<?dynamic>
|
||||
mstore(memPtr, length)
|
||||
</dynamic>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("alloc", allocationFunction())
|
||||
("allocSize", arrayAllocationSizeFunction(_type))
|
||||
("dynamic", _type.isDynamicallySized())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
{
|
||||
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
|
||||
@ -1146,8 +1266,25 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
solUnimplementedAssert(false, "Array conversion not implemented.");
|
||||
{
|
||||
bool equal = _from == _to;
|
||||
|
||||
if (!equal)
|
||||
{
|
||||
ArrayType const& from = dynamic_cast<decltype(from)>(_from);
|
||||
ArrayType const& to = dynamic_cast<decltype(to)>(_to);
|
||||
|
||||
if (*from.mobileType() == *to.mobileType())
|
||||
equal = true;
|
||||
}
|
||||
|
||||
if (equal)
|
||||
body = "converted := value";
|
||||
else
|
||||
solUnimplementedAssert(false, "Array conversion not implemented.");
|
||||
|
||||
break;
|
||||
}
|
||||
case Type::Category::Struct:
|
||||
solUnimplementedAssert(false, "Struct conversion not implemented.");
|
||||
break;
|
||||
@ -1579,3 +1716,60 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata)
|
||||
{
|
||||
string functionName =
|
||||
string("read_from_") +
|
||||
(_fromCalldata ? "calldata" : "memory") +
|
||||
_type.identifier();
|
||||
|
||||
// TODO use ABI functions for handling calldata
|
||||
if (_fromCalldata)
|
||||
solAssert(!_type.isDynamicallyEncoded(), "");
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
||||
{
|
||||
solAssert(refType->sizeOnStack() == 1, "");
|
||||
solAssert(!_fromCalldata, "");
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> value {
|
||||
value := mload(memPtr)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
|
||||
solAssert(_type.isValueType(), "");
|
||||
|
||||
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
|
||||
if (funType->kind() == FunctionType::Kind::External)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> addr, selector {
|
||||
let combined := <load>(memPtr)
|
||||
addr, selector := <splitFunction>(combined)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("load", _fromCalldata ? "calldataload" : "mload")
|
||||
("splitFunction", splitExternalFunctionIdFunction())
|
||||
.render();
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> value {
|
||||
value := <load>(memPtr)
|
||||
<?needsValidation>
|
||||
value := <validate>(value)
|
||||
</needsValidation>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("load", _fromCalldata ? "calldataload" : "mload")
|
||||
("needsValidation", _fromCalldata)
|
||||
("validate", _fromCalldata ? validatorFunction(_type) : "")
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
@ -146,6 +146,17 @@ public:
|
||||
/// signature: (array, index) -> slot, offset
|
||||
std::string storageArrayIndexAccessFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that returns the memory address for the
|
||||
/// given array base ref and index.
|
||||
/// Causes invalid opcode on out of range access.
|
||||
/// signature: (baseRef, index) -> address
|
||||
std::string memoryArrayIndexAccessFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that returns the calldata address for the
|
||||
/// given array base ref and index.
|
||||
/// signature: (baseRef, index) -> address
|
||||
std::string calldataArrayIndexAccessFunction(ArrayType 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);
|
||||
@ -162,6 +173,14 @@ public:
|
||||
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
std::string readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns a function that reads a value type from memory.
|
||||
/// signature: (addr) -> value
|
||||
std::string readFromMemory(Type const& _type);
|
||||
/// @returns a function that reads a value type from calldata.
|
||||
/// Reverts on invalid input.
|
||||
/// signature: (addr) -> value
|
||||
std::string readFromCalldata(Type const& _type);
|
||||
|
||||
/// @returns a function that extracts a value type from storage slot that has been
|
||||
/// retrieved already.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
@ -176,6 +195,12 @@ public:
|
||||
/// signature: (slot, [offset,] value)
|
||||
std::string updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const _offset = boost::optional<unsigned>());
|
||||
|
||||
/// Returns the name of a function that will write the given value to
|
||||
/// the specified address.
|
||||
/// Performs a cleanup before writing for value types.
|
||||
/// signature: (memPtr, value) ->
|
||||
std::string writeToMemoryFunction(Type const& _type);
|
||||
|
||||
/// Performs cleanup after reading from a potentially compressed storage slot.
|
||||
/// The function does not perform any validation, it just masks or sign-extends
|
||||
/// higher order bytes or left-aligns (in case of bytesNN).
|
||||
@ -197,6 +222,11 @@ public:
|
||||
/// Return value: pointer
|
||||
std::string allocationFunction();
|
||||
|
||||
/// @returns the name of a function that allocates a memory array.
|
||||
/// For dynamic arrays it adds space for length and stores it.
|
||||
/// signature: (length) -> memPtr
|
||||
std::string allocateMemoryArrayFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of the function that converts a value of type @a _from
|
||||
/// to a value of type @a _to. The resulting vale is guaranteed to be in range
|
||||
/// (i.e. "clean"). Asserts on failure.
|
||||
@ -244,6 +274,8 @@ private:
|
||||
/// use exactly one variable to hold the value.
|
||||
std::string conversionFunctionSpecial(Type const& _from, Type const& _to);
|
||||
|
||||
std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata);
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
};
|
||||
|
@ -593,8 +593,22 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
|
||||
break;
|
||||
}
|
||||
// Array creation using new
|
||||
case FunctionType::Kind::ObjectCreation:
|
||||
{
|
||||
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type);
|
||||
solAssert(arguments.size() == 1, "");
|
||||
|
||||
defineExpression(_functionCall) <<
|
||||
m_utils.allocateMemoryArrayFunction(arrayType) <<
|
||||
"(" <<
|
||||
expressionAsType(*arguments[0], *TypeProvider::uint256()) <<
|
||||
")\n";
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solUnimplemented("");
|
||||
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@ -756,11 +770,12 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
|
||||
break;
|
||||
case DataLocation::Memory:
|
||||
solUnimplementedAssert(false, "");
|
||||
//m_context << Instruction::MLOAD;
|
||||
defineExpression(_memberAccess) <<
|
||||
"mload(" <<
|
||||
m_context.variable(_memberAccess.expression()) <<
|
||||
")\n";
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Type::Category::FixedBytes:
|
||||
@ -851,13 +866,29 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
break;
|
||||
}
|
||||
case DataLocation::Memory:
|
||||
solUnimplementedAssert(false, "");
|
||||
break;
|
||||
case DataLocation::CallData:
|
||||
solUnimplementedAssert(false, "");
|
||||
break;
|
||||
}
|
||||
{
|
||||
string const memAddress =
|
||||
m_utils.memoryArrayIndexAccessFunction(arrayType) +
|
||||
"(" +
|
||||
m_context.variable(_indexAccess.baseExpression()) +
|
||||
", " +
|
||||
expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) +
|
||||
")";
|
||||
|
||||
setLValue(_indexAccess, make_unique<IRMemoryItem>(
|
||||
m_context,
|
||||
memAddress,
|
||||
false,
|
||||
*arrayType.baseType()
|
||||
));
|
||||
break;
|
||||
}
|
||||
case DataLocation::CallData:
|
||||
{
|
||||
solUnimplemented("calldata not yet implemented!");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (baseType.category() == Type::Category::FixedBytes)
|
||||
solUnimplementedAssert(false, "");
|
||||
|
@ -147,7 +147,7 @@ IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string
|
||||
|
||||
string IRStorageArrayLength::retrieveValue() const
|
||||
{
|
||||
return m_context.utils().arrayLengthFunction(m_arrayType) + "(" + m_slot + ")\n";
|
||||
return m_context.utils().arrayLengthFunction(m_arrayType) + "(" + m_slot + ")";
|
||||
}
|
||||
|
||||
string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const
|
||||
@ -166,3 +166,82 @@ string IRStorageArrayLength::setToZero() const
|
||||
{
|
||||
return storeValue("0", *TypeProvider::uint256());
|
||||
}
|
||||
|
||||
IRMemoryItem::IRMemoryItem(
|
||||
IRGenerationContext& _context,
|
||||
std::string _address,
|
||||
bool _byteArrayElement,
|
||||
Type const& _type
|
||||
):
|
||||
IRLValue(_context, &_type),
|
||||
m_address(move(_address)),
|
||||
m_byteArrayElement(_byteArrayElement)
|
||||
{ }
|
||||
|
||||
string IRMemoryItem::retrieveValue() const
|
||||
{
|
||||
if (m_byteArrayElement)
|
||||
return m_context.utils().cleanupFunction(*m_type) +
|
||||
"(mload(" +
|
||||
m_address +
|
||||
"))";
|
||||
|
||||
if (m_type->isValueType())
|
||||
return m_context.utils().readFromMemory(*m_type) +
|
||||
"(" +
|
||||
m_address +
|
||||
")";
|
||||
else
|
||||
return "mload(" + m_address + ")";
|
||||
}
|
||||
|
||||
string IRMemoryItem::storeValue(string const& _value, Type const& _type) const
|
||||
{
|
||||
if (!m_type->isValueType())
|
||||
{
|
||||
solUnimplementedAssert(_type == *m_type, "Conversion not implemented for assignment to memory.");
|
||||
|
||||
solAssert(m_type->sizeOnStack() == 1, "");
|
||||
solAssert(dynamic_cast<ReferenceType const*>(m_type), "");
|
||||
|
||||
return "mstore(" + m_address + ", " + _value + ")\n";
|
||||
}
|
||||
|
||||
solAssert(_type.isValueType(), "");
|
||||
|
||||
string prepared = _value;
|
||||
|
||||
// Exists to see if this case ever happens
|
||||
solAssert(_type == *m_type, "");
|
||||
|
||||
if (_type != *m_type)
|
||||
prepared =
|
||||
m_context.utils().conversionFunction(_type, *m_type) +
|
||||
"(" +
|
||||
_value +
|
||||
")";
|
||||
else
|
||||
prepared =
|
||||
m_context.utils().cleanupFunction(*m_type) +
|
||||
"(" +
|
||||
_value +
|
||||
")";
|
||||
|
||||
if (m_byteArrayElement)
|
||||
{
|
||||
solAssert(*m_type == *TypeProvider::byte(), "");
|
||||
return "mstore8(" + m_address + ", byte(0, " + prepared + "))\n";
|
||||
}
|
||||
else
|
||||
return m_context.utils().writeToMemoryFunction(*m_type) +
|
||||
"(" +
|
||||
m_address +
|
||||
", " +
|
||||
prepared +
|
||||
")\n";
|
||||
}
|
||||
|
||||
string IRMemoryItem::setToZero() const
|
||||
{
|
||||
return storeValue(m_context.utils().zeroValueFunction(*m_type) + "()", *m_type);
|
||||
}
|
||||
|
@ -127,5 +127,23 @@ private:
|
||||
std::string const m_slot;
|
||||
};
|
||||
|
||||
class IRMemoryItem: public IRLValue
|
||||
{
|
||||
public:
|
||||
IRMemoryItem(
|
||||
IRGenerationContext& _context,
|
||||
std::string _address,
|
||||
bool _byteArrayElement,
|
||||
Type const& _type
|
||||
);
|
||||
std::string retrieveValue() const override;
|
||||
std::string storeValue(std::string const& _value, Type const& _type) const override;
|
||||
|
||||
std::string setToZero() const override;
|
||||
private:
|
||||
std::string const m_address;
|
||||
bool m_byteArrayElement;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
contract C {
|
||||
function test(uint256 len, uint idx) public returns (uint256)
|
||||
{
|
||||
uint[] memory array = new uint[](len);
|
||||
uint result = receiver(array, idx);
|
||||
|
||||
for (uint256 i = 0; i < array.length; i++)
|
||||
require(array[i] == i + 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
function receiver(uint[] memory array, uint idx) public returns (uint256)
|
||||
{
|
||||
for (uint256 i = 0; i < array.length; i++)
|
||||
array[i] = i + 1;
|
||||
|
||||
return array[idx];
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: true
|
||||
// ----
|
||||
// test(uint256,uint256): 0,0 -> FAILURE
|
||||
// test(uint256,uint256): 1,0 -> 1
|
||||
// test(uint256,uint256): 10,5 -> 6
|
||||
// test(uint256,uint256): 10,50 -> FAILURE
|
@ -0,0 +1,13 @@
|
||||
contract C {
|
||||
function create(uint256 len) public returns (uint256)
|
||||
{
|
||||
uint[] memory array = new uint[](len);
|
||||
return array.length;
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: true
|
||||
// ----
|
||||
// create(uint256): 0 -> 0
|
||||
// create(uint256): 7 -> 7
|
||||
// create(uint256): 10 -> 10
|
@ -0,0 +1,35 @@
|
||||
contract C {
|
||||
function index(uint256 len) public returns (bool)
|
||||
{
|
||||
uint[] memory array = new uint[](len);
|
||||
|
||||
for (uint256 i = 0; i < len; i++)
|
||||
array[i] = i + 1;
|
||||
|
||||
for (uint256 i = 0; i < len; i++)
|
||||
require(array[i] == i + 1, "Unexpected value in array!");
|
||||
|
||||
return array.length == len;
|
||||
}
|
||||
function accessIndex(uint256 len, int256 idx) public returns (uint256)
|
||||
{
|
||||
uint[] memory array = new uint[](len);
|
||||
|
||||
for (uint256 i = 0; i < len; i++)
|
||||
array[i] = i + 1;
|
||||
|
||||
return array[uint256(idx)];
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: true
|
||||
// ----
|
||||
// index(uint256): 0 -> true
|
||||
// index(uint256): 10 -> true
|
||||
// index(uint256): 20 -> true
|
||||
// index(uint256): 0xFF -> true
|
||||
// accessIndex(uint256,int256): 10,1 -> 2
|
||||
// accessIndex(uint256,int256): 10,0 -> 1
|
||||
// accessIndex(uint256,int256): 10,11 -> FAILURE
|
||||
// accessIndex(uint256,int256): 10,10 -> FAILURE
|
||||
// accessIndex(uint256,int256): 10,-1 -> FAILURE
|
@ -17,10 +17,6 @@ contract C {
|
||||
|
||||
return func() == internal_func();
|
||||
}
|
||||
function external_func() external pure returns (int8)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: true
|
||||
|
Loading…
Reference in New Issue
Block a user