/*
 * Copyright 2009 Nicolai Hähnle <nhaehnle@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * on the rights to use, copy, modify, merge, publish, distribute, sub
 * license, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE. */

#include "radeon_emulate_branches.h"

#include <stdio.h>

#include "radeon_compiler.h"
#include "radeon_dataflow.h"

#define VERBOSE 0

#define DBG(...) do { if (VERBOSE) fprintf(stderr, __VA_ARGS__); } while(0)


struct proxy_info {
	unsigned int Proxied:1;
	unsigned int Index:RC_REGISTER_INDEX_BITS;
};

struct register_proxies {
	struct proxy_info Temporary[RC_REGISTER_MAX_INDEX];
};

struct branch_info {
	struct rc_instruction * If;
	struct rc_instruction * Else;
};

struct emulate_branch_state {
	struct radeon_compiler * C;

	struct branch_info * Branches;
	unsigned int BranchCount;
	unsigned int BranchReserved;
};


static void handle_if(struct emulate_branch_state * s, struct rc_instruction * inst)
{
	memory_pool_array_reserve(&s->C->Pool, struct branch_info,
			s->Branches, s->BranchCount, s->BranchReserved, 1);

	DBG("%s\n", __FUNCTION__);

	struct branch_info * branch = &s->Branches[s->BranchCount++];
	memset(branch, 0, sizeof(struct branch_info));
	branch->If = inst;

	/* Make a safety copy of the decision register, because we will need
	 * it at ENDIF time and it might be overwritten in both branches. */
	struct rc_instruction * inst_mov = rc_insert_new_instruction(s->C, inst->Prev);
	inst_mov->U.I.Opcode = RC_OPCODE_MOV;
	inst_mov->U.I.DstReg.File = RC_FILE_TEMPORARY;
	inst_mov->U.I.DstReg.Index = rc_find_free_temporary(s->C);
	inst_mov->U.I.DstReg.WriteMask = RC_MASK_X;
	inst_mov->U.I.SrcReg[0] = inst->U.I.SrcReg[0];

	inst->U.I.SrcReg[0].File = RC_FILE_TEMPORARY;
	inst->U.I.SrcReg[0].Index = inst_mov->U.I.DstReg.Index;
	inst->U.I.SrcReg[0].Swizzle = 0;
	inst->U.I.SrcReg[0].Abs = 0;
	inst->U.I.SrcReg[0].Negate = 0;
}

static void handle_else(struct emulate_branch_state * s, struct rc_instruction * inst)
{
	if (!s->BranchCount) {
		rc_error(s->C, "Encountered ELSE outside of branches");
		return;
	}

	DBG("%s\n", __FUNCTION__);

	struct branch_info * branch = &s->Branches[s->BranchCount - 1];
	branch->Else = inst;
}


struct state_and_proxies {
	struct emulate_branch_state * S;
	struct register_proxies * Proxies;
};

static struct proxy_info * get_proxy_info(struct state_and_proxies * sap,
			rc_register_file file, unsigned int index)
{
	if (file == RC_FILE_TEMPORARY) {
		return &sap->Proxies->Temporary[index];
	} else {
		return 0;
	}
}

static void scan_write(void * userdata, struct rc_instruction * inst,
		rc_register_file file, unsigned int index, unsigned int comp)
{
	struct state_and_proxies * sap = userdata;
	struct proxy_info * proxy = get_proxy_info(sap, file, index);

	if (proxy && !proxy->Proxied) {
		proxy->Proxied = 1;
		proxy->Index = rc_find_free_temporary(sap->S->C);
	}
}

static void remap_proxy_function(void * userdata, struct rc_instruction * inst,
		rc_register_file * pfile, unsigned int * pindex)
{
	struct state_and_proxies * sap = userdata;
	struct proxy_info * proxy = get_proxy_info(sap, *pfile, *pindex);

	if (proxy && proxy->Proxied) {
		*pfile = RC_FILE_TEMPORARY;
		*pindex = proxy->Index;
	}
}

