// px_ir.cpp
// `````````

#define PX_INTERNAL_SOURCE
#include "px_ir.h"

//
// Opcodes
//

void PXProgram::op(PXInstruction pxi) {
	
	if (pxi.it == PXIT_NOP) {
		commentBuffer.append( "\n; " );
		commentBuffer.append( pxi.arg1 );
		return;

	} else {
		pxi.comment.append( commentBuffer );
		commentBuffer.clear();

	}

	if (this->should_optimise) {


		size_t ops_size = ops.size();

		// "add reg, 0"
		if (pxi.it == PXIT_ADD_REG_IMM && pxi.i2 == 0) {
			return; // pointless
		}

		// "jmp target; target: "
		if (pxi.it == PXIT_LABEL &&
			ops_size &&
			ops[ops_size - 1].it == PXIT_JMP &&
			ops[ops_size - 1].arg1 == pxi.arg1
		) {
			// jmp target; target: 
			// Remove previous jmp and continue to add label
			ops.pop_back();
		}

		// "jmp {anywhere}; {not a label}" - unreachable code
		if (pxi.it != PXIT_LABEL &&
			ops_size &&
			ops[ops_size - 1].it == PXIT_JMP
		) {
			return; // pointless
		}

		// "mov val, val"
		if (pxi.it == PXIT_MOV &&
			pxi.arg1 == pxi.arg2
		) {
			return; // pointless
		}

		// "mov a, b; mov b, a"
		if (pxi.it == PXIT_MOV &&
			ops_size &&
			ops[ops_size - 1].it == PXIT_MOV &&
			pxi.arg1 == ops[ops_size - 1].arg2 &&
			pxi.arg2 == ops[ops_size - 1].arg1
		) {
			return; // pointless
		}

		// "push src; pop dest" ==> "mov dest, src"
		if (pxi.it == PXIT_POP &&
			ops_size &&
			ops[ops_size - 1].it == PXIT_PUSH
		) {

			std::string last_arg = ops[ops_size - 1].arg1;
			ops.pop_back();

			this->op(PXIT_MOV, pxi.arg1, last_arg);
			return;
		}

		// "push src; add esp, 4 ;pop null" ==> ""
		if (pxi.it == PXIT_POP_NULL &&
			ops_size &&
			ops[ops_size - 1].it == PXIT_PUSH
		) {
			ops.pop_back();
			return;
		}

		// "mov tmp, src; mov dest, tmp; mov tmp, src2" ==> "mov dest, src; mov tmp, src2"
		if (ops_size >= 2 &&
			pxi.it == PXIT_MOV &&
			ops[ops_size - 1].it == PXIT_MOV &&
			ops[ops_size - 2].it == PXIT_MOV &&
			// check for use of identical temporary
			pxi.arg1 == ops[ops_size - 2].arg1 &&
			pxi.arg1 == ops[ops_size - 1].arg2 &&
			// enforce that the temporary is a register
			pxi.arg1.length() == 3 &&
			// enforce that at least one of src/dest must be a register (this could
			//  be expanded)
			(ops[ops_size - 2].arg2.length() == 3 || ops[ops_size - 1].arg1.length() == 3)
		) {
			std::string src = ops[ops_size - 2].arg2, dest = ops[ops_size - 1].arg1;
			ops.pop_back();
			ops.pop_back();
			this->op(PXIT_MOV, dest, src);
			// continue with current instruction
		}


		// "cmp eax, ebx; mov eax, 0; set??? al ; cmp eax, 0 ; je {target} ==> cmp eax, ebx ; j?? {target}"
		// Can't easily be fixed as this only applies to the generated x86 asm, not the pexl IR

		// TODO: detect pattern push,push,pop,pop

	}

	// Normal routine
	ops.push_back( pxi );
}

/*
void PXProgram::op(PXProgram& pxp) {
	// Copy opcodes
	
	for(size_t i = 0, e = pxp.ops.size(); i != e; ++i) {
		//op(pxp.ops[i]); // Skip optimising opcode adder since we've already passed through it
		// This could be reinstated if more optimisations are added that would
		//  affect cross-procedure output
		ops.push_back( pxp.ops[i] );
	}

	// Copy string table
	
	for(size_t i = 0, e = pxp.stringTable.size(); i != e; ++i) {

	}
}

int PXProgram::uniqid() {
	return (int)this;
}
*/

//
// Optimiser
//

void PXProgram::optimise() {
	
	// Nothing for now.
	// Reserved for more complicated matching operations

}

//
// String table
//

std::string PXProgram::register_string_literal(std::string& sz) {
	for (size_t i = 0, e = stringTable.size(); i != e; ++i) {
		if (stringTable[i] == sz) {
			return strf("ctstr%08x", i);
		}
	}

	stringTable.push_back( sz );
	return strf("ctstr%08x", stringTable.size() - 1);
}

//
// x86
//

std::string x86nasm_register_tolower(std::string& reg) {
	if (reg == "eax") return "al"; // this is the only case that's used
	if (reg == "ebx") return "bl";
	if (reg == "ecx") return "cl";
	if (reg == "edx") return "dl";
	return "??"; // syntax error
}

std::string x86nasm_stack_address(int stack_address) {
	stack_address = -stack_address;
	return strf("dword [ebp %s%d]", ((stack_address > 0) ? "+" : ""), stack_address*4);
}

