From f0c5cdca9f2b8c8b6584e6af3c52481ddfcab5b1 Mon Sep 17 00:00:00 2001 From: Djordje Mijovic Date: Wed, 5 May 2021 08:57:19 +0200 Subject: [PATCH] [Sol->Yul] Adding util function to copy literal to storage. Co-authored-by: Daniel Kirchner Co-authored-by: chriseth --- libsolidity/ast/Types.cpp | 17 ---- libsolidity/ast/Types.h | 9 -- libsolidity/codegen/ExpressionCompiler.cpp | 46 +++++++++-- libsolidity/codegen/YulUtilFunctions.cpp | 70 ++++++++++++++-- libsolidity/codegen/YulUtilFunctions.h | 4 + .../codegen/ir/IRGeneratorForStatements.cpp | 82 +++++++++---------- .../array/push/nested_bytes_push.sol | 9 +- ...string_literal_assign_to_storage_bytes.sol | 22 +++++ 8 files changed, 176 insertions(+), 83 deletions(-) create mode 100644 test/libsolidity/semanticTests/array/string_literal_assign_to_storage_bytes.sol diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c4466e398..06d6a0fd9 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2613,23 +2613,6 @@ Type const* TupleType::mobileType() const return TypeProvider::tuple(move(mobiles)); } -Type const* TupleType::closestTemporaryType(Type const* _targetType) const -{ - solAssert(!!_targetType, ""); - TypePointers const& targetComponents = dynamic_cast(*_targetType).components(); - solAssert(components().size() == targetComponents.size(), ""); - TypePointers tempComponents(targetComponents.size()); - for (size_t i = 0; i < targetComponents.size(); ++i) - { - if (components()[i] && targetComponents[i]) - { - tempComponents[i] = components()[i]->closestTemporaryType(targetComponents[i]); - solAssert(tempComponents[i], ""); - } - } - return TypeProvider::tuple(move(tempComponents)); -} - FunctionType::FunctionType(FunctionDefinition const& _function, Kind _kind): m_kind(_kind), m_stateMutability(_function.stateMutability()), diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 95aa3150f..a8cb90d44 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -324,13 +324,6 @@ public: /// @returns true if this is a non-value type and the data of this type is stored at the /// given location. virtual bool dataStoredIn(DataLocation) const { return false; } - /// @returns the type of a temporary during assignment to a variable of the given type. - /// Specifically, returns the requested itself if it can be dynamically allocated (or is a value type) - /// and the mobile type otherwise. - virtual Type const* closestTemporaryType(Type const* _targetType) const - { - return _targetType->dataStoredIn(DataLocation::Storage) ? mobileType() : _targetType; - } /// Returns the list of all members of this type. Default implementation: no members apart from bound. /// @param _currentScope scope in which the members are accessed. @@ -1103,8 +1096,6 @@ public: u256 storageSize() const override; bool hasSimpleZeroValueInMemory() const override { return false; } Type const* mobileType() const override; - /// Converts components to their temporary types and performs some wildcard matching. - Type const* closestTemporaryType(Type const* _targetType) const override; std::vector const& components() const { return m_components; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a8e7dd5b7..5b092363b 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -49,6 +49,34 @@ using namespace solidity::frontend; using namespace solidity::langutil; using namespace solidity::util; +namespace +{ + +Type const* closestType(Type const* _type, Type const* _targetType, bool _isShiftOp) +{ + if (_isShiftOp) + return _type->mobileType(); + else if (auto const* tupleType = dynamic_cast(_type)) + { + solAssert(_targetType, ""); + TypePointers const& targetComponents = dynamic_cast(*_targetType).components(); + solAssert(tupleType->components().size() == targetComponents.size(), ""); + TypePointers tempComponents(targetComponents.size()); + for (size_t i = 0; i < targetComponents.size(); ++i) + { + if (tupleType->components()[i] && targetComponents[i]) + { + tempComponents[i] = closestType(tupleType->components()[i], targetComponents[i], _isShiftOp); + solAssert(tempComponents[i], ""); + } + } + return TypeProvider::tuple(move(tempComponents)); + } + else + return _targetType->dataStoredIn(DataLocation::Storage) ? _type->mobileType() : _targetType; +} + +} void ExpressionCompiler::compile(Expression const& _expression) { @@ -280,13 +308,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) _assignment.rightHandSide().accept(*this); // Perform some conversion already. This will convert storage types to memory and literals // to their actual type, but will not convert e.g. memory to storage. - Type const* rightIntermediateType; - if (op != Token::Assign && TokenTraits::isShiftOp(binOp)) - rightIntermediateType = _assignment.rightHandSide().annotation().type->mobileType(); - else - rightIntermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType( - _assignment.leftHandSide().annotation().type - ); + Type const* rightIntermediateType = closestType( + _assignment.rightHandSide().annotation().type, + _assignment.leftHandSide().annotation().type, + op != Token::Assign && TokenTraits::isShiftOp(binOp) + ); + solAssert(rightIntermediateType, ""); utils().convertType(*_assignment.rightHandSide().annotation().type, *rightIntermediateType, cleanupNeeded); @@ -1016,7 +1043,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // stack: argValue storageSlot slotOffset utils().moveToStackTop(2, argType->sizeOnStack()); // stack: storageSlot slotOffset argValue - Type const* type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); + Type const* type = + arrayType->baseType()->dataStoredIn(DataLocation::Storage) ? + arguments[0]->annotation().type->mobileType() : + arrayType->baseType(); solAssert(type, ""); utils().convertType(*argType, *type); utils().moveToStackTop(1 + type->sizeOnStack()); diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 26c7d0df2..d0bde42c1 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -152,6 +152,54 @@ string YulUtilFunctions::storeLiteralInMemoryFunction(string const& _literal) }); } +string YulUtilFunctions::copyLiteralToStorageFunction(string const& _literal) +{ + string functionName = "copy_literal_to_storage_" + util::toHex(util::keccak256(_literal).asBytes()); + + return m_functionCollector.createFunction(functionName, [&](vector& _args, vector&) { + _args = {"slot"}; + + if (_literal.size() >= 32) + { + size_t words = (_literal.length() + 31) / 32; + vector> wordParams(words); + for (size_t i = 0; i < words; ++i) + { + wordParams[i]["offset"] = to_string(i); + wordParams[i]["wordValue"] = formatAsStringOrNumber(_literal.substr(32 * i, 32)); + } + return Whiskers(R"( + let oldLen := (sload(slot)) + (slot, oldLen, ) + sstore(slot, ) + let dstPtr := (slot) + <#word> + sstore(add(dstPtr, ), ) + + )") + ("byteArrayLength", extractByteArrayLengthFunction()) + ("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage())) + ("dataArea", arrayDataAreaFunction(*TypeProvider::bytesStorage())) + ("word", wordParams) + ("length", to_string(_literal.size())) + ("encodedLen", to_string(2 * _literal.size() + 1)) + .render(); + } + else + return Whiskers(R"( + let oldLen := (sload(slot)) + (slot, oldLen, ) + sstore(slot, add(, )) + )") + ("byteArrayLength", extractByteArrayLengthFunction()) + ("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage())) + ("wordValue", formatAsStringOrNumber(_literal)) + ("length", to_string(_literal.size())) + ("encodedLen", to_string(2 * _literal.size())) + .render(); + }); +} + string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType) { string functionName = @@ -2680,15 +2728,13 @@ string YulUtilFunctions::updateStorageValueFunction( return Whiskers(R"( function (slot, offset) { if offset { () } - let value := () - (slot, value) + (slot) } )") ("functionName", functionName) ("dynamicOffset", !_offset.has_value()) ("panic", panicFunction(PanicCode::Generic)) - ("copyLiteralToMemory", copyLiteralToMemoryFunction(dynamic_cast(_fromType).value())) - ("copyToStorage", copyArrayToStorageFunction(*TypeProvider::bytesMemory(), toArrayType)) + ("copyToStorage", copyLiteralToStorageFunction(dynamic_cast(_fromType).value())) .render(); } @@ -2697,7 +2743,10 @@ string YulUtilFunctions::updateStorageValueFunction( fromReferenceType->isPointer() ).get() == *fromReferenceType, ""); - solAssert(toReferenceType->category() == fromReferenceType->category(), ""); + if (fromReferenceType->category() == Type::Category::ArraySlice) + solAssert(toReferenceType->category() == Type::Category::Array, ""); + else + solAssert(toReferenceType->category() == fromReferenceType->category(), ""); solAssert(_offset.value_or(0) == 0, ""); Whiskers templ(R"( @@ -2715,6 +2764,17 @@ string YulUtilFunctions::updateStorageValueFunction( dynamic_cast(_fromType), dynamic_cast(_toType) )); + else if (_fromType.category() == Type::Category::ArraySlice) + { + solAssert( + _fromType.dataStoredIn(DataLocation::CallData), + "Currently only calldata array slices are supported!" + ); + templ("copyToStorage", copyArrayToStorageFunction( + dynamic_cast(_fromType).arrayType(), + dynamic_cast(_toType) + )); + } else templ("copyToStorage", copyStructToStorageFunction( dynamic_cast(_fromType), diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 30f109b51..b71f0f9a7 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -81,6 +81,10 @@ public: /// signature: (memPtr) -> std::string storeLiteralInMemoryFunction(std::string const& _literal); + /// @returns the name of a function that stores a string literal at a specific location in storage + /// signature: (slot) -> + std::string copyLiteralToStorageFunction(std::string const& _literal); + // @returns the name of a function that has the equivalent logic of an // `assert` or `require` call. std::string requireOrAssertFunction(bool _assert, Type const* _messageType = nullptr); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index aeda4a794..7c00338b1 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -238,9 +238,6 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va _varDecl.value()->accept(*this); - Type const* rightIntermediateType = _varDecl.value()->annotation().type->closestTemporaryType(_varDecl.type()); - solAssert(rightIntermediateType, ""); - IRVariable value = convert(*_varDecl.value(), *rightIntermediateType); writeToLValue( _varDecl.immutable() ? IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} : @@ -248,7 +245,7 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first), m_context.storageLocationOfStateVariable(_varDecl).second }}, - value + *_varDecl.value() ); } catch (langutil::UnimplementedFeatureError const& _error) @@ -407,55 +404,49 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment) assignmentOperator : TokenTraits::AssignmentToBinaryOp(assignmentOperator); - Type const* rightIntermediateType = - TokenTraits::isShiftOp(binaryOperator) ? - type(_assignment.rightHandSide()).mobileType() : - type(_assignment.rightHandSide()).closestTemporaryType( - &type(_assignment.leftHandSide()) - ); - solAssert(rightIntermediateType, ""); - IRVariable value = convert(_assignment.rightHandSide(), *rightIntermediateType); + if (TokenTraits::isShiftOp(binaryOperator)) + solAssert(type(_assignment.rightHandSide()).mobileType(), ""); + IRVariable value = + type(_assignment.leftHandSide()).isValueType() ? + convert( + _assignment.rightHandSide(), + TokenTraits::isShiftOp(binaryOperator) ? *type(_assignment.rightHandSide()).mobileType() : type(_assignment) + ) : + _assignment.rightHandSide(); + _assignment.leftHandSide().accept(*this); + solAssert(!!m_currentLValue, "LValue not retrieved."); setLocation(_assignment); if (assignmentOperator != Token::Assign) { solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types."); - solAssert(rightIntermediateType->isValueType(), "Compound operators only available for value types."); - IRVariable leftIntermediate = readFromLValue(*m_currentLValue); solAssert(binaryOperator != Token::Exp, ""); - if (TokenTraits::isShiftOp(binaryOperator)) - { - solAssert(type(_assignment) == leftIntermediate.type(), ""); - solAssert(type(_assignment) == type(_assignment.leftHandSide()), ""); - define(_assignment) << shiftOperation(binaryOperator, leftIntermediate, value) << "\n"; + solAssert(type(_assignment) == type(_assignment.leftHandSide()), ""); - writeToLValue(*m_currentLValue, IRVariable(_assignment)); - m_currentLValue.reset(); - return false; - } - else - { - solAssert(type(_assignment.leftHandSide()) == *rightIntermediateType, ""); - m_code << value.name() << " := " << binaryOperation( - binaryOperator, - *rightIntermediateType, - leftIntermediate.name(), - value.name() - ); - } + IRVariable leftIntermediate = readFromLValue(*m_currentLValue); + solAssert(type(_assignment) == leftIntermediate.type(), ""); + + define(_assignment) << ( + TokenTraits::isShiftOp(binaryOperator) ? + shiftOperation(binaryOperator, leftIntermediate, value) : + binaryOperation(binaryOperator, type(_assignment), leftIntermediate.name(), value.name()) + ) << "\n"; + + writeToLValue(*m_currentLValue, IRVariable(_assignment)); + } + else + { + writeToLValue(*m_currentLValue, value); + + if (dynamic_cast(&m_currentLValue->type)) + define(_assignment, readFromLValue(*m_currentLValue)); + else if (*_assignment.annotation().type != *TypeProvider::emptyTuple()) + define(_assignment, value); } - writeToLValue(*m_currentLValue, value); - - if (dynamic_cast(&m_currentLValue->type)) - define(_assignment, readFromLValue(*m_currentLValue)); - else if (*_assignment.annotation().type != *TypeProvider::emptyTuple()) - define(_assignment, value); - m_currentLValue.reset(); - return false; } @@ -2857,10 +2848,17 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable prepared.commaSeparatedList() << ")\n"; } + else if (auto const* literalType = dynamic_cast(&_value.type())) + m_code << + m_utils.writeToMemoryFunction(*TypeProvider::uint256()) << + "(" << + _memory.address << + ", " << + m_utils.copyLiteralToMemoryFunction(literalType->value()) + "()" << + ")\n"; else { solAssert(_lvalue.type.sizeOnStack() == 1, ""); - solAssert(dynamic_cast(&_lvalue.type), ""); auto const* valueReferenceType = dynamic_cast(&_value.type()); solAssert(valueReferenceType && valueReferenceType->dataStoredIn(DataLocation::Memory), ""); m_code << "mstore(" + _memory.address + ", " + _value.part("mpos").name() + ")\n"; diff --git a/test/libsolidity/semanticTests/array/push/nested_bytes_push.sol b/test/libsolidity/semanticTests/array/push/nested_bytes_push.sol index f7cc0cc59..23dce6f6c 100644 --- a/test/libsolidity/semanticTests/array/push/nested_bytes_push.sol +++ b/test/libsolidity/semanticTests/array/push/nested_bytes_push.sol @@ -4,12 +4,17 @@ contract C { function f() public { a.push("abc"); - a.push("def"); + a.push("abcdefghabcdefghabcdefghabcdefgh"); + a.push("abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh"); assert(a[0][0] == "a"); - assert(a[1][0] == "d"); + assert(a[1][31] == "h"); + assert(a[2][32] == "a"); } } // ==== // compileViaYul: also // ---- // f() -> +// gas irOptimized: 181480 +// gas legacy: 180320 +// gas legacyOptimized: 180103 diff --git a/test/libsolidity/semanticTests/array/string_literal_assign_to_storage_bytes.sol b/test/libsolidity/semanticTests/array/string_literal_assign_to_storage_bytes.sol new file mode 100644 index 000000000..ee3cda2d1 --- /dev/null +++ b/test/libsolidity/semanticTests/array/string_literal_assign_to_storage_bytes.sol @@ -0,0 +1,22 @@ +contract C { + bytes public s = "abc"; + bytes public s1 = "abcd"; + function f() public { + s = "abcd"; + s1 = "abc"; + } + function g() public { + (s, s1) = ("abc", "abcd"); + } +} +// ==== +// compileViaYul: also +// ---- +// s() -> 0x20, 3, "abc" +// s1() -> 0x20, 4, "abcd" +// f() -> +// s() -> 0x20, 4, "abcd" +// s1() -> 0x20, 3, "abc" +// g() -> +// s() -> 0x20, 3, "abc" +// s1() -> 0x20, 4, "abcd"