/*
	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 .
*/
/**
 * @file PeepholeOptimiser.cpp
 * Performs local optimising code changes to assembly.
 */
#include "PeepholeOptimiser.h"
#include 
#include 
using namespace std;
using namespace dev::eth;
using namespace dev;
// TODO: Extend this to use the tools from ExpressionClasses.cpp
struct Identity
{
	static size_t windowSize() { return 1; }
	static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out)
	{
		*_out = *_in;
		return true;
	}
};
struct PushPop
{
	static size_t windowSize() { return 2; }
	static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator)
	{
		auto t = _in[0].type();
		if (_in[1] == Instruction::POP && (
			SemanticInformation::isDupInstruction(_in[0]) ||
			t == Push || t == PushString || t == PushTag || t == PushSub ||
			t == PushSubSize || t == PushProgramSize || t == PushData || t == PushLibraryAddress
		))
			return true;
		else
			return false;
	}
};
struct AddPop
{
	static size_t windowSize() { return 2; }
	static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out)
	{
		if (_in[1] == Instruction::POP &&
			_in[0].type() == Operation
		)
		{
			Instruction i0 = _in[0].instruction();
			if (instructionInfo(i0).ret == 1 &&
				!SemanticInformation::invalidatesMemory(i0) &&
				!SemanticInformation::invalidatesStorage(i0) &&
				!SemanticInformation::altersControlFlow(i0) &&
				!instructionInfo(i0).sideEffects
			)
			{
				for (int j = 0; j < instructionInfo(i0).args; j++)
					*_out = Instruction::POP;
				return true;
			}
		}
		return false;
	}
};
struct DoubleSwap
{
	static size_t windowSize() { return 2; }
	static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator)
	{
		if (_in[0] == _in[1] && SemanticInformation::isSwapInstruction(_in[0]))
			return true;
		else
			return false;
	}
};
struct JumpToNext
{
	static size_t windowSize() { return 3; }
	static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out)
	{
		if (
			_in[0].type() == PushTag &&
			(_in[1] == Instruction::JUMP || _in[1] == Instruction::JUMPI) &&
			_in[2].type() == Tag &&
			_in[0].data() == _in[2].data()
		)
		{
			if (_in[1] == Instruction::JUMPI)
				*_out = AssemblyItem(Instruction::POP, _in[1].location());
			*_out = _in[2];
			return true;
		}
		else
			return false;
	}
};
struct TagConjunctions
{
	static size_t windowSize() { return 3; }
	static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out)
	{
		if (
			_in[0].type() == PushTag &&
			_in[2] == Instruction::AND &&
			_in[1].type() == Push &&
			(_in[1].data() & u256(0xFFFFFFFF)) == u256(0xFFFFFFFF)
		)
		{
			*_out = _in[0];
			return true;
		}
		else
			return false;
	}
};
struct OptimiserState
{
	AssemblyItems const& items;
	size_t i;
	std::back_insert_iterator out;
};
void applyMethods(OptimiserState&)
{
	assertThrow(false, OptimizerException, "Peephole optimizer failed to apply identity.");
}
template 
void applyMethods(OptimiserState& _state, Method, OtherMethods... _other)
{
	if (_state.i + Method::windowSize() <= _state.items.size() && Method::apply(_state.items.begin() + _state.i, _state.out))
		_state.i += Method::windowSize();
	else
		applyMethods(_state, _other...);
}
bool PeepholeOptimiser::optimise()
{
	OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
	while (state.i < m_items.size())
		applyMethods(state, PushPop(), AddPop(), DoubleSwap(), JumpToNext(), TagConjunctions(), Identity());
	if (m_optimisedItems.size() < m_items.size())
	{
		m_items = std::move(m_optimisedItems);
		return true;
	}
	else
		return false;
}