[Sol->Yul] Implement getters.

This commit is contained in:
chriseth 2020-05-28 17:39:08 +02:00
parent 8ab8d5b1b0
commit 37e8d78cff
6 changed files with 179 additions and 85 deletions

View File

@ -1276,7 +1276,8 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_" +
string(_splitFunctionTypes ? "split_" : "") +
@ -1299,7 +1300,8 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_dynamic" +
string(_splitFunctionTypes ? "split_" : "") +
@ -1429,7 +1431,8 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"extract_from_storage_value_dynamic" +
@ -1474,7 +1477,8 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs
string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
solUnimplementedAssert(!_splitFunctionTypes, "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {

View File

@ -268,101 +268,145 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
{
string functionName = IRNames::function(_varDecl);
return m_context.functionCollector().createFunction(functionName, [&]() {
Type const* type = _varDecl.annotation().type;
Type const* type = _varDecl.annotation().type;
solAssert(_varDecl.isStateVariable(), "");
if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
return m_context.functionCollector().createFunction(functionName, [&]() {
solAssert(!_varDecl.isConstant() && !_varDecl.immutable(), "");
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
solAssert(slot_offset.second == 0, "");
FunctionType funType(_varDecl);
solUnimplementedAssert(funType.returnParameterTypes().size() == 1, "");
TypePointer returnType = funType.returnParameterTypes().front();
unsigned num_keys = 0;
stringstream indexAccesses;
string slot = m_context.newYulVariable();
do
{
solUnimplementedAssert(
mappingType->keyType()->sizeOnStack() == 1,
"Multi-slot mapping key unimplemented - might not be a problem"
);
indexAccesses <<
slot <<
" := " <<
m_utils.mappingIndexAccessFunction(*mappingType, *mappingType->keyType()) <<
"(" <<
slot;
if (mappingType->keyType()->sizeOnStack() > 0)
indexAccesses <<
", " <<
suffixedVariableNameList("key", num_keys, num_keys + mappingType->keyType()->sizeOnStack());
indexAccesses << ")\n";
num_keys += mappingType->keyType()->sizeOnStack();
}
while ((mappingType = dynamic_cast<MappingType const*>(mappingType->valueType())));
solAssert(_varDecl.isStateVariable(), "");
FunctionType accessorType(_varDecl);
TypePointers paramTypes = accessorType.parameterTypes();
if (_varDecl.immutable())
{
solAssert(paramTypes.empty(), "");
solUnimplementedAssert(type->sizeOnStack() == 1, "");
return Whiskers(R"(
function <functionName>(<keys>) -> rval {
let <slot> := <base>
<indexAccesses>
rval := <readStorage>(<slot>)
function <functionName>() -> rval {
rval := loadimmutable("<id>")
}
)")
("functionName", functionName)
("keys", suffixedVariableNameList("key", 0, num_keys))
("readStorage", m_utils.readFromStorage(*returnType, 0, false))
("indexAccesses", indexAccesses.str())
("slot", slot)
("base", slot_offset.first.str())
("id", to_string(_varDecl.id()))
.render();
});
else
{
solUnimplementedAssert(type->isValueType(), "");
}
else if (_varDecl.isConstant())
{
solAssert(paramTypes.empty(), "");
return Whiskers(R"(
function <functionName>() -> <ret> {
<ret> := <constantValueFunction>()
}
)")
("functionName", functionName)
("constantValueFunction", IRGeneratorForStatements(m_context, m_utils).constantValueFunction(_varDecl))
("ret", suffixedVariableNameList("ret_", 0, _varDecl.type()->sizeOnStack()))
.render();
}
return m_context.functionCollector().createFunction(functionName, [&]() {
if (_varDecl.immutable())
string code;
auto const& location = m_context.storageLocationOfVariable(_varDecl);
code += Whiskers(R"(
let slot := <slot>
let offset := <offset>
)")
("slot", location.first.str())
("offset", to_string(location.second))
.render();
if (!paramTypes.empty())
solAssert(
location.second == 0,
"If there are parameters, we are dealing with structs or mappings and thus should have offset zero."
);
// The code of an accessor is of the form `x[a][b][c]` (it is slightly more complicated
// if the final type is a struct).
// In each iteration of the loop below, we consume one parameter, perform an
// index access, reassign the yul variable `slot` and move @a currentType further "down".
// The initial value of @a currentType is only used if we skip the loop completely.
TypePointer currentType = _varDecl.annotation().type;
vector<string> parameters;
vector<string> returnVariables;
for (size_t i = 0; i < paramTypes.size(); ++i)
{
MappingType const* mappingType = dynamic_cast<MappingType const*>(currentType);
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(currentType);
solAssert(mappingType || arrayType, "");
vector<string> keys = IRVariable("key_" + to_string(i),
mappingType ? *mappingType->keyType() : *TypeProvider::uint256()
).stackSlots();
parameters += keys;
code += Whiskers(R"(
slot<?array>, offset</array> := <indexAccess>(slot<?+keys>, <keys></+keys>)
)")
(
"indexAccess",
mappingType ?
m_utils.mappingIndexAccessFunction(*mappingType, *mappingType->keyType()) :
m_utils.storageArrayIndexAccessFunction(*arrayType)
)
("array", arrayType != nullptr)
("keys", joinHumanReadable(keys))
.render();
currentType = mappingType ? mappingType->valueType() : arrayType->baseType();
}
auto returnTypes = accessorType.returnParameterTypes();
solAssert(returnTypes.size() >= 1, "");
if (StructType const* structType = dynamic_cast<StructType const*>(currentType))
{
solAssert(location.second == 0, "");
auto const& names = accessorType.returnParameterNames();
for (size_t i = 0; i < names.size(); ++i)
{
solUnimplementedAssert(type->sizeOnStack() == 1, "");
return Whiskers(R"(
function <functionName>() -> rval {
rval := loadimmutable("<id>")
}
if (returnTypes[i]->category() == Type::Category::Mapping)
continue;
if (auto arrayType = dynamic_cast<ArrayType const*>(returnTypes[i]))
if (!arrayType->isByteArray())
continue;
// TODO conversion from storage byte arrays is not yet implemented.
pair<u256, unsigned> const& offsets = structType->storageOffsetsOfMember(names[i]);
vector<string> retVars = IRVariable("ret_" + to_string(returnVariables.size()), *returnTypes[i]).stackSlots();
returnVariables += retVars;
code += Whiskers(R"(
<ret> := <readStorage>(add(slot, <slotOffset>))
)")
("functionName", functionName)
("id", to_string(_varDecl.id()))
("ret", joinHumanReadable(retVars))
("readStorage", m_utils.readFromStorage(*returnTypes[i], offsets.second, true))
("slotOffset", offsets.first.str())
.render();
}
else if (_varDecl.isConstant())
return Whiskers(R"(
function <functionName>() -> <ret> {
<ret> := <constantValueFunction>()
}
)")
("functionName", functionName)
("constantValueFunction", IRGeneratorForStatements(m_context, m_utils).constantValueFunction(_varDecl))
("ret", suffixedVariableNameList("ret_", 0, _varDecl.type()->sizeOnStack()))
.render();
else
{
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
}
else
{
solAssert(returnTypes.size() == 1, "");
vector<string> retVars = IRVariable("ret", *returnTypes.front()).stackSlots();
returnVariables += retVars;
// TODO conversion from storage byte arrays is not yet implemented.
code += Whiskers(R"(
<ret> := <readStorage>(slot, offset)
)")
("ret", joinHumanReadable(retVars))
("readStorage", m_utils.readFromStorageDynamic(*returnTypes.front(), true))
.render();
}
return Whiskers(R"(
function <functionName>() -> rval {
rval := <readStorage>(<slot>)
}
)")
("functionName", functionName)
("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false))
("slot", slot_offset.first.str())
.render();
return Whiskers(R"(
function <functionName>(<params>) -> <retVariables> {
<code>
}
});
}
)")
("functionName", functionName)
("params", joinHumanReadable(parameters))
("retVariables", joinHumanReadable(returnVariables))
("code", std::move(code))
.render();
});
}
string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDecl)

View File

@ -0,0 +1,14 @@
contract C {
uint8[][2] public a;
constructor() public {
a[1].push(3);
a[1].push(4);
}
}
// ====
// compileViaYul: also
// ----
// a(uint256,uint256): 0, 0 -> FAILURE
// a(uint256,uint256): 1, 0 -> 3
// a(uint256,uint256): 1, 1 -> 4
// a(uint256,uint256): 2, 0 -> FAILURE

View File

@ -0,0 +1,11 @@
contract C {
mapping(uint => mapping(uint => uint)) public x;
constructor() public {
x[1][2] = 3;
}
}
// ====
// compileViaYul: also
// ----
// x(uint256,uint256): 1, 2 -> 3
// x(uint256,uint256): 0, 0 -> 0

View File

@ -0,0 +1,19 @@
contract C {
uint8 public a;
uint16 public b;
uint128 public c;
uint public d;
constructor() public {
a = 3;
b = 4;
c = 5;
d = 6;
}
}
// ====
// compileViaYul: also
// ----
// a() -> 3
// b() -> 4
// c() -> 5
// d() -> 6

View File

@ -8,6 +8,8 @@ contract test {
dynamicData[2][2] = 8;
}
}
// ====
// compileViaYul: also
// ----
// data(uint256,uint256): 2, 2 -> 8
// data(uint256,uint256): 2, 8 -> FAILURE # NB: the original code contained a bug here #