/*
 * This file is part of libFirm.
 * Copyright (C) 2012 University of Karlsruhe.
 */

/**
 * @file
 * @brief       Various verify routines that check a scheduled graph for correctness.
 * @author      Matthias Braun
 * @date        05.05.2006
 */
#include "beverify.h"

#include "array.h"
#include "beirg.h"
#include "belistsched.h"
#include "belive.h"
#include "benode.h"
#include "besched.h"
#include "bitset.h"
#include "irdump_t.h"
#include "iredges_t.h"
#include "irgwalk.h"
#include "irnode_t.h"
#include "irprintf.h"
#include "set.h"
#include "target_t.h"
#include <stdbool.h>
#include <stdbool.h>

typedef struct be_verify_register_pressure_env_t_ {
	be_lv_t                     *lv;                  /**< Liveness information. */
	const arch_register_class_t *cls;                 /**< the register class to check for */
	unsigned                    registers_available;  /**< number of available registers */
	bool                        problem_found;        /**< flag indicating if a problem was found */
} be_verify_register_pressure_env_t;

/**
 * Print all nodes of a pset into a file.
 */
static void print_living_values(FILE *F, const ir_nodeset_t *live_nodes)
{
	ir_fprintf(F, "\t");
	foreach_ir_nodeset(live_nodes, node, iter) {
		ir_fprintf(F, "%+F ", node);
	}
	ir_fprintf(F, "\n");
}

static void verify_warnf(ir_node const *const node, char const *const fmt, ...)
{
	FILE* const f = stderr;

	ir_node const *const block    = get_block_const(node);
	ir_graph      *const irg      = get_irn_irg(node);
	ir_entity     *const irg_ent  = get_irg_entity(irg);
	char    const *const irg_name = get_entity_ld_name(irg_ent);
	ir_fprintf(f, "%+F(%s): verify warning: ", block, irg_name);
	va_list ap;
	va_start(ap, fmt);
	ir_vfprintf(f, fmt, ap);
	va_end(ap);
	fputc('\n', f);
}

/**
 * Check if number of live nodes never exceeds the number of available registers.
 */
static void verify_liveness_walker(ir_node *block, void *data)
{
	be_verify_register_pressure_env_t *env = (be_verify_register_pressure_env_t *)data;
	ir_nodeset_t live_nodes;

	/* collect register pressure info, start with end of a block */
	ir_nodeset_init(&live_nodes);
	be_liveness_end_of_block(env->lv, env->cls, block,
	                         &live_nodes);

	unsigned pressure = ir_nodeset_size(&live_nodes);
	if (pressure > env->registers_available) {
		verify_warnf(block, "register pressure too high at end of block (%d/%d):",
			pressure, env->registers_available);
		print_living_values(stderr, &live_nodes);
		env->problem_found = true;
	}

	sched_foreach_non_phi_reverse(block, irn) {
		// print_living_values(stderr, &live_nodes);
		be_liveness_transfer(env->cls, irn, &live_nodes);

		pressure = ir_nodeset_size(&live_nodes);

		if (pressure > env->registers_available) {
			verify_warnf(block, "register pressure too high before %+F (%d/%d):",
				irn, pressure, env->registers_available);
			print_living_values(stderr, &live_nodes);
			env->problem_found = true;
		}
	}
	ir_nodeset_destroy(&live_nodes);
}

bool be_verify_register_pressure(ir_graph *irg, const arch_register_class_t *cls)
{
	be_verify_register_pressure_env_t env;
	env.lv                  = be_liveness_new(irg);
	env.cls                 = cls;
	env.registers_available = be_get_n_allocatable_regs(irg, cls);
	env.problem_found       = false;

	be_liveness_compute_sets(env.lv);
	irg_block_walk_graph(irg, verify_liveness_walker, NULL, &env);
	be_liveness_free(env.lv);

	return ! env.problem_found;
}

/*--------------------------------------------------------------------------- */

typedef struct be_verify_schedule_env_t_ {
	bool      problem_found; /**< flags indicating a problem */
	bitset_t *scheduled;     /**< bitset of scheduled nodes */
} be_verify_schedule_env_t;

