Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 195 additions & 1 deletion libsolidity/codegen/YulUtilFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(), "");
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this perform cleanup?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but I think we said we leave that for now as there was no function for cleaning up addr/selector. The evm code was only cleaning up addr I think and it used FunctionType for it..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, the function actually already performs cleanup.

.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, "");
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
});
}
32 changes: 32 additions & 0 deletions libsolidity/codegen/YulUtilFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ class YulUtilFunctions
/// 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);
Expand All @@ -162,6 +173,14 @@ class YulUtilFunctions
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.
Expand All @@ -176,6 +195,12 @@ class YulUtilFunctions
/// 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).
Expand All @@ -197,6 +222,11 @@ class YulUtilFunctions
/// 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.
Expand Down Expand Up @@ -244,6 +274,8 @@ class YulUtilFunctions
/// 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;
};
Expand Down
47 changes: 39 additions & 8 deletions libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -851,13 +866,29 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
break;
}
case DataLocation::Memory:
solUnimplementedAssert(false, "");
{
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:
solUnimplementedAssert(false, "");
break;
}
{
solUnimplemented("calldata not yet implemented!");

}
}
}
else if (baseType.category() == Type::Category::FixedBytes)
solUnimplementedAssert(false, "");
Expand Down
Loading