#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include "decompiler.h"
#include "code_analyzer.h"
#include "platform_util.h"

struct Block;

#define PCM_BRANCH_TARGET		(1<<0)
#define PCM_OPCODE				(1<<1)
#define PCM_HAS_BLOCK			(1<<2)
#define PCM_NUM_BITS			3

#define NEXT_OP(cip, op)		cip += (dc->opdef[op].params + 1) * sizeof(cell_t)

struct Edge
{
	Edge *next;
	Block *target;
	uint32_t ip;
};

struct Block
{
	cell_t ip_start;
	cell_t ip_end;
	Edge *edge_list;
	Edge *final_edge;
	Block *next_in_table;
};

struct ControlFlowGraph
{
	int err;
	unsigned int max_blocks;
	unsigned int max_edges;
	unsigned int used_blocks;
	unsigned int used_edges;
	Block *blocks;
	Edge *edges;
	Block *entry;
	FunctionInfo *func;
};

DefineNode::DefineNode(BaseNode *o, const char *fmt, ...) : BaseNode(_OP_DEFINE), other(o)
{
	va_list ap;
	
	va_start(ap, fmt);
	Sp_FormatArgs(name, sizeof(name), fmt, ap);
	va_end(ap);
}

static FunctionInfo *sp_InferFuncPrototype(sp_decomp_t *dc, uint32_t code_addr, bool is_public)
{
	sp_tag_t *pTag;
	sp_file_t *plugin;
	FunctionInfo *info;
	sp_fdbg_symbol_t *sym;

	plugin = dc->plugin;
	info = new FunctionInfo;
	memset(info, 0, sizeof(FunctionInfo));

	info->code_addr = code_addr;
	
	if ((sym = Sp_FindFunctionSym(plugin, code_addr)) != NULL)
	{
		Sp_Format(info->name, sizeof(info->name), "%s", plugin->debug.stringbase + sym->name);
		if (sym->tagid != 0)
		{
			info->tag = Sp_FindTag(plugin, sym->tagid);
		}
	}
	else
	{
		Sp_Format(info->name, sizeof(info->name), "function_%x", code_addr);
		for (uint32_t i = 0; i < plugin->num_publics; i++)
		{
			if (plugin->publics[i].code_offs == code_addr)
			{
				Sp_Format(info->name,
					sizeof(info->name),
					"%s",
					plugin->publics[i].name);
				break;
			}
		}
	}
	info->sym = sym;
	info->is_public = is_public;
	

	/* Search for all locals */
	{
		uint32_t i;
		uint8_t *cursor;

		cursor = (uint8_t *)plugin->debug.symbols;
		for (i = 0; i < plugin->debug.syms_num; i++)
		{
			sym = (sp_fdbg_symbol_t *)cursor;

			if (sym->ident == SP_SYM_VARIABLE
				|| sym->ident == SP_SYM_REFARRAY
				|| sym->ident == SP_SYM_REFERENCE
				|| sym->ident == SP_SYM_ARRAY)
			{
				if (code_addr >= sym->codestart
					&& code_addr < sym->codeend
					&& sym->addr >= 0xC
					&& (sym->addr & 0x3) == 0
					&& ((sym->addr - 0xC) >> 2) < SP_MAX_EXEC_PARAMS
					&& sym->vclass == 1)
				{
					unsigned int num;

					num = (sym->addr - 0xC) >> 2;
					info->known_args[num].sym = sym;
					if (num + 1 > info->num_known_args)
					{
						info->num_known_args = num + 1;
					}
				}
			}

			cursor += sizeof(sp_fdbg_symbol_t);
			if (sym->dimcount > 0)
			{
				cursor += sizeof(sp_fdbg_arraydim_t) * sym->dimcount;
			}
		}
	}

	/* Map all arguments into place. */
	for (unsigned int i = 0; i < info->num_known_args; i++)
	{
		if ((sym = info->known_args[i].sym) == NULL)
		{
			continue;
		}
		if (sym->tagid != 0 && (pTag = Sp_FindTag(plugin, sym->tagid)) != NULL)
		{
			info->known_args[i].tag = pTag;
		}
		info->known_args[i].name = plugin->debug.stringbase + sym->name;
		
		info->known_args[i].dimcount = sym->dimcount;
		if (sym->dimcount > 0)
		{
			info->known_args[i].dims = 
				(sp_fdbg_arraydim_t *)((char *)sym + sizeof(sp_fdbg_symbol_t));
		}
	}

	return info;
}

