diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 1d592489d..ae2cb2966 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2842,6 +2842,14 @@ u256 FunctionType::storageSize() const solAssert(false, "Storage size of non-storable function type requested."); } +bool FunctionType::leftAligned() const +{ + if (m_kind == Kind::External) + return true; + else + solAssert(false, "Alignment property of non-exportable function type requested."); +} + unsigned FunctionType::storageBytes() const { if (m_kind == Kind::External) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 86ae52ff4..5fe1df7d0 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -236,6 +236,13 @@ public: /// In order to avoid computation at runtime of whether such moving is necessary, structs and /// array data (not each element) always start a new slot. virtual unsigned storageBytes() const { return 32; } + /// Returns true if the type is a value type that is left-aligned on the stack with a size of + /// storageBytes() bytes. Returns false if the type is a value type that is right-aligned on + /// the stack with a size of storageBytes() bytes. Asserts if it is not a value type or the + /// encoding is more complicated. + /// Signed integers are not considered "more complicated" even though they need to be + /// sign-extended. + virtual bool leftAligned() const { solAssert(false, "Alignment property of non-value type requested."); } /// Returns true if the type can be stored in storage. virtual bool canBeStored() const { return true; } /// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping. @@ -345,6 +352,7 @@ public: unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : 160 / 8; } unsigned storageBytes() const override { return 160 / 8; } + bool leftAligned() const override { return false; } bool isValueType() const override { return true; } MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; @@ -390,6 +398,7 @@ public: unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; } unsigned storageBytes() const override { return m_bits / 8; } + bool leftAligned() const override { return false; } bool isValueType() const override { return true; } std::string toString(bool _short) const override; @@ -432,6 +441,7 @@ public: unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_totalBits / 8; } unsigned storageBytes() const override { return m_totalBits / 8; } + bool leftAligned() const override { return false; } bool isValueType() const override { return true; } std::string toString(bool _short) const override; @@ -578,6 +588,7 @@ public: unsigned calldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } unsigned storageBytes() const override { return m_bytes; } + bool leftAligned() const override { return true; } bool isValueType() const override { return true; } std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); } @@ -604,6 +615,7 @@ public: unsigned calldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; } unsigned storageBytes() const override { return 1; } + bool leftAligned() const override { return false; } bool isValueType() const override { return true; } std::string toString(bool) const override { return "bool"; } @@ -775,6 +787,7 @@ public: return encodingType()->calldataEncodedSize(_padded); } unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; } + bool leftAligned() const override { solAssert(!isSuper(), ""); return false; } bool canLiveOutsideStorage() const override { return !isSuper(); } unsigned sizeOnStack() const override { return m_super ? 0 : 1; } bool isValueType() const override { return !isSuper(); } @@ -897,6 +910,7 @@ public: return encodingType()->calldataEncodedSize(_padded); } unsigned storageBytes() const override; + bool leftAligned() const override { return false; } bool canLiveOutsideStorage() const override { return true; } std::string toString(bool _short) const override; std::string canonicalName() const override; @@ -1096,6 +1110,7 @@ public: unsigned calldataEncodedSize(bool _padded) const override; bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } u256 storageSize() const override; + bool leftAligned() const override; unsigned storageBytes() const override; bool isValueType() const override { return true; } bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index ef6148ec0..47475eced 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -370,7 +370,7 @@ string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFu if (storageBytes == 32) templ("body", "cleaned := value"); - else if (_type.category() == Type::Category::Function || _type.category() == Type::Category::FixedBytes) + else if (_type.leftAligned()) templ("body", "cleaned := " + m_utils.shiftLeftFunction(256 - 8 * storageBytes) + "(value)"); else templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")"); @@ -811,8 +811,15 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( subOptions.encodeFunctionFromStack = false; subOptions.padded = true; templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); - // There is only one element per slot, so sload suffices, no need to use "readFromStorage". - templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" ); + if (inMemory) + templ("arrayElementAccess", "mload(srcPtr)"); + else if (_from.baseType()->isValueType()) + { + solAssert(_from.dataStoredIn(DataLocation::Storage), ""); + templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)"); + } + else + templ("arrayElementAccess", "srcPtr"); templ("nextArrayElement", m_utils.nextArrayElementFunction(_from)); return templ.render(); }); @@ -920,8 +927,9 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( bool dynamic = _to.isDynamicallyEncoded(); size_t storageBytes = _from.baseType()->storageBytes(); size_t itemsPerSlot = 32 / storageBytes; - // This always writes full slot contents to memory, which might be - // more than desired, i.e. it always writes beyond the end of memory. + solAssert(itemsPerSlot > 0, ""); + // The number of elements we need to handle manually after the loop. + size_t spill = size_t(_from.length() % itemsPerSlot); Whiskers templ( R"( // -> @@ -930,16 +938,31 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( pos := (pos, length) let originalPos := pos let srcPtr := (value) - for { let i := 0 } lt(i, length) { i := add(i, ) } - { + let itemCounter := 0 + if { + // Run the loop over all full slots + for { } lt(add(itemCounter, sub(, 1)), length) + { itemCounter := add(itemCounter, ) } + { + let data := sload(srcPtr) + <#items> + ((data), pos) + pos := add(pos, ) + + srcPtr := add(srcPtr, 1) + } + } + // Handle the last (not necessarily full) slot specially + if { let data := sload(srcPtr) <#items> - ((data), pos) - pos := add(pos, ) + if { + ((data), pos) + pos := add(pos, ) + itemCounter := add(itemCounter, 1) + } - srcPtr := add(srcPtr, 1) } - pos := add(originalPos, mul(length, )) } )" @@ -952,6 +975,15 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("lengthFun", m_utils.arrayLengthFunction(_from)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); templ("dataArea", m_utils.arrayDataAreaFunction(_from)); + // We skip the loop for arrays that fit a single slot. + if (_from.isDynamicallySized() || _from.length() >= itemsPerSlot) + templ("useLoop", "1"); + else + templ("useLoop", "0"); + if (_from.isDynamicallySized() || spill != 0) + templ("useSpill", "1"); + else + templ("useSpill", "0"); templ("itemsPerSlot", to_string(itemsPerSlot)); // We use padded size because array elements are always padded. string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()); @@ -968,7 +1000,15 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("encodeToMemoryFun", encodeToMemoryFun); std::vector> items(itemsPerSlot); for (size_t i = 0; i < itemsPerSlot; ++i) + { + if (_from.isDynamicallySized()) + items[i]["inRange"] = "lt(itemCounter, length)"; + else if (i < spill) + items[i]["inRange"] = "1"; + else + items[i]["inRange"] = "0"; items[i]["extractFromSlot"] = extractFromStorageValue(*_from.baseType(), i * storageBytes, false); + } templ("items", items); return templ.render(); }