Merge pull request #9602 from ethereum/structMemToStorageSol2Yul

[Sol->Yul] Implementing various copying of structs
This commit is contained in:
chriseth 2020-09-02 13:50:46 +02:00 committed by GitHub
commit 3a48be546d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 394 additions and 70 deletions

View File

@ -2,7 +2,7 @@
Language Features:
* Allow function definitions outside of contracts, behaving much like internal library functions.
* Code generator: Implementing copying structs from calldata to storage.
Compiler Features:
* SMTChecker: Add underflow and overflow as verification conditions in the CHC engine.

View File

@ -357,7 +357,15 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
"Struct assignment with conversion."
);
solAssert(!structType.containsNestedMapping(), "");
solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported.");
if (sourceType.location() == DataLocation::CallData)
{
solAssert(sourceType.sizeOnStack() == 1, "");
solAssert(structType.sizeOnStack() == 1, "");
m_context << Instruction::DUP2 << Instruction::DUP2;
m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, 0), 2, 0);
}
else
{
for (auto const& member: structType.members(nullptr))
{
// assign each member that can live outside of storage
@ -390,6 +398,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
}
}
// stack layout: source_ref target_ref
solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");
if (_move)

View File

@ -964,7 +964,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
("dataAreaFunction", arrayDataAreaFunction(_type))
("isByteArray", _type.isByteArray())
("indexAccess", storageArrayIndexAccessFunction(_type))
("storeValue", updateStorageValueFunction(*_type.baseType()))
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
("shl", shiftLeftFunctionDynamic())
("shr", shiftRightFunction(248))
@ -994,7 +994,7 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
("functionName", functionName)
("fetchLength", arrayLengthFunction(_type))
("indexAccess", storageArrayIndexAccessFunction(_type))
("storeValue", updateStorageValueFunction(*_type.baseType()))
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
("zeroValueFunction", zeroValueFunction(*_type.baseType()))
.render();
@ -1404,6 +1404,25 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
if (_type.isValueType())
return readFromStorageValueType(_type, _offset, _splitFunctionTypes);
else
{
solAssert(_offset == 0, "");
return readFromStorageReferenceType(_type);
}
}
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
return readFromStorageValueTypeDynamic(_type, _splitFunctionTypes);
}
string YulUtilFunctions::readFromStorageValueType(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
@ -1413,6 +1432,7 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
to_string(_offset) +
"_" +
_type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"(
@ -1425,13 +1445,14 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
.render();
});
}
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
string YulUtilFunctions::readFromStorageValueTypeDynamic(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_dynamic" +
"read_from_storage_value_type_dynamic" +
string(_splitFunctionTypes ? "split_" : "") +
"_" +
_type.identifier();
@ -1447,6 +1468,55 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
.render();
});
}
string YulUtilFunctions::readFromStorageReferenceType(Type const& _type)
{
solUnimplementedAssert(_type.category() == Type::Category::Struct, "");
string functionName = "read_from_storage_reference_type_" + _type.identifier();
auto const& structType = dynamic_cast<StructType const&>(_type);
solAssert(structType.location() == DataLocation::Memory, "");
MemberList::MemberMap structMembers = structType.nativeMembers(nullptr);
vector<map<string, string>> memberSetValues(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
auto const& [memberSlotDiff, memberStorageOffset] = structType.storageOffsetsOfMember(structMembers[i].name);
memberSetValues[i]["setMember"] = Whiskers(R"(
{
let <memberValues> := <readFromStorage>(add(slot, <memberSlotDiff>)<?hasOffset>, <memberStorageOffset></hasOffset>)
<writeToMemory>(add(value, <memberMemoryOffset>), <memberValues>)
}
)")
("memberValues", suffixedVariableNameList("memberValue_", 0, structMembers[i].type->stackItems().size()))
("memberMemoryOffset", structType.memoryOffsetOfMember(structMembers[i].name).str())
("memberSlotDiff", memberSlotDiff.str())
("memberStorageOffset", to_string(memberStorageOffset))
("readFromStorage",
structMembers[i].type->isValueType() ?
readFromStorageDynamic(*structMembers[i].type, true) :
readFromStorage(*structMembers[i].type, memberStorageOffset, true)
)
("writeToMemory", writeToMemoryFunction(*structMembers[i].type))
("hasOffset", structMembers[i].type->isValueType())
.render();
}
return m_functionCollector.createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot) -> value {
value := <allocStruct>()
<#member>
<setMember>
</member>
}
)")
("functionName", functionName)
("allocStruct", allocateMemoryStructFunction(structType))
("member", memberSetValues)
.render();
});
}
string YulUtilFunctions::readFromMemory(Type const& _type)
{
@ -1458,18 +1528,25 @@ string YulUtilFunctions::readFromCalldata(Type const& _type)
return readFromMemoryOrCalldata(_type, true);
}
string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::optional<unsigned> const& _offset)
string YulUtilFunctions::updateStorageValueFunction(
Type const& _fromType,
Type const& _toType,
std::optional<unsigned> const& _offset
)
{
string const functionName =
"update_storage_value_" +
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
_type.identifier();
_fromType.identifier() +
"_to_" +
_toType.identifier();
return m_functionCollector.createFunction(functionName, [&] {
if (_type.isValueType())
if (_toType.isValueType())
{
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(_type.storageBytes() > 0, "Invalid storage bytes size.");
solAssert(_fromType.isImplicitlyConvertibleTo(_toType), "");
solAssert(_toType.storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(_toType.storageBytes() > 0, "Invalid storage bytes size.");
return Whiskers(R"(
function <functionName>(slot, <offset>value) {
@ -1480,19 +1557,83 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::opti
("functionName", functionName)
("update",
_offset.has_value() ?
updateByteSliceFunction(_type.storageBytes(), *_offset) :
updateByteSliceFunctionDynamic(_type.storageBytes())
updateByteSliceFunction(_toType.storageBytes(), *_offset) :
updateByteSliceFunctionDynamic(_toType.storageBytes())
)
("offset", _offset.has_value() ? "" : "offset, ")
("prepare", prepareStoreFunction(_type))
("prepare", prepareStoreFunction(_toType))
.render();
}
else
{
if (_type.category() == Type::Category::Array)
solUnimplementedAssert(false, "");
else if (_type.category() == Type::Category::Struct)
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
solAssert(fromReferenceType && toReferenceType, "");
solAssert(*toReferenceType->copyForLocation(
fromReferenceType->location(),
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
if (_toType.category() == Type::Category::Array)
solUnimplementedAssert(false, "");
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, "");
Whiskers templ(R"(
function <functionName>(slot, value) {
<#member>
{
<updateMemberCall>
}
</member>
}
)");
templ("functionName", functionName);
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
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(
"memberValue_",
0,
structMembers[i].type->stackItems().size()
))
("hasOffset", structMembers[i].type->isValueType())
(
"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",
fromCalldata ?
to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) :
fromStructType.memoryOffsetOfMember(structMembers[i].name).str()
)
("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata))
.render();
}
templ("member", memberParams);
return templ.render();
}
else
solAssert(false, "Invalid non-value type for assignment.");
}
@ -2052,13 +2193,27 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
solUnimplementedAssert(!fromStructType.isDynamicallyEncoded(), "");
solUnimplementedAssert(toStructType.location() == DataLocation::Memory, "");
solUnimplementedAssert(fromStructType.location() == DataLocation::CallData, "");
solUnimplementedAssert(fromStructType.location() != DataLocation::Memory, "");
if (fromStructType.location() == DataLocation::CallData)
{
body = Whiskers(R"(
converted := <abiDecode>(value, calldatasize())
)")("abiDecode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleDecoder(
{&toStructType}
)).render();
}
else
{
solAssert(fromStructType.location() == DataLocation::Storage, "");
body = Whiskers(R"(
converted := <readFromStorage>(value)
)")
("readFromStorage", readFromStorage(toStructType, 0, true))
.render();
}
break;
}
case Type::Category::FixedBytes:
@ -2490,7 +2645,7 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
}
)")
("functionName", functionName)
("store", updateStorageValueFunction(_type))
("store", updateStorageValueFunction(_type, _type))
("zeroValue", zeroValueFunction(_type))
.render();
else if (_type.category() == Type::Category::Array)