std::string PXProgram::render_x86nasm() {
	
	std::string ret;

	// Program prologue

	ret = strf(
		";begin program %08x\n"
		"section .text\n"
		"global pxlmain\n"
		"pxlmain:\n"
			"\tpush ebp\n"
			"\tmov ebp,esp\n"
			"\tcall pxl_rtl_init\n"
		"; Ready\n"
		, this
	);

	// Each opcode

	for (size_t i = 0, e = ops.size(); i != e; ++i) {
		if (ops[i].comment.length()) {
			ret += "; ";
			ret += ops[i].comment;
			ret += "\n";
		}

		switch(ops[i].it) {
			case PXIT_NOP: {
				// only generated by the optimiser (should we swap this for a linked list?)
			} break;
			
			case PXIT_FATAL: {
				// Return immediately
				return "%fatal \"" + ops[i].arg1 + "\"\n";
			} break;

			case PXIT_LABEL: {
				ret += ops[i].arg1 + ":\n";
			} break;

			case PXIT_CALL: {
				ret +=  "\tcall " + ops[i].arg1 + "\n";
			} break;

			case PXIT_LEAVE: {
				ret += "\tleave\n";
			} break;

			case PXIT_RET: {
				ret += "\tret " + strf("%d\n", ops[i].i1);
			} break;

			case PXIT_MOV: {
				ret += "\tmov " + ops[i].arg1 + ", " + ops[i].arg2 + "\n";
			} break;

			case PXIT_PUSH: {
				ret += "\tpush " + ops[i].arg1 + "\n";
			} break;

			case PXIT_POP: {
				ret += "\tpop " + ops[i].arg1 + "\n";
			} break;

			case PXIT_POP_NULL: {
				ret += "\tadd esp, 4 ;pop\n";
			} break;

			case PXIT_JMP: {
				ret += "\tjmp " + ops[i].arg1 + "\n";
			} break;

			case PXIT_CMP_ZERO: {
				ret += "\tcmp " + ops[i].arg1 + ", 0\n";
			} break;

			case PXIT_JE: {
				ret += "\tje "+ops[i].arg1 + "\n";
			} break;

			case PXIT_JNE: {
				ret += "\tjne "+ops[i].arg1 + "\n";
			} break;

			case PXIT_EQUAL: {
				// dest, src
				ret += strf(
					"\tcmp %s, %s\n"
					"\tmov %s, 0\n"
					"\tsete %s\n"
					,
					ops[i].arg1.c_str(), ops[i].arg2.c_str(),
					ops[i].arg1.c_str(),
					x86nasm_register_tolower( ops[i].arg1 ).c_str()
				);
			} break;

			case PXIT_NOTEQUAL: {
				// dest, src
				ret += strf(
					"\tcmp %s, %s\n"
					"\tmov %s, 0\n"
					"\tsetne %s\n"
					,
					ops[i].arg1.c_str(), ops[i].arg2.c_str(),
					ops[i].arg1.c_str(),
					x86nasm_register_tolower( ops[i].arg1 ).c_str()
				);
			} break;

			case PXIT_GREATERTHAN: {
				// dest, src
				ret += strf(
					"\tcmp %s, %s\n"
					"\tmov %s, 0\n"
					"\tsetg %s\n"
					,
					ops[i].arg1.c_str(), ops[i].arg2.c_str(),
					ops[i].arg1.c_str(),
					x86nasm_register_tolower( ops[i].arg1 ).c_str()
				);
			} break;

			case PXIT_LESSTHAN: {
				// dest, src
				ret += strf(
					"\tcmp %s, %s\n"
					"\tmov %s, 0\n"
					"\tsetl %s\n"
					,
					ops[i].arg1.c_str(), ops[i].arg2.c_str(),
					ops[i].arg1.c_str(),
					x86nasm_register_tolower( ops[i].arg1 ).c_str()
				);
			} break;

			case PXIT_ADD_REG_IMM: {
				ret += "\tadd "+ops[i].arg1 + strf(", %d\n", ops[i].i2);
			} break;

			case PXIT_ADD_REG_REG: {
				ret += "\tadd "+ops[i].arg1 + ", " + ops[i].arg2 + "\n";
			} break;

			case PXIT_SUB_REG_IMM: {
				ret += "\tsub "+ops[i].arg1 + strf(", %d\n", ops[i].i2);
			} break;

			case PXIT_SUB_REG_REG: {
				ret += "\tsub "+ops[i].arg1 + ", " + ops[i].arg2 + "\n";
			} break;

			case PXIT_MUL_REG_IMM: {
				ret += "\timul "+ops[i].arg1 + strf(", %d\n", ops[i].i2);
			} break;

			case PXIT_MUL_REG_REG: {
				ret += "\timul "+ops[i].arg1 + ", " + ops[i].arg2 + "\n";
			} break;

			case PXIT_CLEAR_REGISTER: {
				// faster than xor eax,eax and smaller than mov eax,0
				ret += "\tand "+ops[i].arg1 + ", 0\n";
			} break;

			case PXIT_IMPORT_EXTERN: {
				ret += "extern " + ops[i].arg1 + "\n";
			} break;

			default: {
				return "%fatal \"No x86nasm renderer for IL opcode!\"\n";
			} break;
		}
	}

	// Code epilogue

	if (commentBuffer.length()) {
		ret += "; ";
		ret += commentBuffer;
		ret += "\n";
		commentBuffer.clear();
	}

	ret += (
		"; Code section complete\n"
		"\tleave\n"
		"\tret\n"
	);

	// String table

	if (stringTable.size()) {
		ret += (
			"\n"
			"; String table\n"
				"\tsection .data\n"
		);
		for (size_t i = 0, e = stringTable.size(); i != e; ++i) {
			ret += (strf(
				"PXSTRING_DEFINE_VARIABLE ctstr%08x, %s\n",
				i, 
				escape_string(stringTable[i]).c_str()
			));
		}
	}

	// Final epilogue

	ret += strf(
		"; Program %08x end\n"
		, this
	);

	return ret;
}
