diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 36125c2ef..39ea69350 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -63,6 +63,10 @@ set(sources codegen/ExpressionCompiler.h codegen/LValue.cpp codegen/LValue.h + codegen/MultiUseYulFunctionCollector.h + codegen/MultiUseYulFunctionCollector.cpp + codegen/YulUtilFunctions.h + codegen/YulUtilFunctions.cpp formal/SMTChecker.cpp formal/SMTChecker.h formal/SMTLib2Interface.cpp diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index f4c743174..c8b30a7c4 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -22,7 +22,6 @@ #include -#include #include #include @@ -250,11 +249,9 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) pair> ABIFunctions::requestedFunctions() { - string result; - for (auto const& f: m_requestedFunctions) - result += f.second; - m_requestedFunctions.clear(); - return make_pair(result, std::move(m_externallyUsedFunctions)); + std::set empty; + swap(empty, m_externallyUsedFunctions); + return make_pair(m_functionCollector->requestedFunctions(), std::move(empty)); } string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const @@ -271,6 +268,7 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const return suffix; } + string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) { string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); @@ -401,7 +399,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) FixedBytesType const& toBytesType = dynamic_cast(_to); body = Whiskers("converted := ((value))") - ("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8)) + ("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8)) ("clean", cleanupFunction(_from)) .render(); } @@ -477,7 +475,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) if (toCategory == Type::Category::Integer) body = Whiskers("converted := ((value))") - ("shift", shiftRightFunction(256 - from.numBytes() * 8)) + ("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8)) ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) .render(); else if (toCategory == Type::Category::Address) @@ -541,40 +539,6 @@ string ABIFunctions::cleanupCombinedExternalFunctionIdFunction() }); } -string ABIFunctions::combineExternalFunctionIdFunction() -{ - string functionName = "combine_external_function_id"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (addr, selector) -> combined { - combined := (or((addr), and(selector, 0xffffffff))) - } - )") - ("functionName", functionName) - ("shl32", shiftLeftFunction(32)) - ("shl64", shiftLeftFunction(64)) - .render(); - }); -} - -string ABIFunctions::splitExternalFunctionIdFunction() -{ - string functionName = "split_external_function_id"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (combined) -> addr, selector { - combined := (combined) - selector := and(combined, 0xffffffff) - addr := (combined) - } - )") - ("functionName", functionName) - ("shr32", shiftRightFunction(32)) - ("shr64", shiftRightFunction(64)) - .render(); - }); -} - string ABIFunctions::abiEncodingFunction( Type const& _from, Type const& _to, @@ -655,7 +619,7 @@ string ABIFunctions::abiEncodingFunction( else cleanupConvert = conversionFunction(_from, to) + "(value)"; if (!_options.padded) - cleanupConvert = leftAlignFunction(to) + "(" + cleanupConvert + ")"; + cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")"; templ("cleanupConvert", cleanupConvert); } return templ.render(); @@ -745,8 +709,8 @@ string ABIFunctions::abiEncodingFunctionCalldataArray( templ("functionName", functionName); templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameTo", _to.toString(true)); - templ("copyFun", copyToMemoryFunction(true)); - templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length"); + templ("copyFun", m_utils.copyToMemoryFunction(true)); + templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length"); return templ.render(); }); } @@ -816,16 +780,16 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( templ("readableTypeNameTo", _to.toString(true)); templ("return", dynamic ? " -> end " : ""); templ("assignEnd", dynamic ? "end := pos" : ""); - templ("lengthFun", arrayLengthFunction(_from)); + templ("lengthFun", m_utils.arrayLengthFunction(_from)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("dataAreaFun", arrayDataAreaFunction(_from)); + templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from)); EncodingOptions subOptions(_options); subOptions.encodeFunctionFromStack = false; subOptions.padded = true; templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" ); - templ("nextArrayElement", nextArrayElementFunction(_from)); + templ("nextArrayElement", m_utils.nextArrayElementFunction(_from)); return templ.render(); }); } @@ -859,10 +823,10 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray( } )"); templ("functionName", functionName); - templ("lengthFun", arrayLengthFunction(_from)); + templ("lengthFun", m_utils.arrayLengthFunction(_from)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("copyFun", copyToMemoryFunction(false)); - templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length"); + templ("copyFun", m_utils.copyToMemoryFunction(false)); + templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length"); return templ.render(); }); } @@ -920,7 +884,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); templ("lengthPaddedShort", _options.padded ? "0x20" : "length"); templ("lengthPaddedLong", _options.padded ? "i" : "length"); - templ("arrayDataSlot", arrayDataAreaFunction(_from)); + templ("arrayDataSlot", m_utils.arrayDataAreaFunction(_from)); return templ.render(); } else @@ -961,9 +925,9 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("readableTypeNameTo", _to.toString(true)); templ("return", dynamic ? " -> end " : ""); templ("assignEnd", dynamic ? "end := pos" : ""); - templ("lengthFun", arrayLengthFunction(_from)); + templ("lengthFun", m_utils.arrayLengthFunction(_from)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("dataArea", arrayDataAreaFunction(_from)); + templ("dataArea", m_utils.arrayDataAreaFunction(_from)); templ("itemsPerSlot", to_string(itemsPerSlot)); // We use padded size because array elements are always padded. string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()); @@ -980,7 +944,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("encodeToMemoryFun", encodeToMemoryFun); std::vector> items(itemsPerSlot); for (size_t i = 0; i < itemsPerSlot; ++i) - items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8); + items[i]["shiftRightFun"] = m_utils.shiftRightFunction(i * storageBytes * 8); templ("items", items); return templ.render(); } @@ -1066,7 +1030,7 @@ string ABIFunctions::abiEncodingFunctionStruct( members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; previousSlotOffset = storageSlotOffset; } - members.back()["retrieveValue"] = shiftRightFunction(intraSlotOffset * 8) + "(slotValue)"; + members.back()["retrieveValue"] = m_utils.shiftRightFunction(intraSlotOffset * 8) + "(slotValue)"; } else { @@ -1209,7 +1173,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType( } )") ("functionName", functionName) - ("combineExtFun", combineExternalFunctionIdFunction()) + ("combineExtFun", m_utils.combineExternalFunctionIdFunction()) .render(); }); else @@ -1331,8 +1295,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); - templ("allocate", allocationFunction()); - templ("allocationSize", arrayAllocationSizeFunction(_type)); + templ("allocate", m_utils.allocationFunction()); + templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); if (_type.isDynamicallySized()) templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)"); else @@ -1426,9 +1390,9 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _ ); templ("functionName", functionName); templ("load", _fromMemory ? "mload" : "calldataload"); - templ("allocate", allocationFunction()); - templ("allocationSize", arrayAllocationSizeFunction(_type)); - templ("copyToMemFun", copyToMemoryFunction(!_fromMemory)); + templ("allocate", m_utils.allocationFunction()); + templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); + templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory)); return templ.render(); }); } @@ -1480,7 +1444,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr )"); templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); - templ("allocate", allocationFunction()); + templ("allocate", m_utils.allocationFunction()); solAssert(_type.memorySize() < u256("0xffffffffffffffff"), ""); templ("memorySize", toCompactHexWithPrefix(_type.memorySize())); size_t headPos = 0; @@ -1540,7 +1504,7 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, )") ("functionName", functionName) ("load", _fromMemory ? "mload" : "calldataload") - ("splitExtFun", splitExternalFunctionIdFunction()) + ("splitExtFun", m_utils.splitExternalFunctionIdFunction()) .render(); } else @@ -1558,357 +1522,6 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, }); } -string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) -{ - string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; - return createFunction(functionName, [&]() { - if (_fromCalldata) - { - return Whiskers(R"( - function (src, dst, length) { - calldatacopy(dst, src, length) - // clear end - mstore(add(dst, length), 0) - } - )") - ("functionName", functionName) - .render(); - } - else - { - return Whiskers(R"( - function (src, dst, length) { - let i := 0 - for { } lt(i, length) { i := add(i, 32) } - { - mstore(add(dst, i), mload(add(src, i))) - } - if gt(i, length) - { - // clear end - mstore(add(dst, length), 0) - } - } - )") - ("functionName", functionName) - .render(); - } - }); -} - -string ABIFunctions::leftAlignFunction(Type const& _type) -{ - string functionName = string("leftAlign_") + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (value) -> aligned { - - } - )"); - templ("functionName", functionName); - switch (_type.category()) - { - case Type::Category::Address: - templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)"); - break; - case Type::Category::Integer: - { - IntegerType const& type = dynamic_cast(_type); - if (type.numBits() == 256) - templ("body", "aligned := value"); - else - templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)"); - break; - } - case Type::Category::RationalNumber: - solAssert(false, "Left align requested for rational number."); - break; - case Type::Category::Bool: - templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)"); - break; - case Type::Category::FixedPoint: - solUnimplemented("Fixed point types not implemented."); - break; - case Type::Category::Array: - case Type::Category::Struct: - solAssert(false, "Left align requested for non-value type."); - break; - case Type::Category::FixedBytes: - templ("body", "aligned := value"); - break; - case Type::Category::Contract: - templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)"); - break; - case Type::Category::Enum: - { - unsigned storageBytes = dynamic_cast(_type).storageBytes(); - templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)"); - break; - } - case Type::Category::InaccessibleDynamic: - solAssert(false, "Left align requested for inaccessible dynamic type."); - break; - default: - solAssert(false, "Left align of type " + _type.identifier() + " requested."); - } - - return templ.render(); - }); -} - -string ABIFunctions::shiftLeftFunction(size_t _numBits) -{ - solAssert(_numBits < 256, ""); - - string functionName = "shift_left_" + to_string(_numBits); - if (m_evmVersion.hasBitwiseShifting()) - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := shl(, value) - } - )") - ("functionName", functionName) - ("numBits", to_string(_numBits)) - .render(); - }); - } - else - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := mul(value, ) - } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); - } -} - -string ABIFunctions::shiftRightFunction(size_t _numBits) -{ - solAssert(_numBits < 256, ""); - - // 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"; - if (m_evmVersion.hasBitwiseShifting()) - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := shr(, value) - } - )") - ("functionName", functionName) - ("numBits", to_string(_numBits)) - .render(); - }); - } - else - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := div(value, ) - } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); - } -} - -string ABIFunctions::roundUpFunction() -{ - string functionName = "round_up_to_mul_of_32"; - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> result { - result := and(add(value, 31), not(31)) - } - )") - ("functionName", functionName) - .render(); - }); -} - -string ABIFunctions::arrayLengthFunction(ArrayType const& _type) -{ - string functionName = "array_length_" + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers w(R"( - function (value) -> length { - - } - )"); - w("functionName", functionName); - string body; - if (!_type.isDynamicallySized()) - body = "length := " + toCompactHexWithPrefix(_type.length()); - else - { - switch (_type.location()) - { - case DataLocation::CallData: - solAssert(false, "called regular array length function on calldata array"); - break; - case DataLocation::Memory: - body = "length := mload(value)"; - break; - case DataLocation::Storage: - if (_type.isByteArray()) - { - // Retrieve length both for in-place strings and off-place strings: - // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 - // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it - // computes (x & (-1)) / 2, which is equivalent to just x / 2. - body = R"( - length := sload(value) - let mask := sub(mul(0x100, iszero(and(length, 1))), 1) - length := div(and(length, mask), 2) - )"; - } - else - body = "length := sload(value)"; - break; - } - } - solAssert(!body.empty(), ""); - w("body", body); - return w.render(); - }); -} - -string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type) -{ - solAssert(_type.dataStoredIn(DataLocation::Memory), ""); - string functionName = "array_allocation_size_" + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers w(R"( - function (length) -> size { - // Make sure we can allocate memory without overflow - if gt(length, 0xffffffffffffffff) { revert(0, 0) } - size := - - } - )"); - w("functionName", functionName); - if (_type.isByteArray()) - // Round up - w("allocationSize", "and(add(length, 0x1f), not(0x1f))"); - else - w("allocationSize", "mul(length, 0x20)"); - if (_type.isDynamicallySized()) - w("addLengthSlot", "size := add(size, 0x20)"); - else - w("addLengthSlot", ""); - return w.render(); - }); -} - -string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type) -{ - string functionName = "array_dataslot_" + _type.identifier(); - return createFunction(functionName, [&]() { - if (_type.dataStoredIn(DataLocation::Memory)) - { - if (_type.isDynamicallySized()) - return Whiskers(R"( - function (memPtr) -> dataPtr { - dataPtr := add(memPtr, 0x20) - } - )") - ("functionName", functionName) - .render(); - else - return Whiskers(R"( - function (memPtr) -> dataPtr { - dataPtr := memPtr - } - )") - ("functionName", functionName) - .render(); - } - else if (_type.dataStoredIn(DataLocation::Storage)) - { - if (_type.isDynamicallySized()) - { - Whiskers w(R"( - function (slot) -> dataSlot { - mstore(0, slot) - dataSlot := keccak256(0, 0x20) - } - )"); - w("functionName", functionName); - return w.render(); - } - else - { - Whiskers w(R"( - function (slot) -> dataSlot { - dataSlot := slot - } - )"); - w("functionName", functionName); - return w.render(); - } - } - else - { - // Not used for calldata - solAssert(false, ""); - } - }); -} - -string ABIFunctions::nextArrayElementFunction(ArrayType const& _type) -{ - solAssert(!_type.isByteArray(), ""); - solAssert( - _type.location() == DataLocation::Memory || - _type.location() == DataLocation::Storage, - "" - ); - solAssert( - _type.location() == DataLocation::Memory || - _type.baseType()->storageBytes() > 16, - "" - ); - string functionName = "array_nextElement_" + _type.identifier(); - return createFunction(functionName, [&]() { - if (_type.location() == DataLocation::Memory) - return Whiskers(R"( - function (memPtr) -> nextPtr { - nextPtr := add(memPtr, 0x20) - } - )") - ("functionName", functionName) - .render(); - else if (_type.location() == DataLocation::Storage) - return Whiskers(R"( - function (slot) -> nextSlot { - nextSlot := add(slot, 1) - } - )") - ("functionName", functionName) - .render(); - else - solAssert(false, ""); - }); -} - string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options) { string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix(); @@ -1933,34 +1546,9 @@ string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, }); } -string ABIFunctions::allocationFunction() -{ - string functionName = "allocateMemory"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (size) -> memPtr { - memPtr := mload() - let newFreePtr := add(memPtr, size) - // protect against overflow - if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } - mstore(, newFreePtr) - } - )") - ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) - ("functionName", functionName) - .render(); - }); -} - string ABIFunctions::createFunction(string const& _name, function const& _creator) { - if (!m_requestedFunctions.count(_name)) - { - auto fun = _creator(); - solAssert(!fun.empty(), ""); - m_requestedFunctions[_name] = fun; - } - return _name; + return m_functionCollector->createFunction(_name, _creator); } string ABIFunctions::createExternallyUsedFunction(string const& _name, function const& _creator) diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 654afc034..c1d88d68d 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -22,7 +22,9 @@ #pragma once -#include +#include +#include + #include #include @@ -30,8 +32,10 @@ #include #include -namespace dev { -namespace solidity { +namespace dev +{ +namespace solidity +{ class Type; class ArrayType; @@ -40,17 +44,25 @@ class FunctionType; using TypePointer = std::shared_ptr; using TypePointers = std::vector; -/// -/// Class to generate encoding and decoding functions. Also maintains a collection -/// of "functions to be generated" in order to avoid generating the same function -/// multiple times. -/// -/// Make sure to include the result of ``requestedFunctions()`` to a block that -/// is visible from the code that was generated here, or use named labels. +/** + * Class to generate encoding and decoding functions. Also maintains a collection + * of "functions to be generated" in order to avoid generating the same function + * multiple times. + * + * Make sure to include the result of ``requestedFunctions()`` to a block that + * is visible from the code that was generated here, or use named labels. + */ class ABIFunctions { public: - explicit ABIFunctions(langutil::EVMVersion _evmVersion = langutil::EVMVersion{}) : m_evmVersion(_evmVersion) {} + explicit ABIFunctions( + langutil::EVMVersion _evmVersion, + std::shared_ptr _functionCollector = std::make_shared() + ): + m_evmVersion(_evmVersion), + m_functionCollector(std::move(_functionCollector)), + m_utils(_evmVersion, m_functionCollector) + {} /// @returns name of an assembly function to ABI-encode values of @a _givenTypes /// into memory, converting the types to @a _targetTypes on the fly. @@ -129,14 +141,6 @@ private: std::string cleanupCombinedExternalFunctionIdFunction(); - /// @returns a function that combines the address and selector to a single value - /// for use in the ABI. - std::string combineExternalFunctionIdFunction(); - - /// @returns a function that splits the address and selector from a single value - /// for use in the ABI. - std::string splitExternalFunctionIdFunction(); - /// @returns the name of the ABI encoding function with the given type /// and queues the generation of the function to the requested functions. /// @param _fromStack if false, the input value was just loaded from storage @@ -229,34 +233,6 @@ private: /// Part of @a abiDecodingFunction for array types. std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); - /// @returns a function that copies raw bytes of dynamic length from calldata - /// or memory to memory. - /// Pads with zeros and might write more than exactly length. - std::string copyToMemoryFunction(bool _fromCalldata); - - /// @returns the name of a function that takes a (cleaned) value of the given value type and - /// left-aligns it, usually for use in non-padded encoding. - std::string leftAlignFunction(Type const& _type); - - std::string shiftLeftFunction(size_t _numBits); - std::string shiftRightFunction(size_t _numBits); - /// @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(); - - std::string arrayLengthFunction(ArrayType const& _type); - /// @returns the name of a function that computes the number of bytes required - /// to store an array in memory given its length (internally encoded, not ABI encoded). - /// The function reverts for too large lengths. - std::string arrayAllocationSizeFunction(ArrayType const& _type); - /// @returns the name of a function that converts a storage slot number - /// or a memory pointer to the slot number / memory pointer for the data position of an array - /// which is stored in that slot / memory area. - std::string arrayDataAreaFunction(ArrayType const& _type); - /// @returns the name of a function that advances an array data pointer to the next element. - /// Only works for memory arrays and storage arrays that store one item per slot. - std::string nextArrayElementFunction(ArrayType const& _type); - /// @returns the name of a function used during encoding that stores the length /// if the array is dynamically sized (and the options do not request in-place encoding). /// It returns the new encoding position. @@ -264,12 +240,6 @@ private: /// does nothing and just returns the position again. std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options); - /// @returns the name of a function that allocates memory. - /// Modifies the "free memory pointer" - /// Arguments: size - /// Return value: pointer - std::string allocationFunction(); - /// Helper function that uses @a _creator to create a function and add it to /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both /// cases. @@ -283,10 +253,10 @@ private: /// @returns the size of the static part of the encoding of the given types. static size_t headSize(TypePointers const& _targetTypes); - /// Map from function name to code for a multi-use function. - std::map m_requestedFunctions; - std::set m_externallyUsedFunctions; langutil::EVMVersion m_evmVersion; + std::shared_ptr m_functionCollector; + std::set m_externallyUsedFunctions; + YulUtilFunctions m_utils; }; } diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.cpp b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp new file mode 100644 index 000000000..9dc66cea2 --- /dev/null +++ b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp @@ -0,0 +1,52 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Container of (unparsed) Yul functions identified by name which are meant to be generated + * only once. + */ + +#include + +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +string MultiUseYulFunctionCollector::requestedFunctions() +{ + string result; + for (auto const& f: m_requestedFunctions) + result += f.second; + m_requestedFunctions.clear(); + return result; +} + +string MultiUseYulFunctionCollector::createFunction(string const& _name, function const& _creator) +{ + if (!m_requestedFunctions.count(_name)) + { + string fun = _creator(); + solAssert(!fun.empty(), ""); + solAssert(fun.find("function " + _name) != string::npos, "Function not properly named."); + m_requestedFunctions[_name] = std::move(fun); + } + return _name; +} diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.h b/libsolidity/codegen/MultiUseYulFunctionCollector.h new file mode 100644 index 000000000..321474f90 --- /dev/null +++ b/libsolidity/codegen/MultiUseYulFunctionCollector.h @@ -0,0 +1,56 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Container of (unparsed) Yul functions identified by name which are meant to be generated + * only once. + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +/** + * Container of (unparsed) Yul functions identified by name which are meant to be generated + * only once. + */ +class MultiUseYulFunctionCollector +{ +public: + /// Helper function that uses @a _creator to create a function and add it to + /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both + /// cases. + std::string createFunction(std::string const& _name, std::function const& _creator); + + /// @returns concatenation of all generated functions. + /// Clears the internal list, i.e. calling it again will result in an + /// empty return value. + std::string requestedFunctions(); + +private: + /// Map from function name to code for a multi-use function. + std::map m_requestedFunctions; +}; + +} +} diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp new file mode 100644 index 000000000..489980f96 --- /dev/null +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -0,0 +1,438 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Component that can generate various useful Yul functions. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +string YulUtilFunctions::combineExternalFunctionIdFunction() +{ + string functionName = "combine_external_function_id"; + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (addr, selector) -> combined { + combined := (or((addr), and(selector, 0xffffffff))) + } + )") + ("functionName", functionName) + ("shl32", shiftLeftFunction(32)) + ("shl64", shiftLeftFunction(64)) + .render(); + }); +} + +string YulUtilFunctions::splitExternalFunctionIdFunction() +{ + string functionName = "split_external_function_id"; + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (combined) -> addr, selector { + combined := (combined) + selector := and(combined, 0xffffffff) + addr := (combined) + } + )") + ("functionName", functionName) + ("shr32", shiftRightFunction(32)) + ("shr64", shiftRightFunction(64)) + .render(); + }); +} + +string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata) +{ + string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; + return m_functionCollector->createFunction(functionName, [&]() { + if (_fromCalldata) + { + return Whiskers(R"( + function (src, dst, length) { + calldatacopy(dst, src, length) + // clear end + mstore(add(dst, length), 0) + } + )") + ("functionName", functionName) + .render(); + } + else + { + return Whiskers(R"( + function (src, dst, length) { + let i := 0 + for { } lt(i, length) { i := add(i, 32) } + { + mstore(add(dst, i), mload(add(src, i))) + } + if gt(i, length) + { + // clear end + mstore(add(dst, length), 0) + } + } + )") + ("functionName", functionName) + .render(); + } + }); +} + +string YulUtilFunctions::leftAlignFunction(Type const& _type) +{ + string functionName = string("leftAlign_") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> aligned { + + } + )"); + templ("functionName", functionName); + switch (_type.category()) + { + case Type::Category::Address: + templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)"); + break; + case Type::Category::Integer: + { + IntegerType const& type = dynamic_cast(_type); + if (type.numBits() == 256) + templ("body", "aligned := value"); + else + templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)"); + break; + } + case Type::Category::RationalNumber: + solAssert(false, "Left align requested for rational number."); + break; + case Type::Category::Bool: + templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)"); + break; + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Array: + case Type::Category::Struct: + solAssert(false, "Left align requested for non-value type."); + break; + case Type::Category::FixedBytes: + templ("body", "aligned := value"); + break; + case Type::Category::Contract: + templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)"); + break; + case Type::Category::Enum: + { + unsigned storageBytes = dynamic_cast(_type).storageBytes(); + templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)"); + break; + } + case Type::Category::InaccessibleDynamic: + solAssert(false, "Left align requested for inaccessible dynamic type."); + break; + default: + solAssert(false, "Left align of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + +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(); + }); + } +} + +string YulUtilFunctions::shiftRightFunction(size_t _numBits) +{ + solAssert(_numBits < 256, ""); + + // 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"; + if (m_evmVersion.hasBitwiseShifting()) + { + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := shr(, value) + } + )") + ("functionName", functionName) + ("numBits", to_string(_numBits)) + .render(); + }); + } + else + { + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := div(value, ) + } + )") + ("functionName", functionName) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); + } +} + +string YulUtilFunctions::roundUpFunction() +{ + string functionName = "round_up_to_mul_of_32"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> result { + result := and(add(value, 31), not(31)) + } + )") + ("functionName", functionName) + .render(); + }); +} + +string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) +{ + string functionName = "array_length_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers w(R"( + function (value) -> length { + + } + )"); + w("functionName", functionName); + string body; + if (!_type.isDynamicallySized()) + body = "length := " + toCompactHexWithPrefix(_type.length()); + else + { + switch (_type.location()) + { + case DataLocation::CallData: + solAssert(false, "called regular array length function on calldata array"); + break; + case DataLocation::Memory: + body = "length := mload(value)"; + break; + case DataLocation::Storage: + if (_type.isByteArray()) + { + // Retrieve length both for in-place strings and off-place strings: + // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 + // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it + // computes (x & (-1)) / 2, which is equivalent to just x / 2. + body = R"( + length := sload(value) + let mask := sub(mul(0x100, iszero(and(length, 1))), 1) + length := div(and(length, mask), 2) + )"; + } + else + body = "length := sload(value)"; + break; + } + } + solAssert(!body.empty(), ""); + w("body", body); + return w.render(); + }); +} + +string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + string functionName = "array_allocation_size_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers w(R"( + function (length) -> size { + // Make sure we can allocate memory without overflow + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + size := + + } + )"); + w("functionName", functionName); + if (_type.isByteArray()) + // Round up + w("allocationSize", "and(add(length, 0x1f), not(0x1f))"); + else + w("allocationSize", "mul(length, 0x20)"); + if (_type.isDynamicallySized()) + w("addLengthSlot", "size := add(size, 0x20)"); + else + w("addLengthSlot", ""); + return w.render(); + }); +} + +string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) +{ + string functionName = "array_dataslot_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + if (_type.dataStoredIn(DataLocation::Memory)) + { + if (_type.isDynamicallySized()) + return Whiskers(R"( + function (memPtr) -> dataPtr { + dataPtr := add(memPtr, 0x20) + } + )") + ("functionName", functionName) + .render(); + else + return Whiskers(R"( + function (memPtr) -> dataPtr { + dataPtr := memPtr + } + )") + ("functionName", functionName) + .render(); + } + else if (_type.dataStoredIn(DataLocation::Storage)) + { + if (_type.isDynamicallySized()) + { + Whiskers w(R"( + function (slot) -> dataSlot { + mstore(0, slot) + dataSlot := keccak256(0, 0x20) + } + )"); + w("functionName", functionName); + return w.render(); + } + else + { + Whiskers w(R"( + function (slot) -> dataSlot { + dataSlot := slot + } + )"); + w("functionName", functionName); + return w.render(); + } + } + else + { + // Not used for calldata + solAssert(false, ""); + } + }); +} + +string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) +{ + solAssert(!_type.isByteArray(), ""); + solAssert( + _type.location() == DataLocation::Memory || + _type.location() == DataLocation::Storage, + "" + ); + solAssert( + _type.location() == DataLocation::Memory || + _type.baseType()->storageBytes() > 16, + "" + ); + string functionName = "array_nextElement_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + if (_type.location() == DataLocation::Memory) + return Whiskers(R"( + function (memPtr) -> nextPtr { + nextPtr := add(memPtr, 0x20) + } + )") + ("functionName", functionName) + .render(); + else if (_type.location() == DataLocation::Storage) + return Whiskers(R"( + function (slot) -> nextSlot { + nextSlot := add(slot, 1) + } + )") + ("functionName", functionName) + .render(); + else + solAssert(false, ""); + }); +} + +string YulUtilFunctions::allocationFunction() +{ + string functionName = "allocateMemory"; + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (size) -> memPtr { + memPtr := mload() + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(, newFreePtr) + } + )") + ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) + ("functionName", functionName) + .render(); + }); +} + diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h new file mode 100644 index 000000000..1a21ece2b --- /dev/null +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -0,0 +1,100 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Component that can generate various useful Yul functions. + */ + +#pragma once + +#include + +#include + +#include +#include + +namespace dev +{ +namespace solidity +{ + +class Type; +class ArrayType; + +/** + * Component that can generate various useful Yul functions. + */ +class YulUtilFunctions +{ +public: + explicit YulUtilFunctions( + langutil::EVMVersion _evmVersion, + std::shared_ptr _functionCollector + ): + m_evmVersion(_evmVersion), + m_functionCollector(std::move(_functionCollector)) + {} + + /// @returns a function that combines the address and selector to a single value + /// for use in the ABI. + std::string combineExternalFunctionIdFunction(); + + /// @returns a function that splits the address and selector from a single value + /// for use in the ABI. + std::string splitExternalFunctionIdFunction(); + + /// @returns a function that copies raw bytes of dynamic length from calldata + /// or memory to memory. + /// Pads with zeros and might write more than exactly length. + std::string copyToMemoryFunction(bool _fromCalldata); + + /// @returns the name of a function that takes a (cleaned) value of the given value type and + /// left-aligns it, usually for use in non-padded encoding. + std::string leftAlignFunction(Type const& _type); + + std::string shiftLeftFunction(size_t _numBits); + std::string shiftRightFunction(size_t _numBits); + /// @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(); + + std::string arrayLengthFunction(ArrayType const& _type); + /// @returns the name of a function that computes the number of bytes required + /// to store an array in memory given its length (internally encoded, not ABI encoded). + /// The function reverts for too large lengths. + std::string arrayAllocationSizeFunction(ArrayType const& _type); + /// @returns the name of a function that converts a storage slot number + /// or a memory pointer to the slot number / memory pointer for the data position of an array + /// which is stored in that slot / memory area. + std::string arrayDataAreaFunction(ArrayType const& _type); + /// @returns the name of a function that advances an array data pointer to the next element. + /// Only works for memory arrays and storage arrays that store one item per slot. + std::string nextArrayElementFunction(ArrayType const& _type); + + /// @returns the name of a function that allocates memory. + /// Modifies the "free memory pointer" + /// Arguments: size + /// Return value: pointer + std::string allocationFunction(); + +private: + langutil::EVMVersion m_evmVersion; + std::shared_ptr m_functionCollector; +}; + +} +}