static FunctionInfo *sp_GetFuncPrototype(sp_decomp_t *dc,
										 uint32_t code_addr,
										 bool known,
										 bool is_public = false);
static FunctionInfo *sp_GetFuncPrototype(sp_decomp_t *dc,
										 uint32_t code_addr,
										 bool known,
										 bool is_public)
{
	FunctionInfo *func;

	func = dc->funcs;
	while (func != NULL)
	{
		if (func->code_addr == code_addr)
		{
			return func;
		}
		func = func->next;
	}

	/* See if this is public. */
	if (!known)
	{
		is_public = false;
		for (unsigned int i = 0; i < dc->plugin->num_publics; i++)
		{
			if (dc->plugin->publics[i].code_offs == code_addr)
			{
				is_public = true;
				break;
			}
		}
	}

	func = sp_InferFuncPrototype(dc, code_addr, is_public);
	func->next = dc->funcs;
	dc->funcs = func;

	return func;
}

static void sp_CacheLocals(sp_decomp_t *dc, FunctionInfo *info)
{
	sp_file_t *plugin;
	sp_fdbg_symbol_t *sym;

	plugin = dc->plugin;

	/* Search for all locals */
	{
		uint32_t i;
		uint8_t *cursor;

		cursor = (uint8_t *)plugin->debug.symbols;
		for (i = 0; i < plugin->debug.syms_num; i++)
		{
			sym = (sp_fdbg_symbol_t *)cursor;

			if ((sym->codestart >= info->code_addr && sym->codestart < info->code_end)
				&& (sym->codeend <= info->code_end && sym->codeend >= info->code_addr)
				&& sym->vclass == 1)
			{
				info->num_known_vars++;
			}

			cursor += sizeof(sp_fdbg_symbol_t);
			if (sym->dimcount > 0)
			{
				cursor += sizeof(sp_fdbg_arraydim_t) * sym->dimcount;
			}
		}
	}

	/* Map all locals into place. */
	if (info->num_known_vars)
	{
		uint32_t i, num;
		uint8_t *cursor;

		num = 0;
		info->known_vars = new FuncVar[info->num_known_vars];
		memset(info->known_vars, 0, sizeof(FuncVar) * info->num_known_vars);

		cursor = (uint8_t *)plugin->debug.symbols;
		for (i = 0; i < plugin->debug.syms_num; i++)
		{
			sym = (sp_fdbg_symbol_t *)cursor;

			if (sym->ident == SP_SYM_VARIABLE
				|| sym->ident == SP_SYM_REFARRAY
				|| sym->ident == SP_SYM_REFERENCE
				|| sym->ident == SP_SYM_ARRAY)
			{
				/* Detect all function variables. */
				if ((sym->codestart >= info->code_addr && sym->codestart < info->code_end)
					&& (sym->codeend <= info->code_end && sym->codeend >= info->code_addr)
					&& sym->vclass == 1)
				{
					assert(num < info->num_known_vars);
					info->known_vars[num].sym = sym;
					info->known_vars[num].name = plugin->debug.stringbase + sym->name;
					info->known_vars[num].tag = 
						(sym->tagid == 0) ? NULL : Sp_FindTag(plugin, sym->tagid);
					info->known_vars[num].dimcount = sym->dimcount;
					info->known_vars[num].dims = 
						(sym->dimcount == 0)
						? NULL
						: (sp_fdbg_arraydim_t *)(cursor + sizeof(sp_fdbg_symbol_t));
					num++;
				}
			}

			cursor += sizeof(sp_fdbg_symbol_t);
			if (sym->dimcount > 0)
			{
				cursor += sizeof(sp_fdbg_arraydim_t) * sym->dimcount;
			}
		}

		assert(num == info->num_known_vars);
	}
}

