Merge pull request #6655 from ethereum/storageMappings

[SolYul] Storage mappings
This commit is contained in:
chriseth 2019-05-08 20:38:46 +02:00 committed by GitHub
commit 4d460915f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 0 deletions

View File

@ -539,6 +539,48 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType)
{
solAssert(_keyType.sizeOnStack() <= 1, "");
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
if (_mappingType.keyType()->isDynamicallySized())
return Whiskers(R"(
function <functionName>(slot <comma> <key>) -> dataSlot {
dataSlot := <hash>(slot <comma> <key>)
}
)")
("functionName", functionName)
("key", _keyType.sizeOnStack() > 0 ? "key" : "")
("comma", _keyType.sizeOnStack() > 0 ? "," : "")
("hash", packedHashFunction(
{&_keyType, TypeProvider::uint256()},
{_mappingType.keyType(), TypeProvider::uint256()}
))
.render();
else
{
solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
solAssert(!_mappingType.keyType()->isDynamicallyEncoded(), "");
solAssert(_mappingType.keyType()->calldataEncodedSize(false) <= 0x20, "");
Whiskers templ(R"(
function <functionName>(slot <key>) -> dataSlot {
mstore(0, <convertedKey>)
mstore(0x20, slot)
dataSlot := keccak256(0, 0x40)
}
)");
templ("functionName", functionName);
templ("key", _keyType.sizeOnStack() == 1 ? ", key" : "");
if (_keyType.sizeOnStack() == 0)
templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "()");
else
templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "(key)");
return templ.render();
}
});
}
string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{

View File

@ -35,6 +35,7 @@ namespace solidity
class Type;
class ArrayType;
class MappingType;
/**
* Component that can generate various useful Yul functions.
@ -98,6 +99,11 @@ public:
/// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot.
std::string nextArrayElementFunction(ArrayType const& _type);
/// @returns the name of a function that performs index access for mappings.
/// @param _mappingType the type of the mapping
/// @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.
/// @param _splitFunctionTypes if false, returns the address and function signature in a

View File

@ -614,6 +614,51 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
return false;
}
void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
{
Type const& baseType = *_indexAccess.baseExpression().annotation().type;
if (baseType.category() == Type::Category::Mapping)
{
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
MappingType const& mappingType = dynamic_cast<MappingType const&>(baseType);
Type const& keyType = *_indexAccess.indexExpression()->annotation().type;
solAssert(keyType.sizeOnStack() <= 1, "");
string slot = m_context.newYulVariable();
Whiskers templ("let <slot> := <indexAccess>(<base> <key>)\n");
templ("slot", slot);
templ("indexAccess", m_utils.mappingIndexAccessFunction(mappingType, keyType));
templ("base", m_context.variable(_indexAccess.baseExpression()));
if (keyType.sizeOnStack() == 0)
templ("key", "");
else
templ("key", ", " + m_context.variable(*_indexAccess.indexExpression()));
m_code << templ.render();
setLValue(_indexAccess, make_unique<IRStorageItem>(
m_code,
m_context,
slot,
0,
*_indexAccess.annotation().type
));
}
else if (baseType.category() == Type::Category::Array)
solUnimplementedAssert(false, "");
else if (baseType.category() == Type::Category::FixedBytes)
solUnimplementedAssert(false, "");
else if (baseType.category() == Type::Category::TypeType)
{
solAssert(baseType.sizeOnStack() == 0, "");
solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, "");
// no-op - this seems to be a lone array type (`structType[];`)
}
else
solAssert(false, "Index access only allowed for mappings or arrays.");
}
void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
{
Declaration const* declaration = _identifier.annotation().referencedDeclaration;

View File

@ -57,6 +57,7 @@ public:
void endVisit(FunctionCall const& _funCall) override;
void endVisit(MemberAccess const& _memberAccess) override;
bool visit(InlineAssembly const& _inlineAsm) override;
void endVisit(IndexAccess const& _indexAccess) override;
void endVisit(Identifier const& _identifier) override;
bool visit(Literal const& _literal) override;

View File

@ -60,6 +60,19 @@ IRStorageItem::IRStorageItem(
m_offset = offset;
}
IRStorageItem::IRStorageItem(
ostream& _code,
IRGenerationContext& _context,
string _slot,
unsigned _offset,
Type const& _type
):
IRLValue(_code, _context, &_type),
m_slot(move(_slot)),
m_offset(_offset)
{
}
string IRStorageItem::retrieveValue() const
{
if (!m_type->isValueType())

View File

@ -82,6 +82,13 @@ public:
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
);
IRStorageItem(
std::ostream& _code,
IRGenerationContext& _context,
std::string _slot,
unsigned _offset,
Type const& _type
);
std::string retrieveValue() const override;
void storeValue(std::string const& _value, Type const& _type) const override;

View File

@ -0,0 +1,39 @@
contract C {
mapping(uint => uint) simple;
mapping(uint16 => uint) cleanup;
mapping(string => uint) str;
mapping(uint => mapping(uint => uint)) twodim;
function test_simple(uint _off) public returns (uint _a, uint _b, uint _c) {
simple[_off + 2] = 3;
simple[_off + 3] = 4;
simple[uint(-1)] = 5;
_c = simple[uint(-1)];
_b = simple[3 + _off];
_a = simple[2 + _off];
}
function test_cleanup() public returns (bool) {
uint16 x;
assembly { x := 0xffff0001 }
cleanup[x] = 3;
return cleanup[1] == 3;
}
function test_str() public returns (bool) {
str["abc"] = 3;
string memory s = "abc";
return str[s] == 3;
}
function test_twodim() public returns (uint a, uint b) {
twodim[2][3] = 3;
a = twodim[3][2];
b = twodim[2][3];
}
}
// ====
// compileViaYul: true
// ----
// test_simple(uint256): 0 -> 3, 4, 5
// test_simple(uint256): 1 -> 3, 4, 5
// test_simple(uint256): 2 -> 3, 4, 5
// test_cleanup() -> true
// test_str() -> true
// test_twodim() -> 0, 3