[Sol->Yul] Implement uint256[] memory arrays

This commit is contained in:
Mathias Baumann 2019-06-27 13:36:06 +02:00
parent 035fde2932
commit 0f24fcea17
9 changed files with 440 additions and 16 deletions

View File

@ -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();
});
}

View File

@ -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;
};

View File

@ -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, "");

View File

@ -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);
}

View File

@ -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;
};
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -17,10 +17,6 @@ contract C {
return func() == internal_func();
}
function external_func() external pure returns (int8)
{
return 1;
}
}
// ====
// compileViaYul: true