static void sp_PrintFunctionPrototype(PrintBuffer *printer, sp_decomp_t *dc, FunctionInfo *func)
{
	FuncVar *var;
	
	if (func->is_public)
	{
		printer->Append("public ");
	}
	if (func->tag != NULL)
	{
		printer->Append("%s:", func->tag->name);
	}
	printer->Append("%s(", func->name);

	for (unsigned int i = 0; i < func->num_known_args; i++)
	{
		var = &func->known_args[i];
		if (var->sym == NULL)
		{
			printer->Append("unknown_arg_%d%s, ", i, i == func->num_known_args - 1 ? "" : ",");
			continue;
		}
		if (var->sym->ident == SP_SYM_REFERENCE)
		{
			printer->Append("&");
		}
		if (var->tag != NULL)
		{
			printer->Append("%s:", var->tag->name);
		}
		printer->Append("%s", var->name);

		if (var->sym->dimcount > 0)
		{
			sp_fdbg_arraydim_t *dims = 
				(sp_fdbg_arraydim_t *)((char *)var->sym + sizeof(sp_fdbg_symbol_t));

			for (uint16_t j = 0; j < var->sym->dimcount; j++)
			{
				if (dims[j].size == 0)
				{
					printer->Append("[]");
				}
				else
				{
					printer->Append("[%d]", dims[j].size);
				}
			}
		}

		if (i != func->num_known_args - 1)
		{
			printer->Append(", ");
		}
	}

	printer->Append(")");
}

void sp_DebugExprTree(PrintBuffer *buffer, BaseNode *node, bool is_stmt=false)
{
	switch (node->op)
	{
	case _OP_DEFINE:
		{
			DefineNode *def = (DefineNode *)node;

			buffer->Append("new %s = ", def->name);
			sp_DebugExprTree(buffer, def->other, is_stmt);
			break;
		}
	case OP_CALL:
		{
			CallNode *call = (CallNode *)node;

			buffer->Append("%s(", call->func->name);
			for (unsigned int i = 0; i < call->argc; i++)
			{
				sp_DebugExprTree(buffer, call->args[i], false);
			}
			buffer->Append(")");
			break;
		}
	case _OP_USE:
		{
			UseNode *unode = (UseNode *)node;

			buffer->Append("%s", unode->def->name);
			break;
		}
	case _OP_VAR:
		{
			VarNode *var_node = (VarNode *)node;
			buffer->Append("%s", var_node->var->name);
			break;
		}
	case _OP_NEW:
		{
			DeclNode *d_node = (DeclNode *)node;

			if (d_node->expr == NULL)
			{
				buffer->Append("decl ");
				sp_DebugExprTree(buffer, d_node->var, true);
			}
			else
			{
				buffer->Append("new ");
				sp_DebugExprTree(buffer, d_node->var, true);
				buffer->Append(" = ");
				sp_DebugExprTree(buffer, d_node->expr, true);
			}

			break;
		}
	case _OP_STMT:
	case OP_RETN:
		{
			StmtNode *u_node = (StmtNode *)node;

			while (u_node != NULL)
			{
				buffer->Append("\t");
				if (u_node->op == OP_RETN)
				{
					buffer->Append("return ");
				}
				sp_DebugExprTree(buffer, u_node->node, true);
				buffer->Append(";\n");
				u_node = u_node->next;
			}
			break;
		}
	case _OP_CONST:
		{
			ConstNode *c_node = (ConstNode *)node;

			buffer->Append("%d", c_node->val);

			break;
		}
	case OP_ADD:
	case OP_ADD_C:
	case OP_SMUL:
	case OP_SMUL_C:
	case OP_SDIV:
	case OP_SUB_ALT:
	case _OP_STORE:
	case _OP_MODULO:
		{
			BinOpNode *bin_node = (BinOpNode *)node;

			if (!is_stmt)
			{
				buffer->Append("(");
			}
			sp_DebugExprTree(buffer, bin_node->left);

			switch (node->op)
			{
			case OP_ADD:
			case OP_ADD_C:
				{
					buffer->Append(" + ");
					break;
				}
			case OP_SMUL:
			case OP_SMUL_C:
				{
					buffer->Append(" * ");
					break;
				}
			case _OP_STORE:
				{
					buffer->Append(" = ");
					break;
				}
			case OP_SUB_ALT:
				{
					buffer->Append(" - ");
					break;
				}
			case OP_SDIV:
				{
					buffer->Append(" / ");
					break;
				}
			case _OP_MODULO:
				{
					buffer->Append(" % ");
					break;
				}
			}

			sp_DebugExprTree(buffer, bin_node->right, node->op == _OP_STORE);
			if (!is_stmt)
			{
				buffer->Append(")");
			}

			break;
		}
	}
}

