/*
 * Copyright 2013 Vadim Girlin <vadimgirlin@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.
 *
 * Authors:
 *      Vadim Girlin
 */

#define GVN_DEBUG 0

#if GVN_DEBUG
#define GVN_DUMP(q) do { q } while (0)
#else
#define GVN_DUMP(q)
#endif

#include "sb_shader.h"
#include "sb_pass.h"
#include "sb_sched.h"

namespace r600_sb {

bool gvn::visit(node& n, bool enter) {
	if (enter) {


		bool rewrite = true;

		if (n.dst[0]->is_agpr()) {
			rewrite = false;
		}


		process_op(n, rewrite);

		assert(n.parent);

		if (n.parent->subtype == NST_LOOP_PHI_CONTAINER) {
			// There is a problem - sometimes with nested loops
			// loop counter initialization for inner loop is incorrectly hoisted
			// out of the outer loop

			// FIXME not sure if this is enough to fix a problem completely,
			// possibly more complete fix is needed (anyway, the
			// problem was seen only in relatively complex
			// case involving nested loops and
			// indirect access to loop counters (without proper array info
			// loop counters may be considered as array elements too),
			// was not seen in any tests
			// or real apps when proper array information is available in TGSI).

			// For now just mark the instructions that initialize loop counters
			// with DONT_HOIST flag to prevent the insts like MOV r, 0
			// (initialization of inner loop's counter with const)
			// from being hoisted out of the outer loop

			assert(!n.src.empty());
			value *v = n.src[0];

			if (v->is_any_gpr() && v->def)
				v->def->flags |= NF_DONT_HOIST;
		}

	} else {
	}
	return true;
}

bool gvn::visit(cf_node& n, bool enter) {
	if (enter) {
		process_op(n);
	} else {
	}
	return true;
}

bool gvn::visit(alu_node& n, bool enter) {
	if (enter) {
		process_op(n);
	} else {
	}
	return true;
}

bool gvn::visit(alu_packed_node& n, bool enter) {
	if (enter) {
		process_op(n);
	} else {
	}
	return false;
}

bool gvn::visit(fetch_node& n, bool enter) {
	if (enter) {
		process_op(n);
	} else {
	}
	return true;
}

bool gvn::visit(region_node& n, bool enter) {
	if (enter) {
// FIXME: loop_phi sources are undefined yet (except theone from the preceding
// code), can we handle that somehow?
//		if (n.loop_phi)
//			run_on(*n.loop_phi);
	} else {
		if (n.loop_phi)
			run_on(*n.loop_phi);

		if (n.phi)
			run_on(*n.phi);
	}
	return true;
}

bool gvn::process_src(value* &v, bool rewrite) {
	if (!v->gvn_source)
		sh.vt.add_value(v);

	if (rewrite && !v->gvn_source->is_rel()) {
		v = v->gvn_source;
		return true;
	}
	return false;
}

// FIXME: maybe handle it in the scheduler?
void gvn::process_alu_src_constants(node &n, value* &v) {
	if (n.src.size() < 3) {
		process_src(v, true);
		return;
	}

	if (!v->gvn_source)
		sh.vt.add_value(v);

	rp_kcache_tracker kc(sh);

	if (v->gvn_source->is_kcache())
		kc.try_reserve(v->gvn_source->select);

	// don't propagate 3rd constant to the trans-only instruction
	if (!n.is_alu_packed()) {
		alu_node *a = static_cast<alu_node*>(&n);
		if (a->bc.op_ptr->src_count == 3 && !(a->bc.slot_flags & AF_V)) {
			unsigned const_count = 0;
			for (vvec::iterator I = n.src.begin(), E = n.src.end(); I != E;
					++I) {
				value *c = (*I);
				if (c && c->is_readonly() && ++const_count == 2) {
					process_src(v, false);
					return;
				}
			}
		}
	}

	unsigned kcache_count = 0;
	for (vvec::iterator I = n.src.begin(), E = n.src.end(); I != E; ++I) {
		value *c = (*I);

		if (c->is_kcache() && (!kc.try_reserve(c->select) || ++kcache_count == 2)) {
			process_src(v, false);
			return;
		}
	}
	process_src(v, true);
}

void gvn::process_op(node& n, bool rewrite) {

	for(vvec::iterator I = n.src.begin(), E = n.src.end(); I != E; ++I) {
		value* &v = *I;
		if (v) {
			if (v->rel) {
				process_src(v->rel, rewrite);
			}

			if (rewrite && v->gvn_source && v->gvn_source->is_readonly() &&
					n.is_any_alu()) {
				process_alu_src_constants(n, v);
			} else if (rewrite && v->gvn_source && v->gvn_source->is_const() &&
					(n.is_fetch_op(FETCH_OP_VFETCH) ||
							n.is_fetch_op(FETCH_OP_SEMFETCH)))
				process_src(v, false);
			else
				process_src(v, rewrite);
		}
	}
	if (n.pred)
		process_src(n.pred, false);

	if (n.type == NT_IF) {
		if_node &i = (if_node&)n;
		if (i.cond)
			process_src(i.cond, false);
	}

	for(vvec::iterator I = n.dst.begin(), E = n.dst.end(); I != E; ++I) {
		value *v = *I;
		if (v) {
			if (v->rel)
				process_src(v->rel, rewrite);
			sh.vt.add_value(v);
		}
	}
}

} // namespace r600_sb
