Skip to content

Commit

Permalink
[Sol->Yul] Implement uint256[] memory arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
Marenz committed Jun 27, 2019
1 parent 3be2180 commit 24a97c3
Show file tree
Hide file tree
Showing 8 changed files with 421 additions and 9 deletions.
220 changes: 220 additions & 0 deletions libsolidity/codegen/YulUtilFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,55 @@ 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 {
let offset := index
<?noByteArray>
offset := mul(index, <headSize>)
</noByteArray>
<?dynamicallySized>
offset := add(offset, 32)
</dynamicallySized>
addr := add(baseRef, offset)
}
)")
("functionName", functionName)
("headSize", to_string(_type.memoryHeadSize()))
("noByteArray", !_type.isByteArray())
("dynamicallySized", _type.isDynamicallySized())
.render();
});
}

string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type)
{
string functionName = "calldata_array_index_access_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
string multiplier = "1";

if (!_type.isByteArray())
{
if (_type.baseType()->isDynamicallyEncoded())
multiplier = "32";
else
multiplier = to_string(_type.baseType()->calldataEncodedSize());
}

return Whiskers(R"(
function <functionName>(baseRef, index) -> addr {
addr := add(baseRef, mul(index, <multiplier>))
}
)")
("functionName", functionName)
("multiplier", multiplier)
.render();
});
}

string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
{
solAssert(!_type.isByteArray(), "");
Expand Down Expand Up @@ -881,6 +930,70 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
});
}

string YulUtilFunctions::readFromMemory(Type const& _type, bool _fromCalldata, bool _padToWords)
{
string functionName =
string("read_from_") +
(_fromCalldata ? "calldata" : "memory") +
"_dynamic_" +
_type.identifier();

return m_functionCollector->createFunction(functionName, [&] {
if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
{
solAssert(!arrayType->isDynamicallySized(), "");
solAssert(!_fromCalldata, "");
solAssert(_padToWords, "");

return Whiskers(R"(
function <functionName>(addr) -> value {
value := addr
}
)")
("functionName", functionName)
.render();
}

unsigned numBytes = _type.calldataEncodedSize(_padToWords);

solAssert(numBytes != 0, "");

if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
if (funType->kind() == FunctionType::Kind::External)
return Whiskers(R"(
function <functionName>(addr) -> addr, selector {
addr := <load>(addr)
addr, selector := <splitFunction>(addr)
<?needsValidation>
selector := <validate>(selector)
</needsValidation>
}
)")
("functionName", functionName)
("load", _fromCalldata ? "calldataload" : "mload")
("needsValidation", _fromCalldata)
("validate", _fromCalldata ? validatorFunction(_type) : "")
("splitFunction", splitExternalFunctionIdFunction())
.render();

solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested.");

return Whiskers(R"(
function <functionName>(addr) -> value {
value := <load>(addr)
<?needsValidation>
value := <validate>(value)
</needsValidation>
}
)")
("functionName", functionName)
("load", _fromCalldata ? "calldataload" : "mload")
("needsValidation", _fromCalldata)
("validate", _fromCalldata ? validatorFunction(_type) : "")
.render();
});
}

string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const _offset)
{
string const functionName =
Expand Down Expand Up @@ -922,6 +1035,70 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::op
});
}

string YulUtilFunctions::updateMemoryValueFunction(Type const& _type, bool _pad)
{
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
{
solUnimplementedAssert(
ref->location() == DataLocation::Memory,
"Only in-memory reference type can be stored."
);

return updateMemoryValueFunction(*TypeProvider::uint256(), _pad);
}

string const functionName =
string("update_memory_value_") +
(_pad ? "padded_" : "") +
_type.identifier();

return m_functionCollector->createFunction(functionName, [&] {
if (/*auto str = */dynamic_cast<StringLiteralType const*>(&_type))
{
/*m_context << Instruction::DUP1;
storeStringData(bytesConstRef(str->value()));
if (_padToWordBoundaries)
m_context << u256(max<size_t>(32, ((str->value().size() + 31) / 32) * 32));
else
m_context << u256(str->value().size());
m_context << Instruction::ADD;*/
solUnimplemented("String literals not yet implemented!");
}
else if (
_type.category() == Type::Category::Function &&
dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
)
{
return Whiskers(R"(
function <functionName>(addr, selector, addr) {
mstore(addr, <combine>(addr, selector))
}
)")
("functionName", functionName)
("combine", combineExternalFunctionIdFunction())
.render();
}
else if (_type.isValueType())
{
return Whiskers(R"(
function <functionName>(addr, value) {
mstore(addr, <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 +1215,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>(size) -> memPtr {
memPtr := <alloc>(<allocSize>(size))
<?dynamic>
mstore(memPtr, size)
</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,6 +1345,27 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Array:
if (_from == _to)
{
body = "converted := value";
break;
}
else
{
ArrayType const& from = dynamic_cast<decltype(from)>(_from);
ArrayType const& to = dynamic_cast<decltype(to)> (_to);

ReferenceType const& fromNoPtr =
*TypeProvider::withLocation(&from, from.location(), false);
ReferenceType const& toNoPtr =
*TypeProvider::withLocation(&to, to.location(), false);

if (fromNoPtr == toNoPtr)
{
body = "converted := value";
break;
}
}
solUnimplementedAssert(false, "Array conversion not implemented.");
break;
case Type::Category::Struct:
Expand Down
19 changes: 19 additions & 0 deletions libsolidity/codegen/YulUtilFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,16 @@ 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
/// signature: (baseRef, index) -> address
std::string memoryArrayIndexAccessFunction(ArrayType const& _type);

/// @returns the name of a function that returns the calldata memory 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 +172,8 @@ class YulUtilFunctions
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
std::string readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes);

std::string readFromMemory(Type const& _type, bool _fromCalldata, bool _padToWords);

/// @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 +188,11 @@ 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 will write the given value to
/// the specified address.
/// signature: (address, value) ->
std::string updateMemoryValueFunction(Type const& _type, bool _pad);

/// 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 +214,8 @@ class YulUtilFunctions
/// Return value: pointer
std::string allocationFunction();

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
44 changes: 37 additions & 7 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,11 @@ 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,11 +865,27 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
break;
}
case DataLocation::Memory:
solUnimplementedAssert(false, "");
break;
case DataLocation::CallData:
solUnimplementedAssert(false, "");
{
string const memAddress =
(arrayType.location() == DataLocation::Memory ?
m_utils.memoryArrayIndexAccessFunction(arrayType) :
m_utils.calldataArrayIndexAccessFunction(arrayType)) +
"(" +
m_context.variable(_indexAccess.baseExpression()) +
", " +
m_context.variable(*_indexAccess.indexExpression()) +
")";

setLValue(_indexAccess, make_unique<IRMemoryItem>(
m_context,
memAddress,
arrayType.location() == DataLocation::CallData,
true,
*arrayType.baseType()
));
break;
}
}

}
Expand Down
Loading

0 comments on commit 24a97c3

Please sign in to comment.