void sp_DebugPrint(FunctionInfo *info, sp_decomp_t *dc, BaseNode *node)
{
	PrintBuffer buffer;

	sp_PrintFunctionPrototype(&buffer, dc, info);
	buffer.Append("\n{\n");
	sp_DebugExprTree(&buffer, node, true);
	buffer.Append("}\n");
	buffer.Dump(stdout);
}

#define PCODE_VAL(pcode, offs)			*(cell_t *)((char *)(pcode)+(offs))
#define PCODE_PARAM(pcode, offs, num)	*(cell_t *)((char *)(pcode)+(offs)+((num)*sizeof(cell_t)))

FuncVar *sp_FindGraphVar(FunctionInfo *func, ucell_t cip, cell_t sp, cell_t stack)
{
	if (stack >= 0xC)
	{
		if ((stack & 0x3) != 0)
		{
			return NULL;
		}
		stack -= 0xC;
		stack >>= 2;
		if (stack >= SP_MAX_EXEC_PARAMS)
		{
			return NULL;
		}
		if (func->known_args[stack].sym == NULL)
		{
			return NULL;
		}
		return &func->known_args[stack];
	}
	else if (stack < 0)
	{
		FuncVar *var;

		if (stack < sp)
		{
			return NULL;
		}

		for (unsigned int i = 0; i < func->num_known_vars; i++)
		{
			var = &func->known_vars[i];

			if (cip >= var->sym->codestart
				&& cip <= var->sym->codeend
				&& stack == var->sym->addr)
			{
				return var;
			}
		}

		return NULL;
	}
	else
	{
		return NULL;
	}
}

#define CHECK_VALUE(v)							\
	assert(v != NULL);							\
	if (v == NULL) {							\
		err = SP_ERROR_INVALID_INSTRUCTION;		\
		goto return_error;						\
	}

void *operator new(size_t size, ControlFlowGraph *graph)
{
	return malloc(size);
}

void *operator new[](size_t size, ControlFlowGraph *graph)
{
	return malloc(size);
}

void operator delete(void *mem, ControlFlowGraph *graph)
{
	free(mem);
}

void operator delete[](void *mem, ControlFlowGraph *graph)
{
	free(mem);
}

#define MAX_STACK_ENTRIES		256

struct StackEntry
{
	StackEntry()
	{
	}
	StackEntry(cell_t d, BaseNode *e) : depth(d), expr(e)
	{
	}
	cell_t depth;
	BaseNode *expr;
};

