diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 91089115f..5431436c4 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -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 (slot ) -> dataSlot { + dataSlot := (slot ) + } + )") + ("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 (slot ) -> dataSlot { + mstore(0, ) + 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) { diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index fd1e92ea4..9150d2bbd 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -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 diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 32c574a7e..90f73aa2e 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -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(baseType); + Type const& keyType = *_indexAccess.indexExpression()->annotation().type; + solAssert(keyType.sizeOnStack() <= 1, ""); + + string slot = m_context.newYulVariable(); + Whiskers templ("let := ( )\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( + 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; diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index a3737efcd..396490d19 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -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; diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp index 3c97992ff..c23517a03 100644 --- a/libsolidity/codegen/ir/IRLValue.cpp +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -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()) diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h index f5a980935..b44a94730 100644 --- a/libsolidity/codegen/ir/IRLValue.h +++ b/libsolidity/codegen/ir/IRLValue.h @@ -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; diff --git a/test/libsolidity/semanticTests/viaYul/storage/mappings.sol b/test/libsolidity/semanticTests/viaYul/storage/mappings.sol new file mode 100644 index 000000000..4713a42d3 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/storage/mappings.sol @@ -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