Merge pull request #10033 from ethereum/develop

Merge develop into breaking
This commit is contained in:
chriseth
2020-10-14 14:12:20 +02:00
committed by GitHub
146 changed files with 1380 additions and 595 deletions
+10 -1
View File
@@ -170,7 +170,16 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
// pop offset
m_context << Instruction::POP;
utils().copyToStackTop(paramTypes.size() - i + 1, 1);
ArrayUtils(m_context).accessIndex(*arrayType);
ArrayUtils(m_context).retrieveLength(*arrayType, 1);
// Stack: ref [length] index length
// check out-of-bounds access
m_context << Instruction::DUP2 << Instruction::LT;
auto tag = m_context.appendConditionalJump();
m_context << u256(0) << Instruction::DUP1 << Instruction::REVERT;
m_context << tag;
ArrayUtils(m_context).accessIndex(*arrayType, false);
returnType = arrayType->baseType();
}
else
+5 -4
View File
@@ -41,16 +41,17 @@ ReturnInfo::ReturnInfo(EVMVersion const& _evmVersion, FunctionType const& _funct
returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
for (auto const& retType: returnTypes)
if (retType->isDynamicallyEncoded())
{
solAssert(retType->decodingType(), "");
if (retType->decodingType()->isDynamicallyEncoded())
{
solAssert(haveReturndatacopy, "");
dynamicReturnSize = true;
estimatedReturnSize = 0;
break;
}
else if (retType->decodingType())
estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
else
estimatedReturnSize += retType->calldataEncodedSize();
estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
}
}
}
+178 -28
View File
@@ -918,7 +918,7 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
string functionName = "array_length_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(value) -> length {
function <functionName>(value<?dynamic><?calldata>, len</calldata></dynamic>) -> length {
<?dynamic>
<?memory>
length := mload(value)
@@ -929,6 +929,9 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
length := <extractByteArrayLength>(length)
</byteArray>
</storage>
<?calldata>
length := len
</calldata>
<!dynamic>
length := <length>
</dynamic>
@@ -940,17 +943,14 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
w("length", toCompactHexWithPrefix(_type.length()));
w("memory", _type.location() == DataLocation::Memory);
w("storage", _type.location() == DataLocation::Storage);
w("calldata", _type.location() == DataLocation::CallData);
if (_type.location() == DataLocation::Storage)
{
w("byteArray", _type.isByteArray());
if (_type.isByteArray())
w("extractByteArrayLength", extractByteArrayLengthFunction());
}
if (_type.isDynamicallySized())
solAssert(
_type.location() != DataLocation::CallData,
"called regular array length function on calldata array"
);
return w.render();
});
}
@@ -1295,6 +1295,105 @@ string YulUtilFunctions::clearStorageStructFunction(StructType const& _type)
});
}
string YulUtilFunctions::copyArrayToStorage(ArrayType const& _fromType, ArrayType const& _toType)
{
solAssert(
*_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast<ReferenceType const&>(_toType),
""
);
solUnimplementedAssert(!_fromType.isByteArray(), "");
solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), "");
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"(
function <functionName>(slot, value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) {
let length := <arrayLength>(value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>)
<?isToDynamic>
<resizeArray>(slot, length)
</isToDynamic>
let srcPtr :=
<?isFromMemoryDynamic>
add(value, 0x20)
<!isFromMemoryDynamic>
value
</isFromMemoryDynamic>
let elementSlot := <dstDataLocation>(slot)
let elementOffset := 0
for { let i := 0 } lt(i, length) {i := add(i, 1)} {
<?fromCalldata>
let <elementValues> :=
<?dynamicallyEncodedBase>
<accessCalldataTail>(value, srcPtr)
<!dynamicallyEncodedBase>
srcPtr
</dynamicallyEncodedBase>
<?isValueType>
<elementValues> := <readFromCalldataOrMemory>(<elementValues>)
</isValueType>
</fromCalldata>
<?fromMemory>
let <elementValues> := <readFromCalldataOrMemory>(srcPtr)
</fromMemory>
<updateStorageValue>(elementSlot<?isValueType>, elementOffset</isValueType>, <elementValues>)
srcPtr := add(srcPtr, <stride>)
<?multipleItemsPerSlot>
elementOffset := add(elementOffset, <storageStride>)
if gt(elementOffset, sub(32, <storageStride>)) {
elementOffset := 0
elementSlot := add(elementSlot, 1)
}
<!multipleItemsPerSlot>
elementSlot := add(elementSlot, <storageSize>)
elementOffset := 0
</multipleItemsPerSlot>
}
}
)");
templ("functionName", functionName);
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata);
templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory));
templ("fromCalldata", fromCalldata);
templ("isToDynamic", _toType.isDynamicallySized());
templ("isFromMemoryDynamic", _fromType.isDynamicallySized() && _fromType.dataStoredIn(DataLocation::Memory));
if (fromCalldata)
{
templ("dynamicallySizedBase", _fromType.baseType()->isDynamicallySized());
templ("dynamicallyEncodedBase", _fromType.baseType()->isDynamicallyEncoded());
if (_fromType.baseType()->isDynamicallyEncoded())
templ("accessCalldataTail", accessCalldataTailFunction(*_fromType.baseType()));
}
if (_toType.isDynamicallySized())
templ("resizeArray", resizeDynamicArrayFunction(_toType));
templ("arrayLength",arrayLengthFunction(_fromType));
templ("isValueType", _fromType.baseType()->isValueType());
templ("dstDataLocation", arrayDataAreaFunction(_toType));
if (!fromCalldata || _fromType.baseType()->isValueType())
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata));
templ("elementValues", suffixedVariableNameList(
"elementValue_",
0,
_fromType.baseType()->stackItems().size()
));
templ("updateStorageValue", updateStorageValueFunction(*_fromType.baseType(), *_toType.baseType()));
templ("stride", to_string(fromCalldata ? _fromType.calldataStride() : _fromType.memoryStride()));
templ("multipleItemsPerSlot", _toType.storageStride() <= 16);
templ("storageStride", to_string(_toType.storageStride()));
templ("storageSize", _toType.baseType()->storageSize().str());
return templ.render();
});
}
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{
string functionName = "array_convert_length_to_size_" + _type.identifier();
@@ -1865,23 +1964,39 @@ string YulUtilFunctions::updateStorageValueFunction(
else
{
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
solAssert(fromReferenceType && toReferenceType, "");
solAssert(*toReferenceType->copyForLocation(
fromReferenceType->location(),
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
solUnimplementedAssert(fromReferenceType->location() != DataLocation::Storage, "");
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
if (_toType.category() == Type::Category::Array)
solUnimplementedAssert(false, "");
{
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <value>) {
<copyArrayToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("copyArrayToStorage", copyArrayToStorage(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
return templ.render();
}
else if (_toType.category() == Type::Category::Struct)
{
solAssert(_fromType.category() == Type::Category::Struct, "");
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
auto const& toStructType = dynamic_cast<StructType const&>(_toType);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solAssert(fromStructType.location() != DataLocation::Storage, "");
solUnimplementedAssert(_offset.has_value() && _offset.value() == 0, "");
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, value) {
@@ -1895,6 +2010,7 @@ string YulUtilFunctions::updateStorageValueFunction(
templ("functionName", functionName);
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
MemberList::MemberMap toStructMembers = toStructType.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
@@ -1902,31 +2018,65 @@ string YulUtilFunctions::updateStorageValueFunction(
solAssert(structMembers[i].type->memoryHeadSize() == 32, "");
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
memberParams[i]["updateMemberCall"] = Whiskers(R"(
let <memberValues> := <loadFromMemoryOrCalldata>(add(value, <memberOffset>))
<updateMember>(add(slot, <memberStorageSlotDiff>), <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
)")
("memberValues", suffixedVariableNameList(
Whiskers t(R"(
let memberSlot := add(slot, <memberStorageSlotDiff>)
<?fromCalldata>
<?dynamicallyEncodedMember>
let <memberCalldataOffset> := <accessCalldataTail>(value, add(value, <memberOffset>))
<!dynamicallyEncodedMember>
let <memberCalldataOffset> := add(value, <memberOffset>)
</dynamicallyEncodedMember>
<?isValueType>
let <memberValues> := <loadFromMemoryOrCalldata>(<memberCalldataOffset>)
<updateMember>(memberSlot, <memberStorageOffset>, <memberValues>)
<!isValueType>
<updateMember>(memberSlot, <memberCalldataOffset>)
</isValueType>
<!fromCalldata>
let memberMemoryOffset := add(value, <memberOffset>)
let <memberValues> := <loadFromMemoryOrCalldata>(memberMemoryOffset)
<updateMember>(memberSlot, <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
</fromCalldata>
)");
t("fromCalldata", fromCalldata);
if (fromCalldata)
{
t("memberCalldataOffset", suffixedVariableNameList(
"memberCalldataOffset_",
0,
structMembers[i].type->stackItems().size()
));
t("dynamicallyEncodedMember", structMembers[i].type->isDynamicallyEncoded());
if (structMembers[i].type->isDynamicallySized())
t("accessCalldataTail", accessCalldataTailFunction(*structMembers[i].type));
}
t("isValueType", structMembers[i].type->isValueType());
t("memberValues", suffixedVariableNameList(
"memberValue_",
0,
structMembers[i].type->stackItems().size()
))
("hasOffset", structMembers[i].type->isValueType())
(
));
t("hasOffset", structMembers[i].type->isValueType());
t(
"updateMember",
structMembers[i].type->isValueType() ?
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type) :
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type, offset)
)
("memberStorageSlotDiff", slotDiff.str())
("memberStorageOffset", to_string(offset))
("memberOffset",
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type) :
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type, offset)
);
t("memberStorageSlotDiff", slotDiff.str());
t("memberStorageOffset", to_string(offset));
t(
"memberOffset",
fromCalldata ?
to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) :
fromStructType.memoryOffsetOfMember(structMembers[i].name).str()
)
("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata))
.render();
);
if (!fromCalldata || structMembers[i].type->isValueType())
t("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata));
memberParams[i]["updateMemberCall"] = t.render();
}
templ("member", memberParams);
+4
View File
@@ -181,6 +181,10 @@ public:
/// signature: (slot) ->
std::string clearStorageArrayFunction(ArrayType const& _type);
/// @returns the name of a function that will copy array from calldata or memory to storage
/// signature (to_slot, from_ptr) ->
std::string copyArrayToStorage(ArrayType const& _fromType, ArrayType const& _toType);
/// Returns the name of a function that will convert a given length to the
/// size in memory (number of storage slots or calldata/memory bytes) it
/// will require.
+12 -5
View File
@@ -348,18 +348,25 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
mappingType ? *mappingType->keyType() : *TypeProvider::uint256()
).stackSlots();
parameters += keys;
code += Whiskers(R"(
Whiskers templ(R"(
<?array>
if iszero(lt(<keys>, <length>(slot))) { revert(0, 0) }
</array>
slot<?array>, offset</array> := <indexAccess>(slot<?+keys>, <keys></+keys>)
)")
(
)");
templ(
"indexAccess",
mappingType ?
m_utils.mappingIndexAccessFunction(*mappingType, *mappingType->keyType()) :
m_utils.storageArrayIndexAccessFunction(*arrayType)
)
("array", arrayType != nullptr)
("keys", joinHumanReadable(keys))
.render();
("keys", joinHumanReadable(keys));
if (arrayType)
templ("length", m_utils.arrayLengthFunction(*arrayType));
code += templ.render();
currentType = mappingType ? mappingType->valueType() : arrayType->baseType();
}
@@ -387,7 +387,11 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
writeToLValue(*m_currentLValue, value);
if (m_currentLValue->type.category() != Type::Category::Struct && *_assignment.annotation().type != *TypeProvider::emptyTuple())
if (
m_currentLValue->type.category() != Type::Category::Struct &&
m_currentLValue->type.category() != Type::Category::Array &&
*_assignment.annotation().type != *TypeProvider::emptyTuple()
)
define(_assignment, value);
m_currentLValue.reset();
@@ -1763,32 +1767,11 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
if (member == "length")
{
if (!type.isDynamicallySized())
define(_memberAccess) << type.length() << "\n";
else
switch (type.location())
{
case DataLocation::CallData:
define(_memberAccess, IRVariable(_memberAccess.expression()).part("length"));
break;
case DataLocation::Storage:
{
define(_memberAccess) <<
m_utils.arrayLengthFunction(type) <<
"(" <<
IRVariable(_memberAccess.expression()).commaSeparatedList() <<
")\n";
break;
}
case DataLocation::Memory:
define(_memberAccess) <<
"mload(" <<
IRVariable(_memberAccess.expression()).commaSeparatedList() <<
")\n";
break;
}
}
define(_memberAccess) <<
m_utils.arrayLengthFunction(type) <<
"(" <<
IRVariable(_memberAccess.expression()).commaSeparatedList() <<
")\n";
else if (member == "pop" || member == "push")
{
solAssert(type.location() == DataLocation::Storage, "");