int sp_AnalyzeGraph(sp_decomp_t *dc, ControlFlowGraph *graph)
{
	int err;
	cell_t sp;
	Block *block;
	cell_t *pcode;
	cell_t cip, cip_end;
	StmtNode *root, *rtemp;
	StmtNode *root_tail;
	BaseNode *pri, *alt;
	FunctionInfo *func, *tfunc;
	cell_t p1, p2, op;
	FuncVar *v1, *v2;
	unsigned int callnumber = 0;
	unsigned int stack_entries = 0;
	StackEntry eval_stack[MAX_STACK_ENTRIES];

	/* Init everything.
	 * Stack pointer starts off at 0.
	 */
	sp = 0;
	pri = NULL;
	alt = NULL;
	root = NULL;
	func = graph->func;
	block = graph->entry;
	pcode = (cell_t *)dc->plugin->pcode;

	cip = block->ip_start;
	cip_end = block->ip_end;

	root_tail = root = new (graph) StmtNode(_OP_STMT, NULL, NULL);

	while (cip < cip_end)
	{
		op = PCODE_VAL(pcode, cip);

		switch (op)
		{
		case OP_PROC:
		case OP_BREAK:
			{
				break;
			}
		case OP_CALL:
		case OP_SYSREQ_N:
			{
				p1 = PCODE_PARAM(pcode, cip, 1);
				if (op == OP_CALL)
				{
					tfunc = sp_GetFuncPrototype(dc, p1, false);
				}
				else if (op == OP_SYSREQ_N)
				{
					assert(dc->natives);
					assert(uint32_t(p1) < dc->plugin->num_natives);
					tfunc = &dc->natives[p1];
				}
				CHECK_VALUE(tfunc);

				/* Sanity checks, :TODO: runtime checks later. */
				assert(stack_entries >= 1);

				if (op == OP_SYSREQ_N)
				{
					p2 = PCODE_PARAM(pcode, cip, 2);
				}
				else
				{
					/* Try to read the value off the stack. */
					ConstNode *cnode;
					cnode = (ConstNode *)eval_stack[stack_entries-1].expr;
					/* Sanity checks, :TODO: runtime checks later. */
					assert(cnode->op == _OP_CONST);
					assert(cnode->val >= 0);
					assert(uint32_t(cnode->val) == tfunc->num_known_args);
					p2 = cnode->val;
					stack_entries--;
				}

				assert(uint32_t(p2) <= stack_entries);
				
				/* Build arg SSA */
				BaseNode **nodes = new (graph) BaseNode *[p2];
				for (cell_t argno = 0; argno < p2; argno++, stack_entries--)
				{
					nodes[argno] = eval_stack[stack_entries-1].expr;
				}

				/* Get rid of any args we pushed. */
				sp += p2 * sizeof(cell_t);
				if (op == OP_CALL)
				{
					sp += sizeof(cell_t);
				}
				assert(sp <= 0);

				/* Build function call SSA */
				pri = new (graph) CallNode(tfunc, nodes, p2);
				pri = new (graph) DefineNode(pri, "call%03x", ++callnumber);
				rtemp = new (graph) StmtNode(_OP_STMT, pri, NULL);
				root_tail->next = rtemp;
				root_tail = rtemp;
				pri = new (graph) UseNode((DefineNode *)pri);
				
				break;
			}
		case OP_CONST_PRI:
			{
				p1 = PCODE_PARAM(pcode, cip, 1);
				pri = new (graph) ConstNode(p1);
				break;
			}
		case OP_PUSH_C:
		case OP_PUSH2_C:
		case OP_PUSH3_C:
		case OP_PUSH5_C:
			{
				for (int N = 1; N <= dc->opdef[op].params; N++)
				{
					p1 = PCODE_PARAM(pcode, cip, N);

					/* Adjust stack pointer. */
					sp -= sizeof(cell_t);

					if ((v1 = sp_FindGraphVar(func, cip, sp, sp)) == NULL)
					{
						/* Add a new stack entry. */
						assert(stack_entries < MAX_STACK_ENTRIES);
						eval_stack[stack_entries++] = StackEntry(sp, new ConstNode(p1));
					}
					else
					{
						assert(v1->new_stmt == NULL);
						if (v1->new_stmt == NULL)
						{
							v1->new_stmt = new (graph) DeclNode(
								new (graph) VarNode(v1),
								new (graph) ConstNode(p1));
							rtemp = new (graph) StmtNode(
								_OP_STMT,
								v1->new_stmt,
								NULL);
							root_tail->next = rtemp;
							root_tail = rtemp;
						}
					}
				}
				break;
			}
		case OP_PUSH_S:
			{
				p1 = PCODE_PARAM(pcode, cip, 1);
				v1 = sp_FindGraphVar(func, cip, sp, p1);
				CHECK_VALUE(v1);

				/* Adjust stack pointer. */
				sp -= sizeof(cell_t);

				/* Right now, don't allow aliasing or overflows. */
				assert(sp_FindGraphVar(func, cip, sp, sp) == NULL);
				assert(stack_entries < MAX_STACK_ENTRIES);

				/* Add a new stack entry. */
				eval_stack[stack_entries++] = StackEntry(sp, new (graph) VarNode(v1));
				break;
			}
		case OP_PUSH_PRI:
			{
				CHECK_VALUE(pri);

				/* Adjust stack pointer. */
				sp -= sizeof(cell_t);
				
				/* Right now, don't allow aliasing or overflows. */
				assert(sp_FindGraphVar(func, cip, sp, sp) == NULL);
				assert(stack_entries < MAX_STACK_ENTRIES);

				/* Add a new stack entry. */
				eval_stack[stack_entries++] = StackEntry(sp, pri);
				break;
			}
		case OP_POP_ALT:
			{
				assert(stack_entries > 0);
				assert(eval_stack[stack_entries - 1].depth == sp);

				alt = eval_stack[--stack_entries].expr;
				sp += sizeof(cell_t);
				assert(sp <= 0);
				break;
			}
		case OP_STACK:
			{
				p1 = PCODE_PARAM(pcode, cip, 1);
				sp += p1;
				assert(sp <= 0);
				if (p1 < 0)
				{
					/* Find if we should track a new variable declaration. */
					v1 = sp_FindGraphVar(func, cip, sp, sp);
					CHECK_VALUE(v1);
					assert(v1->new_stmt == NULL);
					if (v1->new_stmt == NULL)
					{
						v1->new_stmt = new (graph) DeclNode(
							new (graph) VarNode(v1),
							NULL);
						rtemp = new (graph) StmtNode(
							_OP_STMT,
							v1->new_stmt,
							NULL);
						root_tail->next = rtemp;
						root_tail = rtemp;
					}
					assert(abs(p1) >> 2 == 1);
				}
				else
				{
					/* Erase anything left over on the evaluation stack. */
					while (stack_entries > 0
						   && eval_stack[stack_entries - 1].depth < sp)
					{
						assert(0);
						stack_entries--;
					}
				}
				break;
			}
		case OP_ZERO_PRI:
			{
				pri = new (graph) ConstNode(0);
				break;
			}
		case OP_MOVE_ALT:
			{
				CHECK_VALUE(pri);
				alt = pri;
				break;
			}
		case OP_LOAD_S_PRI:
			{
				p1 = PCODE_PARAM(pcode, cip, 1);
				v1 = sp_FindGraphVar(func, cip, sp, p1);
				CHECK_VALUE(v1);
				pri = new (graph) VarNode(v1);
				break;
			}
		case OP_LOAD_S_ALT:
			{
				p1 = PCODE_PARAM(pcode, cip, 1);
				v1 = sp_FindGraphVar(func, cip, sp, p1);
				CHECK_VALUE(v1);
				alt = new (graph) VarNode(v1);
				break;
			}
		case OP_STOR_S_PRI:
			{
				p1 = PCODE_PARAM(pcode, cip, 1);
				v1 = sp_FindGraphVar(func, cip, sp, p1);
				CHECK_VALUE(v1);
				CHECK_VALUE(pri);
				rtemp = new (graph) StmtNode(
					_OP_STMT,
					new (graph) BinOpNode(_OP_STORE, new (graph) VarNode(v1), pri),
					NULL);
				root_tail->next = rtemp;
				root_tail = rtemp;

				/** 
				 * We create a varnode to simulate a load here.
				 * This helps accidental duplication of the expression tree.
				 */
				pri = new (graph) VarNode(v1);	
				break;
			}
		case OP_LOAD_S_BOTH:
			{
				p1 = PCODE_PARAM(pcode, cip, 1);
				p2 = PCODE_PARAM(pcode, cip, 2);
				v1 = sp_FindGraphVar(func, cip, sp, p1);
				v2 = sp_FindGraphVar(func, cip, sp, p2);
				CHECK_VALUE(v1);
				CHECK_VALUE(v2);
				pri = new (graph) VarNode(v1);
				alt = new (graph) VarNode(v2);
				break;
			}
		case OP_ADD:
		case OP_SMUL:
			{
				CHECK_VALUE(pri);
				CHECK_VALUE(alt);
				pri = new (graph) BinOpNode(op, pri, alt);
				break;
			}
		case OP_SUB_ALT:
			{
				CHECK_VALUE(pri);
				CHECK_VALUE(alt);
				pri = new (graph) BinOpNode(op, alt, pri);
				break;
			}
		case OP_SDIV:
			{
				CHECK_VALUE(pri);
				CHECK_VALUE(alt);
				pri = new (graph) BinOpNode(OP_SDIV, pri, alt);
				alt = new (graph) BinOpNode(_OP_MODULO, pri, alt);
				break;
			}
		case OP_SDIV_ALT:
			{
				CHECK_VALUE(pri);
				CHECK_VALUE(alt);
				pri = new (graph) BinOpNode(OP_SDIV, alt, pri);
				alt = new (graph) BinOpNode(_OP_MODULO, alt, pri);
				break;
			}
		case OP_ADD_C:
		case OP_SMUL_C:
			{
				p1 = PCODE_PARAM(pcode, cip, 1);
				CHECK_VALUE(pri);
				pri = new (graph) BinOpNode(op, pri, new (graph) ConstNode(p1));
				break;
			}
		case OP_RETN:
			{
				CHECK_VALUE(pri);
				rtemp = new (graph) StmtNode(op, pri, NULL);
				root_tail->next = rtemp;
				root_tail = rtemp;
				break;
			}
		default:
			{
				assert(0);
				break;
			}
		}

		NEXT_OP(cip, op);
	}

	sp_DebugPrint(graph->func, dc, root->next);

	return SP_ERROR_NONE;

return_error:
	return err;
}