static void verify_schedule_walker(ir_node *block, void *data)
{
	be_verify_schedule_env_t *env = (be_verify_schedule_env_t*) data;

	/*
	 * Tests for the following things:
	 *   1. Make sure that all phi nodes are scheduled at the beginning of the
	 *      block
	 *   2. No value is defined after it has been used
	 */
	ir_node         *non_phi_found  = NULL;
	ir_node         *cfchange_found = NULL;
	sched_timestep_t last_timestep  = 0;
	sched_foreach(block, node) {
		/* this node is scheduled */
		if (bitset_is_set(env->scheduled, get_irn_idx(node))) {
			verify_warnf(block, "%+F appears to be schedule twice");
			env->problem_found = true;
		}
		bitset_set(env->scheduled, get_irn_idx(node));

		/* Check that scheduled nodes are in the correct block */
		if (get_nodes_block(node) != block) {
			verify_warnf(block, "%+F is in wrong %+F", node, get_nodes_block(node));
			env->problem_found = true;
		}

		/* Check that timesteps are increasing */
		sched_timestep_t timestep = sched_get_time_step(node);
		if (timestep <= last_timestep) {
			verify_warnf(block, "schedule timestep did not increase at %+F", node);
			env->problem_found = true;
		}
		last_timestep = timestep;

		if (arch_get_irn_flags(node) & arch_irn_flag_not_scheduled) {
			verify_warnf(block, "flag_not_scheduled node %+F scheduled anyway", node);
			env->problem_found = true;
		}

		/* Check that phis come before any other node */
		if (!is_Phi(node)) {
			non_phi_found = node;
		} else if (non_phi_found) {
			verify_warnf(block, "%+F scheduled after non-Phi %+F", node, non_phi_found);
			env->problem_found = true;
		}

		/* Check for control flow changing nodes */
		if (is_cfop(node)) {
			/* check, that only one CF operation is scheduled */
			if (cfchange_found != NULL) {
				verify_warnf(block, "additional control flow changing node %+F scheduled after %+F", node, cfchange_found);
				env->problem_found = true;
			} else {
				cfchange_found = node;
			}
		} else if (cfchange_found != NULL) {
			/* keepany isn't a real instruction. */
			if (!be_is_Keep(node)) {
				verify_warnf(block, "%+F scheduled after control flow changing node", node);
				env->problem_found = true;
			}
		}

		/* Check that all uses come before their definitions */
		if (!is_Phi(node)) {
			sched_timestep_t nodetime = sched_get_time_step(node);
			foreach_irn_in(node, i, arg) {
				if (get_nodes_block(arg) != block || !sched_is_scheduled(arg))
					continue;

				if (sched_get_time_step(arg) >= nodetime) {
					verify_warnf(block, "%+F used by %+F before it was defined", arg, node);
					env->problem_found = true;
				}
			}
		}

		/* Check that no dead nodes are scheduled */
		if (get_irn_n_edges(node) == 0) {
			verify_warnf(block, "%+F is dead but scheduled", node);
			env->problem_found = true;
		}

		if (be_is_Keep(node) || be_is_CopyKeep(node)) {
			/* at least 1 of the keep arguments has to be its schedule
			 * predecessor */
			ir_node *prev = sched_prev(node);
			while (be_is_Keep(prev) || be_is_CopyKeep(prev))
				prev = sched_prev(prev);

			do {
				foreach_irn_in(node, i, in) {
					if (skip_Proj(in) == prev)
						goto ok;
				}
				prev = sched_prev(prev);
			} while (is_Phi(prev));
			verify_warnf(block, "%+F not scheduled after its pred node", node);
			env->problem_found = true;
ok:;
		}
	}
}

static void check_schedule(ir_node *node, void *data)
{
	be_verify_schedule_env_t *env = (be_verify_schedule_env_t*)data;
	bool const should_be = !arch_is_irn_not_scheduled(node);
	bool const scheduled = bitset_is_set(env->scheduled, get_irn_idx(node));

	if (should_be != scheduled) {
		verify_warnf(node, "%+F should%s be scheduled", node, should_be ? "" : " not");
		env->problem_found = true;
	}
}

bool be_verify_schedule(ir_graph *irg)
{
	be_verify_schedule_env_t env;
	env.problem_found = false;
	env.scheduled     = bitset_alloca(get_irg_last_idx(irg));

	irg_block_walk_graph(irg, verify_schedule_walker, NULL, &env);
	/* check if all nodes are scheduled */
	irg_walk_graph(irg, check_schedule, NULL, &env);

	return ! env.problem_found;
}

/*--------------------------------------------------------------------------- */

typedef struct be_verify_reg_alloc_env_t {
	be_lv_t *lv;
	bool     problem_found;
} be_verify_reg_alloc_env_t;