View File

@ -221,8 +221,7 @@ public:
/// @param _keyType the type of the value provided
std::string mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType);
/// @returns a function that reads a value type from storage.
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
/// @returns a function that reads a type from storage.
/// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable.
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
@ -248,7 +247,11 @@ public:
/// the specified slot and offset. If offset is not given, it is expected as
/// runtime parameter.
/// signature: (slot, [offset,] value)
std::string updateStorageValueFunction(Type const& _type, std::optional<unsigned> const& _offset = std::optional<unsigned>());
std::string updateStorageValueFunction(
Type const& _fromType,
Type const& _toType,
std::optional<unsigned> const& _offset = std::optional<unsigned>()
);
/// Returns the name of a function that will write the given value to
/// the specified address.
@ -401,6 +404,16 @@ private:
std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata);
/// @returns a function that reads a value type from storage.
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
/// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable.
std::string readFromStorageValueType(Type const& _type, size_t _offset, bool _splitFunctionTypes);
std::string readFromStorageValueTypeDynamic(Type const& _type, bool _splitFunctionTypes);
/// @returns a function that reads a reference type from storage to memory (performing a deep copy).
std::string readFromStorageReferenceType(Type const& _type);
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
MultiUseYulFunctionCollector& m_functionCollector;

View File

@ -314,9 +314,9 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
writeToLValue(*m_currentLValue, value);
m_currentLValue.reset();
if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
if (m_currentLValue->type.category() != Type::Category::Struct && *_assignment.annotation().type != *TypeProvider::emptyTuple())
define(_assignment, value);
m_currentLValue.reset();
return false;
}
@ -2506,7 +2506,7 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
offset = std::get<unsigned>(_storage.offset);
m_code <<
m_utils.updateStorageValueFunction(_lvalue.type, offset) <<
m_utils.updateStorageValueFunction(_value.type(), _lvalue.type, offset) <<
"(" <<
_storage.slot <<
(

View File

@ -4,15 +4,16 @@ contract C {
struct S {
uint256 a;
uint256 b;
bytes2 c;
}
function f(S calldata s) external pure returns (uint256, uint256) {
function f(S calldata s) external pure returns (uint256, uint256, byte) {
S memory m = s;
return (m.a, m.b);
return (m.a, m.b, m.c[1]);
}
}
// ====
// compileViaYul: also
// ----
// f((uint256,uint256)): 42, 23 -> 42, 23
// f((uint256,uint256, bytes2)): 42, 23, "ab" -> 42, 23, "b"

View File

@ -0,0 +1,22 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint256 a;
uint64 b;
bytes2 c;
}
uint[153] r;
S s;
function f(uint32 a, S calldata c, uint256 b) external returns (uint256, uint256, byte) {
s = c;
return (s.a, s.b, s.c[1]);
}
}
// ====
// compileViaYul: also
// ----
// f(uint32, (uint256, uint64, bytes2), uint256): 1, 42, 23, "ab", 1 -> 42, 23, "b"