#undef CHECK_FUNCVAR

inline Block *sp_BlockFromMap(sp_decomp_t *dc, ControlFlowGraph *graph, cell_t cip)
{
	cell_t val;

	assert(PCODE_VAL(dc->pcode_map, cip) & PCM_HAS_BLOCK);

	val = PCODE_VAL(dc->pcode_map, cip);
	val >>= PCM_NUM_BITS;

	assert(val >= 0 && val < (cell_t)graph->used_blocks);

	return &graph->blocks[val];
}

void sp_InsertBlock(sp_decomp_t *dc, unsigned int index, cell_t cip)
{
	cell_t val;

	val = PCODE_VAL(dc->pcode_map, cip);

	assert(!(val & PCM_HAS_BLOCK));
	assert(index < 0x20000000);

	val |= PCM_HAS_BLOCK;
	val |= index << 3;

	PCODE_VAL(dc->pcode_map, cip) = val;
}

static Block *sp_BuildBlocks(sp_decomp_t *dc, ControlFlowGraph *graph, cell_t cip)
{
	cell_t op;
	bool is_branch;
	cell_t target;
	Block *block;
	Edge *edge_tail;
	Block *outgoing;
	cell_t cip_end;
	cell_t *pcode;

	target = 0;
	cip_end = graph->func->code_end;
	pcode = (cell_t *)dc->plugin->pcode;

	if (PCODE_VAL(dc->pcode_map, cip) & PCM_HAS_BLOCK)
	{
		return sp_BlockFromMap(dc, graph, cip);
	}

	/* We can't add a new block if we hit our limit. */
	assert(graph->used_blocks < graph->max_blocks);
	if (graph->used_blocks >= graph->max_blocks)
	{
		return NULL;
	}

	edge_tail = NULL;

	/* Reserve a new block. */
	block = &graph->blocks[graph->used_blocks++];
	block->ip_start = cip;
	
	/* Enter us into the map. */
	sp_InsertBlock(dc, graph->used_blocks - 1, cip);

	/* Walk the code. */
	while (cip < cip_end)
	{
		/* Check whether this is the start of a new block. */
		if (cip != block->ip_start
			&& (PCODE_VAL(dc->pcode_map, cip) & PCM_BRANCH_TARGET))
		{
			block->ip_end = cip;
			if ((outgoing = sp_BuildBlocks(dc, graph, cip)) == NULL)
			{
				return NULL;
			}
			/* :TODO: add edge */
			return block;
		}

		is_branch = false;
		op = PCODE_VAL(pcode, cip);
		NEXT_OP(cip, op);

		if (is_branch)
		{
			/* Check that branch offsets go to valid instructions. */
			if (!(PCODE_VAL(dc->pcode_map, target) & PCM_OPCODE))
			{
				graph->err = SP_ERROR_INVALID_INSTRUCTION;
				return NULL;
			}

			/* Get our target block. */
			if ((outgoing = sp_BuildBlocks(dc, graph, target)) == NULL)
			{
				return NULL;
			}
			/* :TODO: add edge */
		}
		else if (op == OP_RETN /* :TODO: || op == OP_RET*/)
		{
			block->ip_end = cip;
			return block;
		}
	}

	graph->err = SP_ERROR_INVALID_INSTRUCTION;

	return NULL;
}