static void check_output_constraints(be_verify_reg_alloc_env_t *const env, const ir_node *node)
{
	arch_register_req_t const *const req = arch_get_irn_register_req(node);
	if (!req->cls->regs)
		return;

	/* verify output register */
	arch_register_t const *const reg = arch_get_irn_register(node);
	if (reg == NULL) {
		verify_warnf(node, "%+F should have a register assigned", node);
		env->problem_found = true;
	} else if (!arch_reg_is_allocatable(req, reg)) {
		verify_warnf(node, "register %s assigned as output of %+F not allowed (register constraint)", reg->name, node);
		env->problem_found = true;
	}
}

static void check_input_constraints(be_verify_reg_alloc_env_t *const env, ir_node *const node)
{
	arch_register_req_t const **const in_reqs = arch_get_irn_register_reqs_in(node);
	if (!in_reqs && get_irn_arity(node) != 0) {
		verify_warnf(node, "%+F has no input requirements", node);
		env->problem_found = true;
		return;
	}

	/* verify input register */
	foreach_irn_in(node, i, pred) {
		if (is_Bad(pred)) {
			verify_warnf(node, "%+F has Bad as input %d", node, i);
			env->problem_found = true;
			continue;
		}

		const arch_register_req_t *req      = arch_get_irn_register_req_in(node, i);
		const arch_register_req_t *pred_req = arch_get_irn_register_req(pred);
		if (req->cls != pred_req->cls) {
			verify_warnf(node, "%+F register class of requirement at input %d and operand differ", node, i);
			env->problem_found = true;
		}

		if (!req->cls->regs)
			continue;

		if (req->width > pred_req->width) {
			verify_warnf(node, "%+F register width of value at input %d too small", node, i);
			env->problem_found = true;
		}

		const arch_register_t *reg = arch_get_irn_register(pred);
		if (reg == NULL) {
			verify_warnf(pred, "%+F should have a register assigned (%+F input constraint)", pred, node);
			env->problem_found = true;
		} else if (!arch_reg_is_allocatable(req, reg)) {
			verify_warnf(node, "register %s as input %d of %+F not allowed (register constraint)", reg->name, i, node);
			env->problem_found = true;
		}
	}

	/* phis should be NOPs at this point, which means all input regs
	 * must be the same as the output reg */
	if (is_Phi(node)) {
		const arch_register_t *reg = arch_get_irn_register(node);
		foreach_irn_in(node, i, pred) {
			const arch_register_t *pred_reg = arch_get_irn_register(pred);

			if (reg != pred_reg && !(pred_reg->is_virtual)) {
				const char *pred_name = pred_reg != NULL ? pred_reg->name : "(null)";
				const char *reg_name  = reg != NULL ? reg->name : "(null)";
				verify_warnf(node, "input %d of %+F uses register %s instead of %s", i, node, pred_name, reg_name);
				env->problem_found = true;
			}
		}
	}
}

static bool ignore_error_for_reg(ir_graph *irg, const arch_register_t *reg)
{
	be_irg_t *birg = be_birg_from_irg(irg);
	if (birg->non_ssa_regs == NULL)
		return false;
	return rbitset_is_set(birg->non_ssa_regs, reg->global_index);
}

static void value_used(be_verify_reg_alloc_env_t *const env, ir_node const **const registers, ir_node const *const block, ir_node const *const node)
{
	const arch_register_t *reg = arch_get_irn_register(node);
	if (reg == NULL || reg->is_virtual)
		return;

	const arch_register_req_t *req = arch_get_irn_register_req(node);
	assert(req->width > 0);
	unsigned idx = reg->global_index;
	for (unsigned i = 0; i < req->width; ++i) {
		ir_node const *const reg_node = registers[idx + i];
		if (reg_node != NULL && reg_node != node
			&& !ignore_error_for_reg(get_irn_irg(block), reg)) {
			verify_warnf(block, "register %s assigned more than once (nodes %+F and %+F)", reg->name, node, reg_node);
			env->problem_found = true;
		}
		registers[idx + i] = node;
	}
}

