/* 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(); }); }