/**
 * Redirect all writes in the instruction range [begin, end) to proxy
 * temporary registers.
 */
static void allocate_and_insert_proxies(struct emulate_branch_state * s,
		struct register_proxies * proxies,
		struct rc_instruction * begin,
		struct rc_instruction * end)
{
	struct state_and_proxies sap;

	sap.S = s;
	sap.Proxies = proxies;

	for(struct rc_instruction * inst = begin; inst != end; inst = inst->Next) {
		rc_for_all_writes_mask(inst, scan_write, &sap);
		rc_remap_registers(inst, remap_proxy_function, &sap);
	}

	for(unsigned int index = 0; index < RC_REGISTER_MAX_INDEX; ++index) {
		if (proxies->Temporary[index].Proxied) {
			struct rc_instruction * inst_mov = rc_insert_new_instruction(s->C, begin->Prev);
			inst_mov->U.I.Opcode = RC_OPCODE_MOV;
			inst_mov->U.I.DstReg.File = RC_FILE_TEMPORARY;
			inst_mov->U.I.DstReg.Index = proxies->Temporary[index].Index;
			inst_mov->U.I.DstReg.WriteMask = RC_MASK_XYZW;
			inst_mov->U.I.SrcReg[0].File = RC_FILE_TEMPORARY;
			inst_mov->U.I.SrcReg[0].Index = index;
		}
	}
}


static void inject_cmp(struct emulate_branch_state * s,
		struct rc_instruction * inst_if,
		struct rc_instruction * inst_endif,
		rc_register_file file, unsigned int index,
		struct proxy_info ifproxy,
		struct proxy_info elseproxy)
{
	struct rc_instruction * inst_cmp = rc_insert_new_instruction(s->C, inst_endif);
	inst_cmp->U.I.Opcode = RC_OPCODE_CMP;
	inst_cmp->U.I.DstReg.File = file;
	inst_cmp->U.I.DstReg.Index = index;
	inst_cmp->U.I.DstReg.WriteMask = RC_MASK_XYZW;
	inst_cmp->U.I.SrcReg[0] = inst_if->U.I.SrcReg[0];
	inst_cmp->U.I.SrcReg[0].Abs = 1;
	inst_cmp->U.I.SrcReg[0].Negate = RC_MASK_XYZW;
	inst_cmp->U.I.SrcReg[1].File = RC_FILE_TEMPORARY;
	inst_cmp->U.I.SrcReg[1].Index = ifproxy.Proxied ? ifproxy.Index : index;
	inst_cmp->U.I.SrcReg[2].File = RC_FILE_TEMPORARY;
	inst_cmp->U.I.SrcReg[2].Index = elseproxy.Proxied ? elseproxy.Index : index;
}

static void handle_endif(struct emulate_branch_state * s, struct rc_instruction * inst)
{
	if (!s->BranchCount) {
		rc_error(s->C, "Encountered ENDIF outside of branches");
		return;
	}

	DBG("%s\n", __FUNCTION__);

	struct branch_info * branch = &s->Branches[s->BranchCount - 1];
	struct register_proxies IfProxies;
	struct register_proxies ElseProxies;

	memset(&IfProxies, 0, sizeof(IfProxies));
	memset(&ElseProxies, 0, sizeof(ElseProxies));

	allocate_and_insert_proxies(s, &IfProxies, branch->If->Next, branch->Else ? branch->Else : inst);

	if (branch->Else)
		allocate_and_insert_proxies(s, &ElseProxies, branch->Else->Next, inst);

	/* Insert the CMP instructions at the end. */
	for(unsigned int index = 0; index < RC_REGISTER_MAX_INDEX; ++index) {
		if (IfProxies.Temporary[index].Proxied || ElseProxies.Temporary[index].Proxied) {
			inject_cmp(s, branch->If, inst, RC_FILE_TEMPORARY, index,
					IfProxies.Temporary[index], ElseProxies.Temporary[index]);
		}
	}

	/* Remove all traces of the branch instructions */
	rc_remove_instruction(branch->If);
	if (branch->Else)
		rc_remove_instruction(branch->Else);
	rc_remove_instruction(inst);

	s->BranchCount--;

	if (VERBOSE) {
		DBG("Program after ENDIF handling:\n");
		rc_print_program(&s->C->Program);
	}
}


