Skip to content

Commit

Permalink
Merge pull request #7015 from ethereum/sol-yul-arrays
Browse files Browse the repository at this point in the history
[Sol->Yul] Implement uint256[] memory arrays
  • Loading branch information
chriseth authored Jul 9, 2019
2 parents 859dbaa + 0f24fce commit 15eb8fe
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 14 deletions.
196 changes: 195 additions & 1 deletion libsolidity/codegen/YulUtilFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,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 @@ -882,6 +912,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 @@ -923,6 +963,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, "");
Expand Down Expand Up @@ -1039,6 +1137,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 @@ -1147,8 +1267,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 @@ -1562,3 +1699,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 @@ -237,6 +267,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

0 comments on commit 15eb8fe

Please sign in to comment.