int Sp_DecompFunction(sp_decomp_t *dc, uint32_t code_addr, bool is_public)
{
	int err;
	cell_t op;
	ucell_t cip;
	cell_t *pcode;
	ucell_t cip_end;
	sp_file_t *plugin;
	FunctionInfo *func;
	ControlFlowGraph graph;
	
	func = NULL;
	plugin = dc->plugin;
	memset(&graph, 0, sizeof(graph));

	if (code_addr >= plugin->pcode_size)
	{
		err = SP_ERROR_INVALID_INSTRUCTION;
		goto return_error;
	}

	pcode = (cell_t *)plugin->pcode;

	if (PCODE_VAL(pcode, code_addr) != OP_PROC)
	{
		err = SP_ERROR_INVALID_INSTRUCTION;
		goto return_error;
	}

	cip = code_addr + sizeof(cell_t);

	/* See if we have an existing function stored. */
	func = sp_GetFuncPrototype(dc, code_addr, true, is_public);

	while (cip < plugin->pcode_size)
	{
		op = PCODE_VAL(pcode, cip);
		if (op >= OP_TOTAL || op < 0)
		{
			err = SP_ERROR_INVALID_INSTRUCTION;
		}
		if (dc->opdef[op].name == NULL)
		{
			err = SP_ERROR_INVALID_INSTRUCTION;
		}
		if (op == OP_PROC)
		{
			break;
		}
		NEXT_OP(cip, op);
	}
	cip_end = cip;

	func->code_end = cip_end;

	/* Now we can cache all local vars. */
	sp_CacheLocals(dc, func);

	/** 
	 * Make the entry point as the first block.
	 * It needs to be flagged as well so it doesn't get double counted.
	 */
	memset(&PCODE_VAL(dc->pcode_map, code_addr), 0, cip_end - code_addr);
		   
	graph.max_blocks = 1;
	graph.func = func;
	PCODE_VAL(dc->pcode_map, code_addr) |= PCM_BRANCH_TARGET;

	/* Walk the code. */
	{
		cell_t target;
		bool is_branch;

		target = 0;
		cip = code_addr;
		while (cip < cip_end)
		{
			/* Get opcode */
			op = PCODE_VAL(pcode, cip);
			/* Mark as not a branch */
			is_branch = false;
			/* Make sure we know this is a valid target */
			PCODE_VAL(dc->pcode_map, cip) |= PCM_OPCODE;

			if (is_branch)
			{
				/**
				 * Keep track of where branches are and how many blocks we need.
				 */
				if (!(PCODE_VAL(dc->pcode_map, target) & PCM_BRANCH_TARGET))
				{
					PCODE_VAL(dc->pcode_map, target) |= PCM_BRANCH_TARGET;
					graph.max_blocks++;

					/**
					 * If we're creating a new block, estimate that we need to create a new edge 
					 * as well.
					 */
					graph.max_edges++;
				}

                /**
                 * In this algorithm, jumps do not terminate a block unless they are unconditional.
                 * This reduces the number of blocks and edges.
                 *
                 * A property of this is that jumps are edges, so the number of edges is identical 
				 * to the number of jumps.
                 */
				graph.max_edges++;
			}

			NEXT_OP(cip, op);
		}
	}

	/* Initialize memory needed for the graph. */
	graph.blocks = new Block[graph.max_blocks];
	memset(graph.blocks, 0, sizeof(Block) * graph.max_blocks);
	
	if (graph.max_edges != 0)
	{
		graph.edges = new Edge[graph.max_edges];
		memset(graph.edges, 0, sizeof(Edge) * graph.max_edges);
	}

	if ((graph.entry = sp_BuildBlocks(dc, &graph, code_addr)) == NULL)
	{
		if ((err = graph.err) == SP_ERROR_NONE)
		{
			err = SP_ERROR_ABORTED;
		}
		goto return_error;
	}

	if ((err = sp_AnalyzeGraph(dc, &graph)) != SP_ERROR_NONE)
	{
		goto return_error;
	}

	return SP_ERROR_NONE;

return_error:
	return err;
}
