/*
	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 translates Solidity code into Yul at statement level and below.
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
using namespace std::string_literals;
namespace
{
struct CopyTranslate: public yul::ASTCopier
{
	using ExternalRefsMap = std::map;
	CopyTranslate(yul::Dialect const& _dialect, IRGenerationContext& _context, ExternalRefsMap const& _references):
		m_dialect(_dialect), m_context(_context), m_references(_references) {}
	using ASTCopier::operator();
	yul::Expression operator()(yul::Identifier const& _identifier) override
	{
		// The operator() function is only called in lvalue context. In rvalue context,
		// only translate(yul::Identifier) is called.
		if (m_references.count(&_identifier))
			return translateReference(_identifier);
		else
			return ASTCopier::operator()(_identifier);
	}
	yul::YulString translateIdentifier(yul::YulString _name) override
	{
		// Strictly, the dialect used by inline assembly (m_dialect) could be different
		// from the Yul dialect we are compiling to. So we are assuming here that the builtin
		// functions are identical. This should not be a problem for now since everything
		// is EVM anyway.
		if (m_dialect.builtin(_name))
			return _name;
		else
			return yul::YulString{"usr$" + _name.str()};
	}
	yul::Identifier translate(yul::Identifier const& _identifier) override
	{
		if (!m_references.count(&_identifier))
			return ASTCopier::translate(_identifier);
		yul::Expression translated = translateReference(_identifier);
		solAssert(holds_alternative(translated), "");
		return get(std::move(translated));
	}
private:
	/// Translates a reference to a local variable, potentially including
	/// a suffix. Might return a literal, which causes this to be invalid in
	/// lvalue-context.
	yul::Expression translateReference(yul::Identifier const& _identifier)
	{
		auto const& reference = m_references.at(&_identifier);
		auto const varDecl = dynamic_cast(reference.declaration);
		solUnimplementedAssert(varDecl, "");
		string const& suffix = reference.suffix;
		string value;
		if (suffix.empty() && varDecl->isLocalVariable())
		{
			auto const& var = m_context.localVariable(*varDecl);
			solAssert(var.type().sizeOnStack() == 1, "");
			value = var.commaSeparatedList();
		}
		else if (varDecl->isConstant())
		{
			VariableDeclaration const* variable = rootConstVariableDeclaration(*varDecl);
			solAssert(variable, "");
			if (variable->value()->annotation().type->category() == Type::Category::RationalNumber)
			{
				u256 intValue = dynamic_cast(*variable->value()->annotation().type).literalValue(nullptr);
				if (auto const* bytesType = dynamic_cast(variable->type()))
					intValue <<= 256 - 8 * bytesType->numBytes();
				else
					solAssert(variable->type()->category() == Type::Category::Integer, "");
				value = intValue.str();
			}
			else if (auto const* literal = dynamic_cast(variable->value().get()))
			{
				TypePointer type = literal->annotation().type;
				switch (type->category())
				{
				case Type::Category::Bool:
				case Type::Category::Address:
					solAssert(type->category() == variable->annotation().type->category(), "");
					value = toCompactHexWithPrefix(type->literalValue(literal));
					break;
				case Type::Category::StringLiteral:
				{
					auto const& stringLiteral = dynamic_cast(*type);
					solAssert(variable->type()->category() == Type::Category::FixedBytes, "");
					unsigned const numBytes = dynamic_cast(*variable->type()).numBytes();
					solAssert(stringLiteral.value().size() <= numBytes, "");
					value = formatNumber(u256(h256(stringLiteral.value(), h256::AlignLeft)));
					break;
				}
				default:
					solAssert(false, "");
				}
			}
			else
				solAssert(false, "Invalid constant in inline assembly.");
		}
		else if (varDecl->isStateVariable())
		{
			if (suffix == "slot")
				value = m_context.storageLocationOfStateVariable(*varDecl).first.str();
			else if (suffix == "offset")
				value = to_string(m_context.storageLocationOfStateVariable(*varDecl).second);
			else
				solAssert(false, "");
		}
		else if (varDecl->type()->dataStoredIn(DataLocation::Storage))
		{
			solAssert(suffix == "slot" || suffix == "offset", "");
			solAssert(varDecl->isLocalVariable(), "");
			if (suffix == "slot")
				value = IRVariable{*varDecl}.part("slot").name();
			else if (varDecl->type()->isValueType())
				value = IRVariable{*varDecl}.part("offset").name();
			else
			{
				solAssert(!IRVariable{*varDecl}.hasPart("offset"), "");
				value = "0";
			}
		}
		else if (varDecl->type()->dataStoredIn(DataLocation::CallData))
		{
			solAssert(suffix == "offset" || suffix == "length", "");
			value = IRVariable{*varDecl}.part(suffix).name();
		}
		else
			solAssert(false, "");
		if (isdigit(value.front()))
			return yul::Literal{_identifier.location, yul::LiteralKind::Number, yul::YulString{value}, {}};
		else
			return yul::Identifier{_identifier.location, yul::YulString{value}};
	}
	yul::Dialect const& m_dialect;
	IRGenerationContext& m_context;
	ExternalRefsMap const& m_references;
};
}
string IRGeneratorForStatements::code() const
{
	solAssert(!m_currentLValue, "LValue not reset!");
	return m_code.str();
}
void IRGeneratorForStatements::generate(Block const& _block)
{
	try
	{
		_block.accept(*this);
	}
	catch (langutil::UnimplementedFeatureError const& _error)
	{
		if (!boost::get_error_info(_error))
			_error << langutil::errinfo_sourceLocation(m_currentLocation);
		throw _error;
	}
}
void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl)
{
	try
	{
		setLocation(_varDecl);
		solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable.");
		solAssert(!_varDecl.isConstant(), "");
		if (!_varDecl.value())
			return;
		_varDecl.value()->accept(*this);
		Type const* rightIntermediateType = _varDecl.value()->annotation().type->closestTemporaryType(_varDecl.type());
		solAssert(rightIntermediateType, "");
		IRVariable value = convert(*_varDecl.value(), *rightIntermediateType);
		writeToLValue(
			_varDecl.immutable() ?
			IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
			IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
				util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
				m_context.storageLocationOfStateVariable(_varDecl).second
			}},
			value
		);
	}
	catch (langutil::UnimplementedFeatureError const& _error)
	{
		if (!boost::get_error_info(_error))
			_error << langutil::errinfo_sourceLocation(m_currentLocation);
		throw _error;
	}
}
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
{
	try
	{
		setLocation(_varDecl);
		solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
		auto const* type = _varDecl.type();
		if (dynamic_cast(type))
			return;
		else if (auto const* refType = dynamic_cast(type))
			if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
				return;
		IRVariable zero = zeroValue(*type);
		assign(m_context.localVariable(_varDecl), zero);
	}
	catch (langutil::UnimplementedFeatureError const& _error)
	{
		if (!boost::get_error_info(_error))
			_error << langutil::errinfo_sourceLocation(m_currentLocation);
		throw _error;
	}
}
IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType)
{
	try
	{
		setLocation(_expression);
		_expression.accept(*this);
		IRVariable variable{m_context.newYulVariable(), _targetType};
		define(variable, _expression);
		return variable;
	}
	catch (langutil::UnimplementedFeatureError const& _error)
	{
		if (!boost::get_error_info(_error))
			_error << langutil::errinfo_sourceLocation(m_currentLocation);
		throw _error;
	}
}
string IRGeneratorForStatements::constantValueFunction(VariableDeclaration const& _constant)
{
	try
	{
		setLocation(_constant);
		string functionName = IRNames::constantValueFunction(_constant);
		return m_context.functionCollector().createFunction(functionName, [&] {
			Whiskers templ(R"(
				function () ->  {
					
					 := 
				}
			)");
			templ("functionName", functionName);
			IRGeneratorForStatements generator(m_context, m_utils);
			solAssert(_constant.value(), "");
			Type const& constantType = *_constant.type();
			templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList());
			templ("code", generator.code());
			templ("ret", IRVariable("ret", constantType).commaSeparatedList());
			return templ.render();
		});
	}
	catch (langutil::UnimplementedFeatureError const& _error)
	{
		if (!boost::get_error_info(_error))
			_error << langutil::errinfo_sourceLocation(m_currentLocation);
		throw _error;
	}
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
	setLocation(_varDeclStatement);
	if (Expression const* expression = _varDeclStatement.initialValue())
	{
		if (_varDeclStatement.declarations().size() > 1)
		{
			auto const* tupleType = dynamic_cast(expression->annotation().type);
			solAssert(tupleType, "Expected expression of tuple type.");
			solAssert(_varDeclStatement.declarations().size() == tupleType->components().size(), "Invalid number of tuple components.");
			for (size_t i = 0; i < _varDeclStatement.declarations().size(); ++i)
				if (auto const& decl = _varDeclStatement.declarations()[i])
				{
					solAssert(tupleType->components()[i], "");
					define(m_context.addLocalVariable(*decl), IRVariable(*expression).tupleComponent(i));
				}
		}
		else
		{
			VariableDeclaration const& varDecl = *_varDeclStatement.declarations().front();
			define(m_context.addLocalVariable(varDecl), *expression);
		}
	}
	else
		for (auto const& decl: _varDeclStatement.declarations())
			if (decl)
			{
				declare(m_context.addLocalVariable(*decl));
				initializeLocalVar(*decl);
			}
}
bool IRGeneratorForStatements::visit(Conditional const& _conditional)
{
	_conditional.condition().accept(*this);
	setLocation(_conditional);
	string condition = expressionAsType(_conditional.condition(), *TypeProvider::boolean());
	declare(_conditional);
	m_code << "switch " << condition << "\n" "case 0 {\n";
	_conditional.falseExpression().accept(*this);
	setLocation(_conditional);
	assign(_conditional, _conditional.falseExpression());
	m_code << "}\n" "default {\n";
	_conditional.trueExpression().accept(*this);
	setLocation(_conditional);
	assign(_conditional, _conditional.trueExpression());
	m_code << "}\n";
	return false;
}
bool IRGeneratorForStatements::visit(Assignment const& _assignment)
{
	_assignment.rightHandSide().accept(*this);
	setLocation(_assignment);
	Token assignmentOperator = _assignment.assignmentOperator();
	Token binaryOperator =
		assignmentOperator == Token::Assign ?
		assignmentOperator :
		TokenTraits::AssignmentToBinaryOp(assignmentOperator);
	Type const* rightIntermediateType =
		TokenTraits::isShiftOp(binaryOperator) ?
		type(_assignment.rightHandSide()).mobileType() :
		type(_assignment.rightHandSide()).closestTemporaryType(
			&type(_assignment.leftHandSide())
		);
	solAssert(rightIntermediateType, "");
	IRVariable value = convert(_assignment.rightHandSide(), *rightIntermediateType);
	_assignment.leftHandSide().accept(*this);
	solAssert(!!m_currentLValue, "LValue not retrieved.");
	setLocation(_assignment);
	if (assignmentOperator != Token::Assign)
	{
		solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types.");
		solAssert(rightIntermediateType->isValueType(), "Compound operators only available for value types.");
		IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
		solAssert(binaryOperator != Token::Exp, "");
		if (TokenTraits::isShiftOp(binaryOperator))
		{
			solAssert(type(_assignment) == leftIntermediate.type(), "");
			solAssert(type(_assignment) == type(_assignment.leftHandSide()), "");
			define(_assignment) << shiftOperation(binaryOperator, leftIntermediate, value) << "\n";
			writeToLValue(*m_currentLValue, IRVariable(_assignment));
			m_currentLValue.reset();
			return false;
		}
		else
		{
			solAssert(type(_assignment.leftHandSide()) == *rightIntermediateType, "");
			m_code << value.name() << " := " << binaryOperation(
				binaryOperator,
				*rightIntermediateType,
				leftIntermediate.name(),
				value.name()
			);
		}
	}
	writeToLValue(*m_currentLValue, value);
	if (dynamic_cast(&m_currentLValue->type))
		define(_assignment, readFromLValue(*m_currentLValue));
	else if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
		define(_assignment, value);
	m_currentLValue.reset();
	return false;
}
bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
{
	setLocation(_tuple);
	if (_tuple.isInlineArray())
	{
		auto const& arrayType = dynamic_cast(*_tuple.annotation().type);
		solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
		define(_tuple) <<
			m_utils.allocateMemoryArrayFunction(arrayType) <<
			"(" <<
			_tuple.components().size() <<
			")\n";
		string mpos = IRVariable(_tuple).part("mpos").name();
		Type const& baseType = *arrayType.baseType();
		for (size_t i = 0; i < _tuple.components().size(); i++)
		{
			Expression const& component = *_tuple.components()[i];
			component.accept(*this);
			setLocation(_tuple);
			IRVariable converted = convert(component, baseType);
			m_code <<
				m_utils.writeToMemoryFunction(baseType) <<
				"(" <<
				("add(" + mpos + ", " + to_string(i * arrayType.memoryStride()) + ")") <<
				", " <<
				converted.commaSeparatedList() <<
				")\n";
		}
	}
	else
	{
		bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
		if (willBeWrittenTo)
			solAssert(!m_currentLValue, "");
		if (_tuple.components().size() == 1)
		{
			solAssert(_tuple.components().front(), "");
			_tuple.components().front()->accept(*this);
			setLocation(_tuple);
			if (willBeWrittenTo)
				solAssert(!!m_currentLValue, "");
			else
				define(_tuple, *_tuple.components().front());
		}
		else
		{
			vector> lvalues;
			for (size_t i = 0; i < _tuple.components().size(); ++i)
				if (auto const& component = _tuple.components()[i])
				{
					component->accept(*this);
					setLocation(_tuple);
					if (willBeWrittenTo)
					{
						solAssert(!!m_currentLValue, "");
						lvalues.emplace_back(std::move(m_currentLValue));
						m_currentLValue.reset();
					}
					else
						define(IRVariable(_tuple).tupleComponent(i), *component);
				}
				else if (willBeWrittenTo)
					lvalues.emplace_back();
			if (_tuple.annotation().willBeWrittenTo)
				m_currentLValue.emplace(IRLValue{
					*_tuple.annotation().type,
					IRLValue::Tuple{std::move(lvalues)}
				});
		}
	}
	return false;
}
bool IRGeneratorForStatements::visit(Block const& _block)
{
	if (_block.unchecked())
	{
		solAssert(m_context.arithmetic() == Arithmetic::Checked, "");
		m_context.setArithmetic(Arithmetic::Wrapping);
	}
	return true;
}
void IRGeneratorForStatements::endVisit(Block const& _block)
{
	if (_block.unchecked())
	{
		solAssert(m_context.arithmetic() == Arithmetic::Wrapping, "");
		m_context.setArithmetic(Arithmetic::Checked);
	}
}
bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
{
	_ifStatement.condition().accept(*this);
	setLocation(_ifStatement);
	string condition = expressionAsType(_ifStatement.condition(), *TypeProvider::boolean());
	if (_ifStatement.falseStatement())
	{
		m_code << "switch " << condition << "\n" "case 0 {\n";
		_ifStatement.falseStatement()->accept(*this);
		setLocation(_ifStatement);
		m_code << "}\n" "default {\n";
	}
	else
		m_code << "if " << condition << " {\n";
	_ifStatement.trueStatement().accept(*this);
	setLocation(_ifStatement);
	m_code << "}\n";
	return false;
}
void IRGeneratorForStatements::endVisit(PlaceholderStatement const&)
{
	solAssert(m_placeholderCallback, "");
	m_code << m_placeholderCallback();
}
bool IRGeneratorForStatements::visit(ForStatement const& _forStatement)
{
	setLocation(_forStatement);
	generateLoop(
		_forStatement.body(),
		_forStatement.condition(),
		_forStatement.initializationExpression(),
		_forStatement.loopExpression()
	);
	return false;
}
bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement)
{
	setLocation(_whileStatement);
	generateLoop(
		_whileStatement.body(),
		&_whileStatement.condition(),
		nullptr,
		nullptr,
		_whileStatement.isDoWhile()
	);
	return false;
}
bool IRGeneratorForStatements::visit(Continue const& _continue)
{
	setLocation(_continue);
	m_code << "continue\n";
	return false;
}
bool IRGeneratorForStatements::visit(Break const& _break)
{
	setLocation(_break);
	m_code << "break\n";
	return false;
}
void IRGeneratorForStatements::endVisit(Return const& _return)
{
	setLocation(_return);
	if (Expression const* value = _return.expression())
	{
		solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer.");
		vector> const& returnParameters =
			_return.annotation().functionReturnParameters->parameters();
		if (returnParameters.size() > 1)
			for (size_t i = 0; i < returnParameters.size(); ++i)
				assign(m_context.localVariable(*returnParameters[i]), IRVariable(*value).tupleComponent(i));
		else if (returnParameters.size() == 1)
			assign(m_context.localVariable(*returnParameters.front()), *value);
	}
	m_code << "leave\n";
}
void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
{
	setLocation(_unaryOperation);
	Type const& resultType = type(_unaryOperation);
	Token const op = _unaryOperation.getOperator();
	if (op == Token::Delete)
	{
		solAssert(!!m_currentLValue, "LValue not retrieved.");
		std::visit(
			util::GenericVisitor{
				[&](IRLValue::Storage const& _storage) {
					m_code <<
						m_utils.storageSetToZeroFunction(m_currentLValue->type) <<
						"(" <<
						_storage.slot <<
						", " <<
						_storage.offsetString() <<
						")\n";
					m_currentLValue.reset();
				},
				[&](auto const&) {
					IRVariable zeroValue(m_context.newYulVariable(), m_currentLValue->type);
					define(zeroValue) << m_utils.zeroValueFunction(m_currentLValue->type) << "()\n";
					writeToLValue(*m_currentLValue, zeroValue);
					m_currentLValue.reset();
				}
			},
			m_currentLValue->kind
		);
	}
	else if (resultType.category() == Type::Category::RationalNumber)
		define(_unaryOperation) << formatNumber(resultType.literalValue(nullptr)) << "\n";
	else if (resultType.category() == Type::Category::Integer)
	{
		solAssert(resultType == type(_unaryOperation.subExpression()), "Result type doesn't match!");
		if (op == Token::Inc || op == Token::Dec)
		{
			solAssert(!!m_currentLValue, "LValue not retrieved.");
			IRVariable modifiedValue(m_context.newYulVariable(), resultType);
			IRVariable originalValue = readFromLValue(*m_currentLValue);
			bool checked = m_context.arithmetic() == Arithmetic::Checked;
			define(modifiedValue) <<
				(op == Token::Inc ?
					(checked ? m_utils.incrementCheckedFunction(resultType) : m_utils.incrementWrappingFunction(resultType)) :
					(checked ? m_utils.decrementCheckedFunction(resultType) : m_utils.decrementWrappingFunction(resultType))
				) <<
				"(" <<
				originalValue.name() <<
				")\n";
			writeToLValue(*m_currentLValue, modifiedValue);
			m_currentLValue.reset();
			define(_unaryOperation, _unaryOperation.isPrefixOperation() ? modifiedValue : originalValue);
		}
		else if (op == Token::BitNot)
			appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression());
		else if (op == Token::Add)
			// According to SyntaxChecker...
			solAssert(false, "Use of unary + is disallowed.");
		else if (op == Token::Sub)
		{
			IntegerType const& intType = *dynamic_cast(&resultType);
			define(_unaryOperation) << (
				m_context.arithmetic() == Arithmetic::Checked ?
				m_utils.negateNumberCheckedFunction(intType) :
				m_utils.negateNumberWrappingFunction(intType)
			) << "(" << IRVariable(_unaryOperation.subExpression()).name() << ")\n";
		}
		else
			solUnimplementedAssert(false, "Unary operator not yet implemented");
	}
	else if (resultType.category() == Type::Category::FixedBytes)
	{
		solAssert(op == Token::BitNot, "Only bitwise negation is allowed for FixedBytes");
		solAssert(resultType == type(_unaryOperation.subExpression()), "Result type doesn't match!");
		appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression());
	}
	else if (resultType.category() == Type::Category::Bool)
	{
		solAssert(
			op != Token::BitNot,
			"Bitwise Negation can't be done on bool!"
		);
		appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression());
	}
	else
		solUnimplementedAssert(false, "Unary operator not yet implemented");
}
bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
{
	setLocation(_binOp);
	solAssert(!!_binOp.annotation().commonType, "");
	TypePointer commonType = _binOp.annotation().commonType;
	langutil::Token op = _binOp.getOperator();
	if (op == Token::And || op == Token::Or)
	{
		// This can short-circuit!
		appendAndOrOperatorCode(_binOp);
		return false;
	}
	if (commonType->category() == Type::Category::RationalNumber)
	{
		define(_binOp) << toCompactHexWithPrefix(commonType->literalValue(nullptr)) << "\n";
		return false; // skip sub-expressions
	}
	_binOp.leftExpression().accept(*this);
	_binOp.rightExpression().accept(*this);
	setLocation(_binOp);
	if (TokenTraits::isCompareOp(op))
	{
		if (auto type = dynamic_cast(commonType))
		{
			solAssert(op == Token::Equal || op == Token::NotEqual, "Invalid function pointer comparison!");
			solAssert(type->kind() != FunctionType::Kind::External, "External function comparison not allowed!");
		}
		solAssert(commonType->isValueType(), "");
		bool isSigned = false;
		if (auto type = dynamic_cast(commonType))
			isSigned = type->isSigned();
		string args =
			expressionAsType(_binOp.leftExpression(), *commonType, true) +
			", " +
			expressionAsType(_binOp.rightExpression(), *commonType, true);
		string expr;
		if (op == Token::Equal)
			expr = "eq(" + move(args) + ")";
		else if (op == Token::NotEqual)
			expr = "iszero(eq(" + move(args) + "))";
		else if (op == Token::GreaterThanOrEqual)
			expr = "iszero(" + string(isSigned ? "slt(" : "lt(") + move(args) + "))";
		else if (op == Token::LessThanOrEqual)
			expr = "iszero(" + string(isSigned ? "sgt(" : "gt(") + move(args) + "))";
		else if (op == Token::GreaterThan)
			expr = (isSigned ? "sgt(" : "gt(") + move(args) + ")";
		else if (op == Token::LessThan)
			expr = (isSigned ? "slt(" : "lt(") + move(args) + ")";
		else
			solAssert(false, "Unknown comparison operator.");
		define(_binOp) << expr << "\n";
	}
	else if (op == Token::Exp)
	{
		IRVariable left = convert(_binOp.leftExpression(), *commonType);
		IRVariable right = convert(_binOp.rightExpression(), *type(_binOp.rightExpression()).mobileType());
		if (m_context.arithmetic() == Arithmetic::Wrapping)
			define(_binOp) << m_utils.wrappingIntExpFunction(
				dynamic_cast(left.type()),
				dynamic_cast(right.type())
			) << "(" << left.name() << ", " << right.name() << ")\n";
		else if (auto rationalNumberType = dynamic_cast(_binOp.leftExpression().annotation().type))
		{
			solAssert(rationalNumberType->integerType(), "Invalid literal as the base for exponentiation.");
			solAssert(dynamic_cast(commonType), "");
			define(_binOp) << m_utils.overflowCheckedIntLiteralExpFunction(
				*rationalNumberType,
				dynamic_cast(right.type()),
				dynamic_cast(*commonType)
			) << "(" << right.name() << ")\n";
		}
		else
			define(_binOp) << m_utils.overflowCheckedIntExpFunction(
				dynamic_cast(left.type()),
				dynamic_cast(right.type())
			) << "(" << left.name() << ", " << right.name() << ")\n";
	}
	else if (TokenTraits::isShiftOp(op))
	{
		IRVariable left = convert(_binOp.leftExpression(), *commonType);
		IRVariable right = convert(_binOp.rightExpression(), *type(_binOp.rightExpression()).mobileType());
		define(_binOp) << shiftOperation(_binOp.getOperator(), left, right) << "\n";
	}
	else
	{
		string left = expressionAsType(_binOp.leftExpression(), *commonType);
		string right = expressionAsType(_binOp.rightExpression(), *commonType);
		define(_binOp) << binaryOperation(_binOp.getOperator(), *commonType, left, right) << "\n";
	}
	return false;
}
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{
	setLocation(_functionCall);
	auto functionCallKind = *_functionCall.annotation().kind;
	if (functionCallKind == FunctionCallKind::TypeConversion)
	{
		solAssert(
			_functionCall.expression().annotation().type->category() == Type::Category::TypeType,
			"Expected category to be TypeType"
		);
		solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion");
		define(_functionCall, *_functionCall.arguments().front());
		return;
	}
	FunctionTypePointer functionType = nullptr;
	if (functionCallKind == FunctionCallKind::StructConstructorCall)
	{
		auto const& type = dynamic_cast(*_functionCall.expression().annotation().type);
		auto const& structType = dynamic_cast(*type.actualType());
		functionType = structType.constructorType();
	}
	else
		functionType = dynamic_cast(_functionCall.expression().annotation().type);
	TypePointers parameterTypes = functionType->parameterTypes();
	vector> const& arguments = _functionCall.sortedArguments();
	if (functionCallKind == FunctionCallKind::StructConstructorCall)
	{
		TypeType const& type = dynamic_cast(*_functionCall.expression().annotation().type);
		auto const& structType = dynamic_cast(*type.actualType());
		define(_functionCall) << m_utils.allocateMemoryStructFunction(structType) << "()\n";
		MemberList::MemberMap members = structType.nativeMembers(nullptr);
		solAssert(members.size() == arguments.size(), "Struct parameter mismatch.");
		for (size_t i = 0; i < arguments.size(); i++)
		{
			IRVariable converted = convert(*arguments[i], *parameterTypes[i]);
			m_code <<
				m_utils.writeToMemoryFunction(*functionType->parameterTypes()[i]) <<
				"(add(" <<
				IRVariable(_functionCall).part("mpos").name() <<
				", " <<
				structType.memoryOffsetOfMember(members[i].name) <<
				"), " <<
				converted.commaSeparatedList() <<
				")\n";
		}
		return;
	}
	auto memberAccess = dynamic_cast(&_functionCall.expression());
	if (memberAccess)
	{
		if (auto expressionType = dynamic_cast(memberAccess->expression().annotation().type))
		{
			solAssert(!functionType->bound(), "");
			if (auto contractType = dynamic_cast(expressionType->actualType()))
				if (contractType->contractDefinition().isLibrary())
					solAssert(functionType->kind() == FunctionType::Kind::Internal || functionType->kind() == FunctionType::Kind::DelegateCall, "");
		}
	}
	switch (functionType->kind())
	{
	case FunctionType::Kind::Declaration:
		solAssert(false, "Attempted to generate code for calling a function definition.");
		break;
	case FunctionType::Kind::Internal:
	{
		auto identifier = dynamic_cast(&_functionCall.expression());
		FunctionDefinition const* functionDef = IRHelpers::referencedFunctionDeclaration(_functionCall.expression());
		if (functionDef)
		{
			solAssert(memberAccess || identifier, "");
			solAssert(functionType->declaration() == *functionDef, "");
			if (identifier)
			{
				solAssert(*identifier->annotation().requiredLookup == VirtualLookup::Virtual, "");
				functionDef = &functionDef->resolveVirtual(m_context.mostDerivedContract());
			}
			else if (auto typeType = dynamic_cast(memberAccess->expression().annotation().type))
				if (
					auto contractType = dynamic_cast(typeType->actualType());
					contractType->isSuper()
				)
				{
					ContractDefinition const* super = contractType->contractDefinition().superContract(m_context.mostDerivedContract());
					solAssert(super, "Super contract not available.");
					solAssert(*memberAccess->annotation().requiredLookup == VirtualLookup::Super, "");
					functionDef = &functionDef->resolveVirtual(m_context.mostDerivedContract(), super);
				}
			solAssert(functionDef && functionDef->isImplemented(), "");
			solAssert(
				functionDef->parameters().size() == arguments.size() + (functionType->bound() ? 1 : 0),
				""
			);
		}
		solAssert(!functionType->takesArbitraryParameters(), "");
		vector args;
		if (functionType->bound())
			args += IRVariable(_functionCall.expression()).part("self").stackSlots();
		for (size_t i = 0; i < arguments.size(); ++i)
			args += convert(*arguments[i], *parameterTypes[i]).stackSlots();
		if (functionDef)
			define(_functionCall) <<
				m_context.enqueueFunctionForCodeGeneration(*functionDef) <<
				"(" <<
				joinHumanReadable(args) <<
				")\n";
		else
		{
			YulArity arity = YulArity::fromType(*functionType);
			m_context.internalFunctionCalledThroughDispatch(arity);
			define(_functionCall) <<
				IRNames::internalDispatch(arity) <<
				"(" <<
				IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
				joinHumanReadablePrefixed(args) <<
				")\n";
		}
		break;
	}
	case FunctionType::Kind::External:
	case FunctionType::Kind::DelegateCall:
		appendExternalFunctionCall(_functionCall, arguments);
		break;
	case FunctionType::Kind::BareCall:
	case FunctionType::Kind::BareDelegateCall:
	case FunctionType::Kind::BareStaticCall:
		appendBareCall(_functionCall, arguments);
		break;
	case FunctionType::Kind::BareCallCode:
		solAssert(false, "Callcode has been removed.");
	case FunctionType::Kind::Event:
	{
		auto const& event = dynamic_cast(functionType->declaration());
		TypePointers paramTypes = functionType->parameterTypes();
		ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
		vector indexedArgs;
		vector nonIndexedArgs;
		TypePointers nonIndexedArgTypes;
		TypePointers nonIndexedParamTypes;
		if (!event.isAnonymous())
			define(indexedArgs.emplace_back(m_context.newYulVariable(), *TypeProvider::uint256())) <<
				formatNumber(u256(h256::Arith(keccak256(functionType->externalSignature())))) << "\n";
		for (size_t i = 0; i < event.parameters().size(); ++i)
		{
			Expression const& arg = *arguments[i];
			if (event.parameters()[i]->isIndexed())
			{
				string value;
				if (auto const& referenceType = dynamic_cast(paramTypes[i]))
					define(indexedArgs.emplace_back(m_context.newYulVariable(), *TypeProvider::uint256())) <<
						m_utils.packedHashFunction({arg.annotation().type}, {referenceType}) <<
						"(" <<
						IRVariable(arg).commaSeparatedList() <<
						")\n";
				else if (auto functionType = dynamic_cast(paramTypes[i]))
				{
					solAssert(
						IRVariable(arg).type() == *functionType &&
						functionType->kind() == FunctionType::Kind::External &&
						!functionType->bound(),
						""
					);
					define(indexedArgs.emplace_back(m_context.newYulVariable(), *TypeProvider::fixedBytes(32))) <<
						m_utils.combineExternalFunctionIdFunction() <<
						"(" <<
						IRVariable(arg).commaSeparatedList() <<
						")\n";
				}
				else
					indexedArgs.emplace_back(convert(arg, *paramTypes[i]));
			}
			else
			{
				nonIndexedArgs += IRVariable(arg).stackSlots();
				nonIndexedArgTypes.push_back(arg.annotation().type);
				nonIndexedParamTypes.push_back(paramTypes[i]);
			}
		}
		solAssert(indexedArgs.size() <= 4, "Too many indexed arguments.");
		Whiskers templ(R"({
			let  := ()
			let  := ( )
			(, sub(, ) )
		})");
		templ("pos", m_context.newYulVariable());
		templ("end", m_context.newYulVariable());
		templ("allocateUnbounded", m_utils.allocateUnboundedFunction());
		templ("encode", abi.tupleEncoder(nonIndexedArgTypes, nonIndexedParamTypes));
		templ("nonIndexedArgs", joinHumanReadablePrefixed(nonIndexedArgs));
		templ("log", "log" + to_string(indexedArgs.size()));
		templ("indexedArgs", joinHumanReadablePrefixed(indexedArgs | boost::adaptors::transformed([&](auto const& _arg) {
			return _arg.commaSeparatedList();
		})));
		m_code << templ.render();
		break;
	}
	case FunctionType::Kind::Assert:
	case FunctionType::Kind::Require:
	{
		solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert");
		solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert");
		Type const* messageArgumentType =
			arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip ?
			arguments[1]->annotation().type :
			nullptr;
		string requireOrAssertFunction = m_utils.requireOrAssertFunction(
			functionType->kind() == FunctionType::Kind::Assert,
			messageArgumentType
		);
		m_code << move(requireOrAssertFunction) << "(" << IRVariable(*arguments[0]).name();
		if (messageArgumentType && messageArgumentType->sizeOnStack() > 0)
			m_code << ", " << IRVariable(*arguments[1]).commaSeparatedList();
		m_code << ")\n";
		break;
	}
	case FunctionType::Kind::ABIEncode:
	case FunctionType::Kind::ABIEncodePacked:
	case FunctionType::Kind::ABIEncodeWithSelector:
	case FunctionType::Kind::ABIEncodeWithSignature:
	{
		bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked;
		solAssert(functionType->padArguments() != isPacked, "");
		bool const hasSelectorOrSignature =
			functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
			functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature;
		TypePointers argumentTypes;
		TypePointers targetTypes;
		vector argumentVars;
		for (size_t i = 0; i < arguments.size(); ++i)
		{
			// ignore selector
			if (hasSelectorOrSignature && i == 0)
				continue;
			argumentTypes.emplace_back(&type(*arguments[i]));
			targetTypes.emplace_back(type(*arguments[i]).fullEncodingType(false, true, isPacked));
			argumentVars += IRVariable(*arguments[i]).stackSlots();
		}
		string selector;
		if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature)
		{
			// hash the signature
			Type const& selectorType = type(*arguments.front());
			if (auto const* stringType = dynamic_cast(&selectorType))
				selector = formatNumber(util::selectorFromSignature(stringType->value()));
			else
			{
				// Used to reset the free memory pointer later.
				// TODO This is an abuse of the `allocateUnbounded` function.
				// We might want to introduce a new set of memory handling functions here
				// a la "setMemoryCheckPoint" and "freeUntilCheckPoint".
				string freeMemoryPre = m_context.newYulVariable();
				m_code << "let " << freeMemoryPre << " := " << m_utils.allocateUnboundedFunction() << "()\n";
				IRVariable array = convert(*arguments[0], *TypeProvider::bytesMemory());
				IRVariable hashVariable(m_context.newYulVariable(), *TypeProvider::fixedBytes(32));
				define(hashVariable) <<
					"keccak256(" <<
					m_utils.arrayDataAreaFunction(*TypeProvider::bytesMemory()) <<
					"(" <<
					array.commaSeparatedList() <<
					"), " <<
					m_utils.arrayLengthFunction(*TypeProvider::bytesMemory()) <<
					"(" <<
					array.commaSeparatedList() <<
					"))\n";
				IRVariable selectorVariable(m_context.newYulVariable(), *TypeProvider::fixedBytes(4));
				define(selectorVariable, hashVariable);
				selector = selectorVariable.name();
				m_code << m_utils.finalizeAllocationFunction() << "(" << freeMemoryPre << ", 0)\n";
			}
		}
		else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector)
			selector = convert(*arguments.front(), *TypeProvider::fixedBytes(4)).name();
		Whiskers templ(R"(
			let  := ()
			let  := add(, 0x20)
			+selector>
				mstore(, )
				 := add(, 4)
			+selector>
			let  := ()
			mstore(, sub(, add(, 0x20)))
			(, sub(, ))
		)");
		templ("data", IRVariable(_functionCall).part("mpos").name());
		templ("allocateUnbounded", m_utils.allocateUnboundedFunction());
		templ("memPtr", m_context.newYulVariable());
		templ("mend", m_context.newYulVariable());
		templ("selector", selector);
		templ("encode",
			isPacked ?
			m_context.abiFunctions().tupleEncoderPacked(argumentTypes, targetTypes) :
			m_context.abiFunctions().tupleEncoder(argumentTypes, targetTypes, false)
		);
		templ("arguments", joinHumanReadablePrefixed(argumentVars));
		templ("finalizeAllocation", m_utils.finalizeAllocationFunction());
		m_code << templ.render();
		break;
	}
	case FunctionType::Kind::ABIDecode:
	{
		Whiskers templ(R"(
			+retVars>let  := +retVars> (, add(, ))
		)");
		TypePointer firstArgType = arguments.front()->annotation().type;
		TypePointers targetTypes;
		if (TupleType const* targetTupleType = dynamic_cast(_functionCall.annotation().type))
			targetTypes = targetTupleType->components();
		else
			targetTypes = TypePointers{_functionCall.annotation().type};
		if (
			auto referenceType = dynamic_cast(firstArgType);
			referenceType && referenceType->dataStoredIn(DataLocation::CallData)
			)
		{
			solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()), "");
			IRVariable var = convert(*arguments[0], *TypeProvider::bytesCalldata());
			templ("abiDecode", m_context.abiFunctions().tupleDecoder(targetTypes, false));
			templ("offset", var.part("offset").name());
			templ("length", var.part("length").name());
		}
		else
		{
			IRVariable var = convert(*arguments[0], *TypeProvider::bytesMemory());
			templ("abiDecode", m_context.abiFunctions().tupleDecoder(targetTypes, true));
			templ("offset", "add(" + var.part("mpos").name() + ", 32)");
			templ("length",
				m_utils.arrayLengthFunction(*TypeProvider::bytesMemory()) + "(" + var.part("mpos").name() + ")"
			);
		}
		templ("retVars", IRVariable(_functionCall).commaSeparatedList());
		m_code << templ.render();
		break;
	}
	case FunctionType::Kind::Revert:
	{
		solAssert(arguments.size() == parameterTypes.size(), "");
		if (arguments.empty())
			m_code << "revert(0, 0)\n";
		else
		{
			solAssert(arguments.size() == 1, "");
			if (m_context.revertStrings() == RevertStrings::Strip)
				m_code << "revert(0, 0)\n";
			else
			{
				solAssert(type(*arguments.front()).isImplicitlyConvertibleTo(*TypeProvider::stringMemory()),"");
				Whiskers templ(R"({
					let  := ()
					mstore(, )
					let  := (add(, 4) )
					revert(, sub(, ))
				})");
				templ("pos", m_context.newYulVariable());
				templ("end", m_context.newYulVariable());
				templ("hash", util::selectorFromSignature("Error(string)").str());
				templ("allocateUnbounded", m_utils.allocateUnboundedFunction());
				templ(
					"argumentVars",
					joinHumanReadablePrefixed(IRVariable{*arguments.front()}.stackSlots())
				);
				templ("encode", m_context.abiFunctions().tupleEncoder(
					{&type(*arguments.front())},
					{TypeProvider::stringMemory()}
				));
				m_code << templ.render();
			}
		}
		break;
	}
	// Array creation using new
	case FunctionType::Kind::ObjectCreation:
	{
		ArrayType const& arrayType = dynamic_cast(*_functionCall.annotation().type);
		solAssert(arguments.size() == 1, "");
		IRVariable value = convert(*arguments[0], *TypeProvider::uint256());
		define(_functionCall) <<
			m_utils.allocateAndInitializeMemoryArrayFunction(arrayType) <<
			"(" <<
			value.commaSeparatedList() <<
			")\n";
		break;
	}
	case FunctionType::Kind::KECCAK256:
	{
		solAssert(arguments.size() == 1, "");
		ArrayType const* arrayType = TypeProvider::bytesMemory();
		if (auto const* stringLiteral = dynamic_cast(arguments.front()->annotation().type))
		{
			// Optimization: Compute keccak256 on string literals at compile-time.
			define(_functionCall) <<
				("0x" + keccak256(stringLiteral->value()).hex()) <<
				"\n";
		}
		else
		{
			auto array = convert(*arguments[0], *arrayType);
			define(_functionCall) <<
				"keccak256(" <<
				m_utils.arrayDataAreaFunction(*arrayType) <<
				"(" <<
				array.commaSeparatedList() <<
				"), " <<
				m_utils.arrayLengthFunction(*arrayType) <<
				"(" <<
				array.commaSeparatedList() <<
				"))\n";
		}
		break;
	}
	case FunctionType::Kind::ArrayPop:
	{
		auto const& memberAccessExpression = dynamic_cast(_functionCall.expression()).expression();
		ArrayType const& arrayType = dynamic_cast(*memberAccessExpression.annotation().type);
		define(_functionCall) <<
			m_utils.storageArrayPopFunction(arrayType) <<
			"(" <<
			IRVariable(_functionCall.expression()).commaSeparatedList() <<
			")\n";
		break;
	}
	case FunctionType::Kind::ByteArrayPush:
	case FunctionType::Kind::ArrayPush:
	{
		auto const& memberAccessExpression = dynamic_cast(_functionCall.expression()).expression();
		ArrayType const& arrayType = dynamic_cast(*memberAccessExpression.annotation().type);
		if (arguments.empty())
		{
			auto slotName = m_context.newYulVariable();
			auto offsetName = m_context.newYulVariable();
			m_code << "let " << slotName << ", " << offsetName << " := " <<
				m_utils.storageArrayPushZeroFunction(arrayType) <<
				"(" << IRVariable(_functionCall.expression()).commaSeparatedList() << ")\n";
			setLValue(_functionCall, IRLValue{
				*arrayType.baseType(),
				IRLValue::Storage{
					slotName,
					offsetName,
				}
			});
		}
		else
		{
			IRVariable argument =
				arrayType.baseType()->isValueType() ?
				convert(*arguments.front(), *arrayType.baseType()) :
				*arguments.front();
			m_code <<
				m_utils.storageArrayPushFunction(arrayType, &argument.type()) <<
				"(" <<
				IRVariable(_functionCall.expression()).commaSeparatedList() <<
				(argument.stackSlots().empty() ? "" : (", " + argument.commaSeparatedList()))  <<
				")\n";
		}
		break;
	}
	case FunctionType::Kind::MetaType:
	{
		break;
	}
	case FunctionType::Kind::AddMod:
	case FunctionType::Kind::MulMod:
	{
		static map functions = {
			{FunctionType::Kind::AddMod, "addmod"},
			{FunctionType::Kind::MulMod, "mulmod"},
		};
		solAssert(functions.find(functionType->kind()) != functions.end(), "");
		solAssert(arguments.size() == 3 && parameterTypes.size() == 3, "");
		IRVariable modulus(m_context.newYulVariable(), *(parameterTypes[2]));
		define(modulus, *arguments[2]);
		Whiskers templ("if iszero() { () }\n");
		templ("modulus", modulus.name());
		templ("panic", m_utils.panicFunction(PanicCode::DivisionByZero));
		m_code << templ.render();
		string args;
		for (size_t i = 0; i < 2; ++i)
			args += expressionAsType(*arguments[i], *(parameterTypes[i])) + ", ";
		args += modulus.name();
		define(_functionCall) << functions[functionType->kind()] << "(" << args << ")\n";
		break;
	}
	case FunctionType::Kind::GasLeft:
	case FunctionType::Kind::Selfdestruct:
	case FunctionType::Kind::BlockHash:
	{
		static map functions = {
			{FunctionType::Kind::GasLeft, "gas"},
			{FunctionType::Kind::Selfdestruct, "selfdestruct"},
			{FunctionType::Kind::BlockHash, "blockhash"},
		};
		solAssert(functions.find(functionType->kind()) != functions.end(), "");
		string args;
		for (size_t i = 0; i < arguments.size(); ++i)
			args += (args.empty() ? "" : ", ") + expressionAsType(*arguments[i], *(parameterTypes[i]));
		define(_functionCall) << functions[functionType->kind()] << "(" << args << ")\n";
		break;
	}
	case FunctionType::Kind::Creation:
	{
		solAssert(!functionType->gasSet(), "Gas limit set for contract creation.");
		solAssert(
			functionType->returnParameterTypes().size() == 1,
			"Constructor should return only one type"
		);
		TypePointers argumentTypes;
		vector constructorParams;
		for (ASTPointer const& arg: arguments)
		{
			argumentTypes.push_back(arg->annotation().type);
			constructorParams += IRVariable{*arg}.stackSlots();
		}
		ContractDefinition const* contract =
			&dynamic_cast(*functionType->returnParameterTypes().front()).contractDefinition();
		m_context.subObjectsCreated().insert(contract);
		Whiskers t(R"(
			let  := ()
			let  := add(, datasize("