/*
	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 .
*/
// SPDX-License-Identifier: GPL-3.0
/**
 * Component that can generate various useful Yul functions.
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
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::requireOrAssertFunction(bool _assert, Type const* _messageType)
{
	string functionName =
		string(_assert ? "assert_helper" : "require_helper") +
		(_messageType ? ("_" + _messageType->identifier()) : "");
	solAssert(!_assert || !_messageType, "Asserts can't have messages!");
	return m_functionCollector.createFunction(functionName, [&]() {
		if (!_messageType)
			return Whiskers(R"(
				function (condition) {
					if iszero(condition) {  }
				}
			)")
			("error", _assert ? panicFunction() + "()" : "revert(0, 0)")
			("functionName", functionName)
			.render();
		int const hashHeaderSize = 4;
		u256 const errorHash = util::selectorFromSignature("Error(string)");
		string const encodeFunc = ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector)
			.tupleEncoder(
				{_messageType},
				{TypeProvider::stringMemory()}
			);
		return Whiskers(R"(
			function (condition ) {
				if iszero(condition) {
					let fmp := mload()
					mstore(fmp, )
					let end := (add(fmp, ) )
					revert(fmp, sub(end, fmp))
				}
			}
		)")
		("functionName", functionName)
		("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer))
		("errorHash", formatNumber(errorHash))
		("abiEncodeFunc", encodeFunc)
		("hashHeaderSize", to_string(hashHeaderSize))
		("messageVars",
			(_messageType->sizeOnStack() > 0 ? ", " : "") +
			suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack())
		)
		.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(*TypeProvider::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);
	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::shiftLeftFunctionDynamic()
{
	string functionName = "shift_left_dynamic";
	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)
{
	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";
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (value) -> newValue {
				newValue :=
				
					shr(, value)
				
					div(value, )
				
			}
			)")
			("functionName", functionName)
			("hasShifts", m_evmVersion.hasBitwiseShifting())
			("numBits", to_string(_numBits))
			("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
			.render();
	});
}
string YulUtilFunctions::shiftRightFunctionDynamic()
{
	string const functionName = "shift_right_unsigned_dynamic";
	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::shiftRightSignedFunctionDynamic()
{
	string const functionName = "shift_right_signed_dynamic";
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (bits, value) -> result {
				
					result := sar(bits, value)
				
					let divisor := exp(2, bits)
					let xor_mask := sub(0, slt(value, 0))
					result := xor(div(xor(value, xor_mask), divisor), xor_mask)
					// combined version of
					//   switch slt(value, 0)
					//   case 0 { result := div(value, divisor) }
					//   default { result := not(div(not(value), divisor)) }
				
			}
			)")
			("functionName", functionName)
			("hasShifts", m_evmVersion.hasBitwiseShifting())
			.render();
	});
}
string YulUtilFunctions::typedShiftLeftFunction(Type const& _type, Type const& _amountType)
{
	solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, "");
	solAssert(_amountType.category() == Type::Category::Integer, "");
	solAssert(!dynamic_cast(_amountType).isSigned(), "");
	string const functionName = "shift_left_" + _type.identifier() + "_" + _amountType.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (value, bits) -> result {
				bits := (bits)
				result := ((bits, value))
			}
			)")
			("functionName", functionName)
			("cleanAmount", cleanupFunction(_amountType))
			("shift", shiftLeftFunctionDynamic())
			("cleanup", cleanupFunction(_type))
			.render();
	});
}
string YulUtilFunctions::typedShiftRightFunction(Type const& _type, Type const& _amountType)
{
	solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, "");
	solAssert(_amountType.category() == Type::Category::Integer, "");
	solAssert(!dynamic_cast(_amountType).isSigned(), "");
	IntegerType const* integerType = dynamic_cast(&_type);
	bool valueSigned = integerType && integerType->isSigned();
	string const functionName = "shift_right_" + _type.identifier() + "_" + _amountType.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (value, bits) -> result {
				bits := (bits)
				result := ((bits, (value)))
			}
			)")
			("functionName", functionName)
			("cleanAmount", cleanupFunction(_amountType))
			("shift", valueSigned ? shiftRightSignedFunctionDynamic() : shiftRightFunctionDynamic())
			("cleanup", cleanupFunction(_type))
			.render();
	});
}
string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes)
{
	solAssert(_numBytes <= 32, "");
	solAssert(_shiftBytes <= 32, "");
	size_t numBits = _numBytes * 8;
	size_t shiftBits = _shiftBytes * 8;
	string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (value, toInsert) -> result {
				let mask := 
				toInsert := (toInsert)
				value := and(value, not(mask))
				result := or(value, and(toInsert, mask))
			}
			)")
			("functionName", functionName)
			("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits))
			("shl", shiftLeftFunction(shiftBits))
			.render();
	});
}
string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
{
	solAssert(_numBytes <= 32, "");
	size_t numBits = _numBytes * 8;
	string functionName = "update_byte_slice_dynamic" + 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", shiftLeftFunctionDynamic())
			.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::overflowCheckedIntAddFunction(IntegerType const& _type)
{
	string functionName = "checked_add_" + _type.identifier();
	// TODO: Consider to add a special case for unsigned 256-bit integers
	//       and use the following instead:
	//       sum := add(x, y) if lt(sum, x) { () }
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (x, y) -> sum {
				x := (x)
				y := (y)
				
					// overflow, if x >= 0 and y > (maxValue - x)
					if and(iszero(slt(x, 0)), sgt(y, sub(, x))) { () }
					// underflow, if x < 0 and y < (minValue - x)
					if and(slt(x, 0), slt(y, sub(, x))) { () }
				
					// overflow, if x > (maxValue - y)
					if gt(x, sub(, y)) { () }
				
				sum := add(x, y)
			}
			)")
			("functionName", functionName)
			("signed", _type.isSigned())
			("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
			("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
			("cleanupFunction", cleanupFunction(_type))
			("panic", panicFunction())
			.render();
	});
}
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
{
	string functionName = "checked_mul_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			// Multiplication by zero could be treated separately and directly return zero.
			Whiskers(R"(
			function (x, y) -> product {
				x := (x)
				y := (y)
				
					// overflow, if x > 0, y > 0 and x > (maxValue / y)
					if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(, y))) { () }
					// underflow, if x > 0, y < 0 and y < (minValue / x)
					if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(, x))) { () }
					// underflow, if x < 0, y > 0 and x < (minValue / y)
					if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(, y))) { () }
					// overflow, if x < 0, y < 0 and x < (maxValue / y)
					if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(, y))) { () }
				
					// overflow, if x != 0 and y > (maxValue / x)
					if and(iszero(iszero(x)), gt(y, div(, x))) { () }
				
				product := mul(x, y)
			}
			)")
			("functionName", functionName)
			("signed", _type.isSigned())
			("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
			("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
			("cleanupFunction", cleanupFunction(_type))
			("panic", panicFunction())
			.render();
	});
}
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{
	string functionName = "checked_div_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (x, y) -> r {
				x := (x)
				y := (y)
				if iszero(y) { () }
				
				// overflow for minVal / -1
				if and(
					eq(x, ),
					eq(y, sub(0, 1))
				) { () }
				
				r := sdiv(x, y)
			}
			)")
			("functionName", functionName)
			("signed", _type.isSigned())
			("minVal", toCompactHexWithPrefix(u256(_type.minValue())))
			("cleanupFunction", cleanupFunction(_type))
			("panic", panicFunction())
			.render();
	});
}
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
{
	string functionName = "checked_mod_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (x, y) -> r {
				x := (x)
				y := (y)
				if iszero(y) { () }
				r := smod(x, y)
			}
			)")
			("functionName", functionName)
			("signed", _type.isSigned())
			("cleanupFunction", cleanupFunction(_type))
			("panic", panicFunction())
			.render();
	});
}
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
{
	string functionName = "checked_sub_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&] {
		return
			Whiskers(R"(
			function (x, y) -> diff {
				x := (x)
				y := (y)
				
					// underflow, if y >= 0 and x < (minValue + y)
					if and(iszero(slt(y, 0)), slt(x, add(, y))) { () }
					// overflow, if y < 0 and x > (maxValue + y)
					if and(slt(y, 0), sgt(x, add(, y))) { () }
				
					if lt(x, y) { () }
				
				diff := sub(x, y)
			}
			)")
			("functionName", functionName)
			("signed", _type.isSigned())
			("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
			("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
			("cleanupFunction", cleanupFunction(_type))
			("panic", panicFunction())
			.render();
	});
}
string YulUtilFunctions::overflowCheckedIntExpFunction(
	IntegerType const& _type,
	IntegerType const& _exponentType
)
{
	solAssert(!_exponentType.isSigned(), "");
	string functionName = "checked_exp_" + _type.identifier() + "_" + _exponentType.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (base, exponent) -> power {
				base := (base)
				exponent := (exponent)
				
					power := (base, exponent, , )
				
					power := (base, exponent, )
				
			}
			)")
			("functionName", functionName)
			("signed", _type.isSigned())
			("exp", _type.isSigned() ? overflowCheckedSignedExpFunction() : overflowCheckedUnsignedExpFunction())
			("maxValue", toCompactHexWithPrefix(_type.max()))
			("minValue", toCompactHexWithPrefix(_type.min()))
			("baseCleanupFunction", cleanupFunction(_type))
			("exponentCleanupFunction", cleanupFunction(_exponentType))
			.render();
	});
}
string YulUtilFunctions::overflowCheckedIntLiteralExpFunction(
	RationalNumberType const& _baseType,
	IntegerType const& _exponentType,
	IntegerType const& _commonType
)
{
	solAssert(!_exponentType.isSigned(), "");
	solAssert(_baseType.isNegative() == _commonType.isSigned(), "");
	solAssert(_commonType.numBits() == 256, "");
	string functionName = "checked_exp_" + _baseType.richIdentifier() + "_" + _exponentType.identifier();
	return m_functionCollector.createFunction(functionName, [&]()
	{
		// Converts a bigint number into u256 (negative numbers represented in two's complement form.)
		// We assume that `_v` fits in 256 bits.
		auto bigint2u = [&](bigint const& _v) -> u256
		{
			if (_v < 0)
				return s2u(s256(_v));
			return u256(_v);
		};
		// Calculates the upperbound for exponentiation, that is, calculate `b`, such that
		// _base**b <= _maxValue and _base**(b + 1) > _maxValue
		auto findExponentUpperbound = [](bigint const _base, bigint const _maxValue) -> unsigned
		{
			// There is no overflow for these cases
			if (_base == 0 || _base == -1 || _base == 1)
				return 0;
			unsigned first = 0;
			unsigned last = 255;
			unsigned middle;
			while (first < last)
			{
				middle = (first + last) / 2;
				if (
					// The condition on msb is a shortcut that avoids computing large powers in
					// arbitrary precision.
					boost::multiprecision::msb(_base) * middle <= boost::multiprecision::msb(_maxValue) &&
					boost::multiprecision::pow(_base, middle) <= _maxValue
				)
				{
					if (boost::multiprecision::pow(_base, middle + 1) > _maxValue)
						return middle;
					else
						first = middle + 1;
				}
				else
					last = middle;
			}
			return last;
		};
		bigint baseValue = _baseType.isNegative() ?
			u2s(_baseType.literalValue(nullptr)) :
			_baseType.literalValue(nullptr);
		bool needsOverflowCheck = !((baseValue == 0) || (baseValue == -1) || (baseValue == 1));
		unsigned exponentUpperbound;
		if (_baseType.isNegative())
		{
			// Only checks for underflow. The only case where this can be a problem is when, for a
			// negative base, say `b`, and an even exponent, say `e`, `b**e = 2**255` (which is an
			// overflow.) But this never happens because, `255 = 3*5*17`, and therefore there is no even
			// number `e` such that `b**e = 2**255`.
			exponentUpperbound = findExponentUpperbound(abs(baseValue), abs(_commonType.minValue()));
			bigint power = boost::multiprecision::pow(baseValue, exponentUpperbound);
			bigint overflowedPower = boost::multiprecision::pow(baseValue, exponentUpperbound + 1);
			if (needsOverflowCheck)
				solAssert(
					(power <= _commonType.maxValue()) && (power >= _commonType.minValue()) &&
					!((overflowedPower <= _commonType.maxValue()) && (overflowedPower >= _commonType.minValue())),
					"Incorrect exponent upper bound calculated."
				);
		}
		else
		{
			exponentUpperbound = findExponentUpperbound(baseValue, _commonType.maxValue());
			if (needsOverflowCheck)
				solAssert(
					boost::multiprecision::pow(baseValue, exponentUpperbound) <= _commonType.maxValue() &&
					boost::multiprecision::pow(baseValue, exponentUpperbound + 1) > _commonType.maxValue(),
					"Incorrect exponent upper bound calculated."
				);
		}
		return Whiskers(R"(
			function (exponent) -> power {
				exponent := (exponent)
				
				if gt(exponent, ) { () }
				
				power := exp(, exponent)
			}
			)")
			("functionName", functionName)
			("exponentCleanupFunction", cleanupFunction(_exponentType))
			("needsOverflowCheck", needsOverflowCheck)
			("exponentUpperbound", to_string(exponentUpperbound))
			("panic", panicFunction())
			("base", bigint2u(baseValue).str())
			.render();
	});
}
string YulUtilFunctions::overflowCheckedUnsignedExpFunction()
{
	// Checks for the "small number specialization" below.
	using namespace boost::multiprecision;
	solAssert(pow(bigint(10), 77) < pow(bigint(2), 256), "");
	solAssert(pow(bigint(11), 77) >= pow(bigint(2), 256), "");
	solAssert(pow(bigint(10), 78) >= pow(bigint(2), 256), "");
	solAssert(pow(bigint(306), 31) < pow(bigint(2), 256), "");
	solAssert(pow(bigint(307), 31) >= pow(bigint(2), 256), "");
	solAssert(pow(bigint(306), 32) >= pow(bigint(2), 256), "");
	string functionName = "checked_exp_unsigned";
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (base, exponent, max) -> power {
				// This function currently cannot be inlined because of the
				// "leave" statements. We have to improve the optimizer.
				// Note that 0**0 == 1
				if iszero(exponent) { power := 1 leave }
				if iszero(base) { power := 0 leave }
				// Specializations for small bases
				switch base
				// 0 is handled above
				case 1 { power := 1 leave }
				case 2
				{
					if gt(exponent, 255) { () }
					power := exp(2, exponent)
					if gt(power, max) { () }
					leave
				}
				if or(
					and(lt(base, 11), lt(exponent, 78)),
					and(lt(base, 307), lt(exponent, 32))
				)
				{
					power := exp(base, exponent)
					if gt(power, max) { () }
					leave
				}
				power, base := (1, base, exponent, max)
				if gt(power, div(max, base)) { () }
				power := mul(power, base)
			}
			)")
			("functionName", functionName)
			("panic", panicFunction())
			("expLoop", overflowCheckedExpLoopFunction())
			("shr_1", shiftRightFunction(1))
			.render();
	});
}
string YulUtilFunctions::overflowCheckedSignedExpFunction()
{
	string functionName = "checked_exp_signed";
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (base, exponent, min, max) -> power {
				// Currently, `leave` avoids this function being inlined.
				// We have to improve the optimizer.
				// Note that 0**0 == 1
				switch exponent
				case 0 { power := 1 leave }
				case 1 { power := base leave }
				if iszero(base) { power := 0 leave }
				power := 1
				// We pull out the first iteration because it is the only one in which
				// base can be negative.
				// Exponent is at least 2 here.
				// overflow check for base * base
				switch sgt(base, 0)
				case 1 { if gt(base, div(max, base)) { () } }
				case 0 { if slt(base, sdiv(max, base)) { () } }
				if and(exponent, 1)
				{
					power := base
				}
				base := mul(base, base)
				exponent := (exponent)
				// Below this point, base is always positive.
				power, base := (power, base, exponent, max)
				if and(sgt(power, 0), gt(power, div(max, base))) { () }
				if and(slt(power, 0), slt(power, sdiv(min, base))) { () }
				power := mul(power, base)
			}
			)")
			("functionName", functionName)
			("panic", panicFunction())
			("expLoop", overflowCheckedExpLoopFunction())
			("shr_1", shiftRightFunction(1))
			.render();
	});
}
string YulUtilFunctions::overflowCheckedExpLoopFunction()
{
	// We use this loop for both signed and unsigned exponentiation
	// because we pull out the first iteration in the signed case which
	// results in the base always being positive.
	// This function does not include the final multiplication.
	string functionName = "checked_exp_helper";
	return m_functionCollector.createFunction(functionName, [&]() {
		return
			Whiskers(R"(
			function (_power, _base, exponent, max) -> power, base {
				power := _power
				base  := _base
				for { } gt(exponent, 1) {}
				{
					// overflow check for base * base
					if gt(base, div(max, base)) { () }
					if and(exponent, 1)
					{
						// No checks for power := mul(power, base) needed, because the check
						// for base * base above is sufficient, since:
						// |power| <= base (proof by induction) and thus:
						// |power * base| <= base * base <= max <= |min| (for signed)
						// (this is equally true for signed and unsigned exp)
						power := mul(power, base)
					}
					base := mul(base, base)
					exponent := (exponent)
				}
			}
			)")
			("functionName", functionName)
			("panic", panicFunction())
			("shr_1", shiftRightFunction(1))
			.render();
	});
}
string YulUtilFunctions::extractByteArrayLengthFunction()
{
	string functionName = "extract_byte_array_length";
	return m_functionCollector.createFunction(functionName, [&]() {
		Whiskers w(R"(
			function (data) -> length {
				// 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.
				let mask := sub(mul(0x100, iszero(and(data, 1))), 1)
				length := div(and(data, mask), 2)
			}
		)");
		w("functionName", functionName);
		return w.render();
	});
}
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
	string functionName = "array_length_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		Whiskers w(R"(
			function (value, len) -> length {
				
					
						length := mload(value)
					
					
						length := sload(value)
						
							length := (length)
						
					
					
						length := len
					
				
					length := 
				
			}
		)");
		w("functionName", functionName);
		w("dynamic", _type.isDynamicallySized());
		if (!_type.isDynamicallySized())
			w("length", toCompactHexWithPrefix(_type.length()));
		w("memory", _type.location() == DataLocation::Memory);
		w("storage", _type.location() == DataLocation::Storage);
		w("calldata", _type.location() == DataLocation::CallData);
		if (_type.location() == DataLocation::Storage)
		{
			w("byteArray", _type.isByteArray());
			if (_type.isByteArray())
				w("extractByteArrayLength", extractByteArrayLengthFunction());
		}
		return w.render();
	});
}
std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
{
	solAssert(_type.location() == DataLocation::Storage, "");
	solAssert(_type.isDynamicallySized(), "");
	solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
	solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "...");
	string functionName = "resize_array_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return Whiskers(R"(
			function (array, newLen) {
				if gt(newLen, ) {
					()
				}
				let oldLen := (array)
				// Store new length
				sstore(array, newLen)
				// Size was reduced, clear end of array
				if lt(newLen, oldLen) {
					let oldSlotCount := (oldLen)
					let newSlotCount := (newLen)
					let arrayDataStart := (array)
					let deleteStart := add(arrayDataStart, newSlotCount)
					let deleteEnd := add(arrayDataStart, oldSlotCount)
					
						// if we are dealing with packed array and offset is greater than zero
						// we have  to partially clear last slot that is still used, so decreasing start by one
						let offset := mul(mod(newLen, ), )
						if gt(offset, 0) { (sub(deleteStart, 1), offset) }
					
					(deleteStart, deleteEnd)
				}
			})")
			("functionName", functionName)
			("panic", panicFunction())
			("fetchLength", arrayLengthFunction(_type))
			("convertToSize", arrayConvertLengthToSize(_type))
			("dataPosition", arrayDataAreaFunction(_type))
			("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
			("maxArrayLength", (u256(1) << 64).str())
			("packed", _type.baseType()->storageBytes() <= 16)
			("itemsPerSlot", to_string(32 / _type.baseType()->storageBytes()))
			("storageBytes", to_string(_type.baseType()->storageBytes()))
			("partialClearStorageSlot", partialClearStorageSlotFunction())
			.render();
	});
}
string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
{
	solAssert(_type.location() == DataLocation::Storage, "");
	solAssert(_type.isDynamicallySized(), "");
	solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
	if (_type.isByteArray())
		return storageByteArrayPopFunction(_type);
	string functionName = "array_pop_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return Whiskers(R"(
			function (array) {
				let oldLen := (array)
				if iszero(oldLen) { () }
				let newLen := sub(oldLen, 1)
				let slot, offset := (array, newLen)
				(slot, offset)
				sstore(array, newLen)
			})")
			("functionName", functionName)
			("panic", panicFunction())
			("fetchLength", arrayLengthFunction(_type))
			("indexAccess", storageArrayIndexAccessFunction(_type))
			("setToZero", storageSetToZeroFunction(*_type.baseType()))
			.render();
	});
}
string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type)
{
	solAssert(_type.location() == DataLocation::Storage, "");
	solAssert(_type.isDynamicallySized(), "");
	solAssert(_type.isByteArray(), "");
	string functionName = "byte_array_pop_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return Whiskers(R"(
			function (array) {
				let data := sload(array)
				let oldLen := (data)
				if iszero(oldLen) { () }
				switch eq(oldLen, 32)
				case 1 {
					// Here we have a special case where array transitions to shorter than 32
					// So we need to copy data
					let copyFromSlot := (array)
					data := sload(copyFromSlot)
					sstore(copyFromSlot, 0)
					// New length is 31, encoded to 31 * 2 = 62
					data := or(and(data, not(0xff)), 62)
				}
				default {
					data := sub(data, 2)
					let newLen := sub(oldLen, 1)
					switch lt(oldLen, 32)
					case 1 {
						// set last element to zero
						let mask := not((mul(8, sub(31, newLen)), 0xff))
						data := and(data, mask)
					}
					default {
						let slot, offset := (array, newLen)
						(slot, offset)
					}
				}
				sstore(array, data)
			})")
			("functionName", functionName)
			("panic", panicFunction())
			("extractByteArrayLength", extractByteArrayLengthFunction())
			("dataAreaFunction", arrayDataAreaFunction(_type))
			("indexAccess", storageArrayIndexAccessFunction(_type))
			("setToZero", storageSetToZeroFunction(*_type.baseType()))
			("shl", shiftLeftFunctionDynamic())
			.render();
	});
}
string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
{
	solAssert(_type.location() == DataLocation::Storage, "");
	solAssert(_type.isDynamicallySized(), "");
	solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
	string functionName = "array_push_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return Whiskers(R"(
			function (array, value) {
				
					let data := sload(array)
					let oldLen := (data)
					if iszero(lt(oldLen, )) { () }
					switch gt(oldLen, 31)
					case 0 {
						value := byte(0, value)
						switch oldLen
						case 31 {
							// Here we have special case when array switches from short array to long array
							// We need to copy data
							let dataArea := (array)
							data := and(data, not(0xff))
							sstore(dataArea, or(and(0xff, value), data))
							// New length is 32, encoded as (32 * 2 + 1)
							sstore(array, 65)
						}
						default {
							data := add(data, 2)
							let shiftBits := mul(8, sub(31, oldLen))
							let valueShifted := (shiftBits, and(0xff, value))
							let mask := (shiftBits, 0xff)
							data := or(and(data, not(mask)), valueShifted)
							sstore(array, data)
						}
					}
					default {
						sstore(array, add(data, 2))
						let slot, offset := (array, oldLen)
						(slot, offset, value)
					}
				
					let oldLen := sload(array)
					if iszero(lt(oldLen, )) { () }
					sstore(array, add(oldLen, 1))
					let slot, offset := (array, oldLen)
					(slot, offset, value)
				
			})")
			("functionName", functionName)
			("panic", panicFunction())
			("extractByteArrayLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "")
			("dataAreaFunction", arrayDataAreaFunction(_type))
			("isByteArray", _type.isByteArray())
			("indexAccess", storageArrayIndexAccessFunction(_type))
			("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
			("maxArrayLength", (u256(1) << 64).str())
			("shl", shiftLeftFunctionDynamic())
			("shr", shiftRightFunction(248))
			.render();
	});
}
string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
{
	solAssert(_type.location() == DataLocation::Storage, "");
	solAssert(_type.isDynamicallySized(), "");
	solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
	solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
	string functionName = "array_push_zero_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return Whiskers(R"(
			function (array) -> slot, offset {
				let oldLen := (array)
				if iszero(lt(oldLen, )) { () }
				sstore(array, add(oldLen, 1))
				slot, offset := (array, oldLen)
			})")
			("functionName", functionName)
			("panic", panicFunction())
			("fetchLength", arrayLengthFunction(_type))
			("indexAccess", storageArrayIndexAccessFunction(_type))
			("maxArrayLength", (u256(1) << 64).str())
			.render();
	});
}
string YulUtilFunctions::partialClearStorageSlotFunction()
{
	string functionName = "partial_clear_storage_slot";
	return m_functionCollector.createFunction(functionName, [&]() {
		return Whiskers(R"(
		function (slot, offset) {
			let mask := (mul(8, sub(32, offset)), )
			sstore(slot, and(mask, sload(slot)))
		}
		)")
		("functionName", functionName)
		("ones", formatNumber((bigint(1) << 256) - 1))
		("shr", shiftRightFunctionDynamic())
		.render();
	});
}
string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
{
	if (_type.storageBytes() < 32)
		solAssert(_type.isValueType(), "");
	string functionName = "clear_storage_range_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return Whiskers(R"(
			function (start, end) {
				for {} lt(start, end) { start := add(start, ) }
				{
					(start, 0)
				}
			}
		)")
		("functionName", functionName)
		("setToZero", storageSetToZeroFunction(_type.storageBytes() < 32 ? *TypeProvider::uint256() : _type))
		("increment", _type.storageSize().str())
		.render();
	});
}
string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
{
	solAssert(_type.location() == DataLocation::Storage, "");
	if (_type.baseType()->storageBytes() < 32)
	{
		solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
		solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
	}
	if (_type.baseType()->isValueType())
		solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
	string functionName = "clear_storage_array_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&]() {
		return Whiskers(R"(
			function (slot) {
				
					(slot, 0)
				
					(slot, add(slot, ()))
				
			}
		)")
		("functionName", functionName)
		("dynamic", _type.isDynamicallySized())
		("resizeArray", _type.isDynamicallySized() ? resizeDynamicArrayFunction(_type) : "")
		(
			"clearRange",
			clearStorageRangeFunction(
				(_type.baseType()->storageBytes() < 32) ?
				*TypeProvider::uint256() :
				*_type.baseType()
			)
		)
		("lenToSize", arrayConvertLengthToSize(_type))
		("len", _type.length().str())
		.render();
	});
}
string YulUtilFunctions::clearStorageStructFunction(StructType const& _type)
{
	solAssert(_type.location() == DataLocation::Storage, "");
	string functionName = "clear_struct_storage_" + _type.identifier();
	return m_functionCollector.createFunction(functionName, [&] {
		MemberList::MemberMap structMembers = _type.nativeMembers(nullptr);
		vector