View File

@ -3,6 +3,7 @@ contract c {
uint256 a;
uint256 b;
}
uint[75] r;
Struct data1;
Struct data2;
@ -15,5 +16,7 @@ contract c {
}
}
// ====
// compileViaYul: also
// ----
// test() -> true

View File

@ -0,0 +1,28 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint32 a;
uint128 b;
uint256 c;
}
struct X {
uint256 a;
S s;
}
uint[79] r;
X x;
function f() external returns (uint32, uint128, uint256) {
X memory m = X(12, S(42, 23, 34));
x = m;
return (x.s.a, x.s.b, x.s.c);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 42, 23, 34

View File

@ -0,0 +1,33 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint32 a;
uint128 b;
uint256 c;
function() internal returns (uint32) f;
}
struct X {
uint256 a;
S s;
}
uint[79] r;
X x;
function f() external returns (uint32, uint128, uint256, uint32, uint32) {
X memory m = X(12, S(42, 23, 34, g));
x = m;
return (x.s.a, x.s.b, x.s.c, x.s.f(), m.s.f());
}
function g() internal returns (uint32) {
return x.s.a;
}
}
// ====
// compileViaYul: false
// ----
// f() -> 42, 23, 34, 42, 42

View File

@ -10,5 +10,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// s() -> 1, true

View File

@ -0,0 +1,26 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint32 a;
uint128 b;
uint256 c;
}
struct X {
uint32 a;
S s;
}
uint[79] arr;
X x = X(12, S(42, 23, 34));
function f() external returns (uint32, uint128, uint256) {
X memory m = x;
return (m.s.a, m.s.b, m.s.c);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 42, 23, 34

View File

@ -0,0 +1,32 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint32 a;
uint128 b;
uint256 c;
function() internal returns (uint32) f;
}
struct X {
uint256 a;
S s;
}
uint[79] arr;
X x = X(12, S(42, 23, 34, g));
function f() external returns (uint32, uint128, uint256, uint32, uint32) {
X memory m = x;
return (m.s.a, m.s.b, m.s.c, m.s.f(), x.s.f());
}
function g() internal returns (uint32) {
return x.s.a;
}
}
// ====
// compileViaYul: false
// ----
// f() -> 42, 23, 34, 42, 42