static void value_def(be_verify_reg_alloc_env_t *const env, ir_node const **const registers, ir_node const *const node)
{
	const arch_register_t *reg = arch_get_irn_register(node);

	if (reg == NULL || reg->is_virtual)
		return;

	const arch_register_req_t *req = arch_get_irn_register_req(node);
	assert(req->width > 0);
	unsigned idx = reg->global_index;
	for (unsigned i = 0; i < req->width; ++i) {
		ir_node const *const reg_node = registers[idx + i];

		/* a little cheat, since its so hard to remove all outedges to dead code
		 * in the backend. This particular case should never be a problem. */
		if (reg_node == NULL && get_irn_n_edges(node) == 0)
			return;

		if (reg_node != node && !ignore_error_for_reg(get_irn_irg(node), reg)) {
			verify_warnf(node, "%+F not registered as value for register %s (but %+F)", node, reg->name, reg_node);
			env->problem_found = true;
		}
		registers[idx + i] = NULL;
	}
}

static void verify_block_register_allocation(ir_node *block, void *data)
{
	be_verify_reg_alloc_env_t *const env = (be_verify_reg_alloc_env_t*)data;

	unsigned        const n_regs    = ir_target.isa->n_registers;
	ir_node const **const registers = ALLOCANZ(ir_node const*, n_regs);

	be_lv_foreach(env->lv, block, be_lv_state_end, lv_node) {
		value_used(env, registers, block, lv_node);
	}

	sched_foreach_reverse(block, node) {
		be_foreach_value(node, value,
			value_def(env, registers, value);
			check_output_constraints(env, value);
		);

		check_input_constraints(env, node);

		/* process uses. (Phi inputs are no real uses) */
		if (!is_Phi(node)) {
			foreach_irn_in(node, i, use) {
				value_used(env, registers, block, use);
			}
		}
	}

	be_lv_foreach(env->lv, block, be_lv_state_in, lv_node) {
		value_def(env, registers, lv_node);
	}

	/* set must be empty now */
	for (unsigned i = 0; i < n_regs; ++i) {
		if (registers[i]) {
			verify_warnf(block, "%+F not live-in and no def found", registers[i]);
			env->problem_found = true;
		}
	}
}

bool be_verify_register_allocation(ir_graph *const irg)
{
	be_verify_reg_alloc_env_t env = {
		.lv                 = be_liveness_new(irg),
		.problem_found      = false,
	};

	be_liveness_compute_sets(env.lv);
	irg_block_walk_graph(irg, verify_block_register_allocation, NULL, &env);
	be_liveness_free(env.lv);

	return !env.problem_found;
}

/*--------------------------------------------------------------------------- */

typedef struct lv_walker_t {
	be_lv_t *given;
	be_lv_t *fresh;
} lv_walker_t;

static const char *lv_flags_to_str(unsigned flags)
{
	static const char *states[] = {
		"---",
		"i--",
		"-e-",
		"ie-",
		"--o",
		"i-o",
		"-eo",
		"ieo"
	};

	return states[flags & 7];
}

static void lv_check_walker(ir_node *bl, void *data)
{
	lv_walker_t    *const w       = (lv_walker_t*)data;
	be_lv_info_t   *const curr    = ir_nodehashmap_get(be_lv_info_t, &w->given->map, bl);
	be_lv_info_t   *const fresh   = ir_nodehashmap_get(be_lv_info_t, &w->fresh->map, bl);
	unsigned const        n_curr  = curr  ? curr->n_members  : 0;
	unsigned const        n_fresh = fresh ? fresh->n_members : 0;
	if (n_curr != n_fresh) {
		ir_fprintf(stderr, "%+F: liveness set sizes differ. curr %d, correct %d\n", bl, n_curr, n_fresh);

		ir_fprintf(stderr, "current:\n");
		for (unsigned i = 0; i < n_curr; ++i) {
			be_lv_info_node_t *const n = &curr->nodes[i];
			ir_fprintf(stderr, "%+F %u %+F %s\n", bl, i, n->node, lv_flags_to_str(n->flags));
		}

		ir_fprintf(stderr, "correct:\n");
		for (unsigned i = 0; i < n_fresh; ++i) {
			be_lv_info_node_t *const n = &fresh->nodes[i];
			ir_fprintf(stderr, "%+F %u %+F %s\n", bl, i, n->node, lv_flags_to_str(n->flags));
		}
	}
}

void be_liveness_check(be_lv_t *lv)
{
	be_lv_t *const fresh = be_liveness_new(lv->irg);
	be_liveness_compute_sets(fresh);
	lv_walker_t w = {
		.given = lv,
		.fresh = fresh,
	};
	irg_block_walk_graph(lv->irg, lv_check_walker, NULL, &w);
	be_liveness_free(fresh);
}
