diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 2fe363e1b..65991f864 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -224,34 +224,45 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits) solAssert(_numBits < 256, ""); string functionName = "shift_left_" + to_string(_numBits); - if (m_evmVersion.hasBitwiseShifting()) - { - return m_functionCollector->createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := shl(, value) - } - )") - ("functionName", functionName) - ("numBits", to_string(_numBits)) - .render(); - }); - } - else - { - return m_functionCollector->createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := mul(value, ) - } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); - } + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := + + shl(, value) + + mul(value, ) + + } + )") + ("functionName", functionName) + ("numBits", to_string(_numBits)) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); +} + +string YulUtilFunctions::dynamicShiftLeftFunction() +{ + string functionName = "shift_left"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (bits, value) -> newValue { + newValue := + + shl(bits, value) + + mul(value, exp(2, bits)) + + } + )") + ("functionName", functionName) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + .render(); + }); } string YulUtilFunctions::shiftRightFunction(size_t _numBits) @@ -261,7 +272,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits) // Note that if this is extended with signed shifts, // the opcodes SAR and SDIV behave differently with regards to rounding! - string functionName = "shift_right_" + to_string(_numBits) + "_unsigned_" + m_evmVersion.name(); + string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( @@ -282,6 +293,30 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits) }); } +string YulUtilFunctions::dynamicShiftRightFunction() +{ + // Note that if this is extended with signed shifts, + // the opcodes SAR and SDIV behave differently with regards to rounding! + + string const functionName = "shift_right_unsigned"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (bits, value) -> newValue { + newValue := + + shr(bits, value) + + div(value, exp(2, bits)) + + } + )") + ("functionName", functionName) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + .render(); + }); +} + string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) { solAssert(_numBytes <= 32, ""); @@ -306,6 +341,29 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift }); } +string YulUtilFunctions::dynamicUpdateByteSliceFunction(size_t _numBytes) +{ + solAssert(_numBytes <= 32, ""); + size_t numBits = _numBytes * 8; + string functionName = "update_byte_slice_" + to_string(_numBytes); + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value, shiftBytes, toInsert) -> result { + let shiftBits := mul(shiftBytes, 8) + let mask := (shiftBits, ) + toInsert := (shiftBits, toInsert) + value := and(value, not(mask)) + result := or(value, and(toInsert, mask)) + } + )") + ("functionName", functionName) + ("mask", formatNumber((bigint(1) << numBits) - 1)) + ("shl", dynamicShiftLeftFunction()) + .render(); + }); +} + string YulUtilFunctions::roundUpFunction() { string functionName = "round_up_to_mul_of_32"; @@ -613,6 +671,89 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool }); } +string YulUtilFunctions::dynamicReadFromStorage(Type const& _type, bool _splitFunctionTypes) +{ + solUnimplementedAssert(!_splitFunctionTypes, ""); + string functionName = + "read_from_storage_" + + string(_splitFunctionTypes ? "split_" : "") + + "_" + + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&] { + solAssert(_type.sizeOnStack() == 1, ""); + return Whiskers(R"( + function (slot, offset) -> value { + value := (sload(slot), offset) + } + )") + ("functionName", functionName) + ("extract", dynamicExtractFromStorageValue(_type, false)) + .render(); + }); +} + +string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional const _offset) +{ + string const functionName = + "update_storage_value_" + + (_offset.is_initialized() ? ("offset_" + to_string(*_offset)) : "") + + _type.identifier(); + + return m_functionCollector->createFunction(functionName, [&] { + if (_type.isValueType()) + { + solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size."); + solAssert(_type.storageBytes() > 0, "Invalid storage bytes size."); + + return Whiskers(R"( + function (slot, value) { + sstore(slot, (sload(slot), (value))) + } + + )") + ("functionName", functionName) + ("update", + _offset.is_initialized() ? + updateByteSliceFunction(_type.storageBytes(), *_offset) : + dynamicUpdateByteSliceFunction(_type.storageBytes()) + ) + ("offset", _offset.is_initialized() ? "" : "offset, ") + ("prepare", prepareStoreFunction(_type)) + .render(); + } + else + { + if (_type.category() == Type::Category::Array) + solUnimplementedAssert(false, ""); + else if (_type.category() == Type::Category::Struct) + solUnimplementedAssert(false, ""); + else + solAssert(false, "Invalid non-value type for assignment."); + } + }); +} + +string YulUtilFunctions::dynamicExtractFromStorageValue(Type const& _type, bool _splitFunctionTypes) +{ + solUnimplementedAssert(!_splitFunctionTypes, ""); + + string functionName = + "extract_from_storage_value_" + + string(_splitFunctionTypes ? "split_" : "") + + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&] { + return Whiskers(R"( + function (slot_value, offset) -> value { + value := ((mul(offset, 8), slot_value)) + } + )") + ("functionName", functionName) + ("shr", dynamicShiftRightFunction()) + ("cleanupStorage", cleanupFromStorageFunction(_type, false)) + .render(); + }); +} + string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes) { solUnimplementedAssert(!_splitFunctionTypes, ""); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 245d29c12..b4e0dc727 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -74,13 +74,18 @@ public: std::string leftAlignFunction(Type const& _type); std::string shiftLeftFunction(size_t _numBits); + std::string dynamicShiftLeftFunction(); std::string shiftRightFunction(size_t _numBits); + std::string dynamicShiftRightFunction(); /// @returns the name of a function f(value, toInsert) -> newValue which replaces the /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant /// byte) by the _numBytes least significant bytes of `toInsert`. std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); + /// signature: (value, shiftBytes, toInsert) -> result + std::string dynamicUpdateByteSliceFunction(size_t _numBytes); + /// @returns the name of a function that rounds its input to the next multiple /// of 32 or the input if it is a multiple of 32. std::string roundUpFunction(); @@ -121,6 +126,7 @@ public: /// @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); + std::string dynamicReadFromStorage(Type const& _type, bool _splitFunctionTypes); /// @returns a function that extracts a value type from storage slot that has been /// retrieved already. @@ -128,6 +134,13 @@ public: /// @param _splitFunctionTypes if false, returns the address and function signature in a /// single variable. std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes); + std::string dynamicExtractFromStorageValue(Type const& _type, bool _splitFunctionTypes); + + /// Returns the name of a function will write the given value to + /// 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, boost::optional const _offset = boost::optional()); /// Performs cleanup after reading from a potentially compressed storage slot. /// The function does not perform any validation, it just masks or sign-extends diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp index 4c3fe1acf..ff8e4bb13 100644 --- a/libsolidity/codegen/ir/IRLValue.cpp +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -54,26 +54,37 @@ string IRLocalVariable::setToZero() const IRStorageItem::IRStorageItem( IRGenerationContext& _context, VariableDeclaration const& _varDecl -): - IRLValue(_context, _varDecl.annotation().type) +) +:IRStorageItem( + _context, + *_varDecl.annotation().type, + _context.storageLocationOfVariable(_varDecl) +) +{ } + +IRStorageItem::IRStorageItem( + IRGenerationContext& _context, + Type const& _type, + std::pair slot_offset +) +: IRLValue(_context, &_type), + m_slot(toCompactHexWithPrefix(slot_offset.first)), + m_offset(slot_offset.second) { - u256 slot; - unsigned offset; - std::tie(slot, offset) = _context.storageLocationOfVariable(_varDecl); - m_slot = toCompactHexWithPrefix(slot); - m_offset = offset; } IRStorageItem::IRStorageItem( IRGenerationContext& _context, string _slot, - unsigned _offset, + boost::variant _offset, Type const& _type ): IRLValue(_context, &_type), m_slot(move(_slot)), - m_offset(_offset) + m_offset(std::move(_offset)) { + solAssert(!m_offset.empty(), ""); + solAssert(!m_slot.empty(), ""); } string IRStorageItem::retrieveValue() const @@ -81,39 +92,45 @@ string IRStorageItem::retrieveValue() const if (!m_type->isValueType()) return m_slot; solUnimplementedAssert(m_type->category() != Type::Category::Function, ""); - return m_context.utils().readFromStorage(*m_type, m_offset, false) + "(" + m_slot + ")"; + if (m_offset.type() == typeid(string)) + return + m_context.utils().dynamicReadFromStorage(*m_type, false) + + "(" + + m_slot + + ", " + + boost::get(m_offset) + + ")"; + else if (m_offset.type() == typeid(unsigned)) + return + m_context.utils().readFromStorage(*m_type, boost::get(m_offset), false) + + "(" + + m_slot + + ")"; + + solAssert(false, ""); } string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const { if (m_type->isValueType()) - { - solAssert(m_type->storageBytes() <= 32, "Invalid storage bytes size."); - solAssert(m_type->storageBytes() > 0, "Invalid storage bytes size."); - solAssert(m_type->storageBytes() + m_offset <= 32, ""); - solAssert(_sourceType == *m_type, "Different type, but might not be an error."); - return Whiskers("sstore(, (sload(), ()))\n") - ("slot", m_slot) - ("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset)) - ("prepare", m_context.utils().prepareStoreFunction(*m_type)) - ("value", _value) - .render(); - } - else - { - solAssert( - _sourceType.category() == m_type->category(), - "Wrong type conversation for assignment." - ); - if (m_type->category() == Type::Category::Array) - solUnimplementedAssert(false, ""); - else if (m_type->category() == Type::Category::Struct) - solUnimplementedAssert(false, ""); - else - solAssert(false, "Invalid non-value type for assignment."); - } + boost::optional offset; + + if (m_offset.type() == typeid(unsigned)) + offset = get(m_offset); + + return + m_context.utils().updateStorageValueFunction(*m_type, offset) + + "(" + + m_slot + + (m_offset.type() == typeid(string) ? + (", " + get(m_offset)) : + "" + ) + + ", " + + _value + + ")\n"; } string IRStorageItem::setToZero() const diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h index 98ad987f1..803223fe7 100644 --- a/libsolidity/codegen/ir/IRLValue.h +++ b/libsolidity/codegen/ir/IRLValue.h @@ -20,8 +20,11 @@ #pragma once +#include + #include #include +#include namespace dev { @@ -83,7 +86,7 @@ public: IRStorageItem( IRGenerationContext& _context, std::string _slot, - unsigned _offset, + boost::variant _offset, Type const& _type ); std::string retrieveValue() const override; @@ -91,8 +94,14 @@ public: std::string setToZero() const override; private: - std::string m_slot; - unsigned m_offset; + IRStorageItem( + IRGenerationContext& _context, + Type const& _type, + std::pair slot_offset + ); + + std::string const m_slot; + boost::variant const m_offset; }; diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol index d74db942c..f1f57aca9 100644 --- a/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol @@ -9,5 +9,7 @@ contract C { } } } +// ==== +// compileViaYul: also // ---- // f() -> 6 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol index f25a3c06a..96277887e 100644 --- a/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol @@ -9,5 +9,7 @@ contract C { } } } +// ==== +// compileViaYul: also // ---- // f() -> 5 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol index 3a13e9f0e..b8d35bb8f 100644 --- a/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol @@ -13,6 +13,8 @@ contract C { } } } +// ==== +// compileViaYul: also // ---- // f(uint256): 0 -> 2 // f(uint256): 1 -> 18 diff --git a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol index f2016c967..b77e5be69 100644 --- a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol +++ b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol @@ -24,6 +24,8 @@ contract C { return 500; } } +// ==== +// compileViaYul: also // ---- // call(uint256): 0 -> 0 // call(uint256): 1 -> 1 diff --git a/test/libsolidity/semanticTests/shifts.sol b/test/libsolidity/semanticTests/shifts.sol index e202f2889..4f8892250 100644 --- a/test/libsolidity/semanticTests/shifts.sol +++ b/test/libsolidity/semanticTests/shifts.sol @@ -4,6 +4,7 @@ contract C { } } // ==== +// compileViaYul: also // EVMVersion: >=constantinople // ---- // f(uint256): 7 -> 28 diff --git a/test/libsolidity/semanticTests/smoke_test_multiline.sol b/test/libsolidity/semanticTests/smoke_test_multiline.sol index 7395b1c3b..5d02e148e 100644 --- a/test/libsolidity/semanticTests/smoke_test_multiline.sol +++ b/test/libsolidity/semanticTests/smoke_test_multiline.sol @@ -3,6 +3,8 @@ contract C { return a + b + c + d + e; } } +// ==== +// compileViaYul: also // ---- // f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1 // -> 5 diff --git a/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol b/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol index 17de40fc4..66bdfb915 100644 --- a/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol +++ b/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol @@ -3,6 +3,8 @@ contract C { return a + b + c + d + e; } } +// ==== +// compileViaYul: also // ---- // f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1 // # A comment on the function parameters. #