struct remap_output_data {
	unsigned int Output:RC_REGISTER_INDEX_BITS;
	unsigned int Temporary:RC_REGISTER_INDEX_BITS;
};

static void remap_output_function(void * userdata, struct rc_instruction * inst,
		rc_register_file * pfile, unsigned int * pindex)
{
	struct remap_output_data * data = userdata;

	if (*pfile == RC_FILE_OUTPUT && *pindex == data->Output) {
		*pfile = RC_FILE_TEMPORARY;
		*pindex = data->Temporary;
	}
}


/**
 * Output registers cannot be read from and so cannot be dealt with like
 * temporary registers.
 *
 * We do the simplest thing: If an output registers is written within
 * a branch, then *all* writes to this register are proxied to a
 * temporary register, and a final MOV is appended to the end of
 * the program.
 */
static void fix_output_writes(struct emulate_branch_state * s, struct rc_instruction * inst)
{
	if (!s->BranchCount)
		return;

	const struct rc_opcode_info * opcode = rc_get_opcode_info(inst->U.I.Opcode);

	if (!opcode->HasDstReg)
		return;

	if (inst->U.I.DstReg.File == RC_FILE_OUTPUT) {
		struct remap_output_data remap;

		remap.Output = inst->U.I.DstReg.Index;
		remap.Temporary = rc_find_free_temporary(s->C);

		for(struct rc_instruction * inst = s->C->Program.Instructions.Next;
		    inst != &s->C->Program.Instructions;
		    inst = inst->Next) {
			rc_remap_registers(inst, &remap_output_function, &remap);
		}

		struct rc_instruction * inst_mov = rc_insert_new_instruction(s->C, s->C->Program.Instructions.Prev);
		inst_mov->U.I.Opcode = RC_OPCODE_MOV;
		inst_mov->U.I.DstReg.File = RC_FILE_OUTPUT;
		inst_mov->U.I.DstReg.Index = remap.Output;
		inst_mov->U.I.DstReg.WriteMask = RC_MASK_XYZW;
		inst_mov->U.I.SrcReg[0].File = RC_FILE_TEMPORARY;
		inst_mov->U.I.SrcReg[0].Index = remap.Temporary;
	}
}

/**
 * Remove branch instructions; instead, execute both branches
 * on different register sets and choose between their results
 * using CMP instructions in place of the original ENDIF.
 */
void rc_emulate_branches(struct radeon_compiler *c, void *user)
{
	struct emulate_branch_state s;

	memset(&s, 0, sizeof(s));
	s.C = c;

	/* Untypical loop because we may remove the current instruction */
	struct rc_instruction * ptr = c->Program.Instructions.Next;
	while(ptr != &c->Program.Instructions) {
		struct rc_instruction * inst = ptr;
		ptr = ptr->Next;

		if (inst->Type == RC_INSTRUCTION_NORMAL) {
			switch(inst->U.I.Opcode) {
			case RC_OPCODE_IF:
				handle_if(&s, inst);
				break;
			case RC_OPCODE_ELSE:
				handle_else(&s, inst);
				break;
			case RC_OPCODE_ENDIF:
				handle_endif(&s, inst);
				break;
			default:
				fix_output_writes(&s, inst);
				break;
			}
		} else {
			rc_error(c, "%s: unhandled instruction type\n", __FUNCTION__);
		}
	}
}
