The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * jit-cfg.c - Control Flow Graph routines for the JIT.
 *
 * Copyright (C) 2006  Southern Storm Software, Pty Ltd.
 *
 * This file is part of the libjit library.
 *
 * The libjit library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * The libjit library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the libjit library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include "jit-internal.h"
#include "jit-cfg.h"

static void
init_node(_jit_node_t node, jit_block_t block)
{
	node->block = block;
	if(block)
	{
		jit_block_set_meta(block, _JIT_BLOCK_CFG_NODE, node, 0);
	}
	node->flags = 0;
	node->succs = 0;
	node->num_succs = 0;
	node->preds = 0;
	node->num_preds = 0;

	_jit_bitset_init(&node->live_in);
	_jit_bitset_init(&node->live_out);
	_jit_bitset_init(&node->live_use);
	_jit_bitset_init(&node->live_def);

	node->dfn = -1;
}

static void
init_edge(_jit_edge_t edge)
{
	edge->src = 0;
	edge->dst = 0;
	edge->flags = 0;
}

static void
init_value_entry(_jit_value_entry_t value)
{
	value->value = 0;
}

static _jit_node_t
create_node()
{
	_jit_node_t node;
	node = jit_new(struct _jit_node);
	if(node)
	{
		init_node(node, 0);
	}
	return node;
}

_jit_cfg_t
create_cfg(jit_function_t func)
{
	_jit_cfg_t cfg;

	cfg = jit_new(struct _jit_cfg);
	if(!cfg)
	{
		return 0;
	}

	cfg->entry = create_node();
	if(!cfg->entry)
	{
		jit_free(cfg);
		return 0;
	}

	cfg->exit = create_node();
	if(!cfg->exit)
	{
		jit_free(cfg->entry);
		jit_free(cfg);
		return 0;
	}

	cfg->func = func;
	cfg->nodes = 0;
	cfg->num_nodes = 0;
	cfg->edges = 0;
	cfg->num_edges = 0;
	cfg->post_order = 0;
	cfg->values = 0;
	cfg->num_values = 0;
	cfg->max_values = 0;

	return cfg;
}

static int
build_nodes(_jit_cfg_t cfg, jit_function_t func)
{
	int count;
	jit_block_t block;

	count = 0;
	block = 0;
	while((block = jit_block_next(func, block)) != 0)
	{
		++count;
	}

	cfg->num_nodes = count;
	cfg->nodes = jit_malloc(count * sizeof(struct _jit_node));
	if(!cfg->nodes)
	{
		return 0;
	}

	count = 0;
	block = 0;
	while((block = jit_block_next(func, block)) != 0)
	{
		init_node(&cfg->nodes[count++], block);
	}

	return 1;
}

static _jit_node_t
get_next_node(_jit_cfg_t cfg, _jit_node_t node)
{
	int index = (node - cfg->nodes) + 1;
	if(index < cfg->num_nodes)
	{
		return cfg->nodes + index;
	}
	else
	{
		return cfg->exit;
	}
}

static _jit_node_t
get_label_node(_jit_cfg_t cfg, jit_label_t label)
{
	jit_block_t block;
	block = jit_block_from_label(cfg->func, label);
	if(!block)
	{
		return 0;
	}
	return jit_block_get_meta(block, _JIT_BLOCK_CFG_NODE);
}

static _jit_node_t
get_catcher_node(_jit_cfg_t cfg)
{
	jit_label_t label;
	label = cfg->func->builder->catcher_label;
	if(label == jit_label_undefined)
	{
		return cfg->exit;
	}
	return get_label_node(cfg, label);
}

static void
enum_edge(_jit_cfg_t cfg, _jit_node_t src, _jit_node_t dst, int flags, int create)
{
	if(!cfg || !src || !dst)
	{
		return;
	}

	if(create)
	{
		cfg->edges[cfg->num_edges].src = src;
		cfg->edges[cfg->num_edges].dst = dst;
		cfg->edges[cfg->num_edges].flags = flags;
		src->succs[src->num_succs] = &cfg->edges[cfg->num_edges];
		dst->preds[dst->num_preds] = &cfg->edges[cfg->num_edges];
	}

	++(cfg->num_edges);
	++(src->num_succs);
	++(dst->num_preds);
}

static void
enum_node_edges(_jit_cfg_t cfg, _jit_node_t node, int create)
{
	jit_insn_t insn;
	jit_label_t label;
	jit_label_t *labels;
	int index, num_labels;

	/* TODO: Handle catch, finally, filter blocks and calls. */

	insn = _jit_block_get_last(node->block);
	if(!insn)
	{
		/* empty block: create a fall-through edge */
		enum_edge(cfg, node, get_next_node(cfg, node), 0, create);
	}
	else if(insn->opcode == JIT_OP_BR)
	{
		label = (jit_label_t) insn->dest;
		enum_edge(cfg, node, get_label_node(cfg, label), 0, create);
	}
	else if(insn->opcode >= JIT_OP_BR_IFALSE && insn->opcode <= JIT_OP_BR_NFGE_INV)
	{
		label = (jit_label_t) insn->dest;
		enum_edge(cfg, node, get_label_node(cfg, label), 0, create);
		enum_edge(cfg, node, get_next_node(cfg, node), 0, create);
	}
	else if(insn->opcode >= JIT_OP_RETURN && insn->opcode <= JIT_OP_RETURN_SMALL_STRUCT)
	{
		enum_edge(cfg, node, cfg->exit, 0, create);
	}
	else if(insn->opcode == JIT_OP_THROW)
	{
		enum_edge(cfg, node, get_catcher_node(cfg), 0, create);
	}
	else if(insn->opcode == JIT_OP_RETHROW)
	{
		enum_edge(cfg, node, cfg->exit, 0, create);
	}
	else if(insn->opcode == JIT_OP_JUMP_TABLE)
	{
		labels = (jit_label_t *) insn->value1->address;
		num_labels = (int) insn->value2->address;
		for(index = 0; index < num_labels; index++)
		{
			enum_edge(cfg, node, get_label_node(cfg, labels[index]), 0, create);
		}
		enum_edge(cfg, node, get_next_node(cfg, node), 0, create);
	}
	else
	{
		/* otherwise create a fall-through edge */
		enum_edge(cfg, node, get_next_node(cfg, node), 0, create);
	}
}

static void
enum_all_edges(_jit_cfg_t cfg, jit_function_t func, int create)
{
	int index;

	if(cfg->num_nodes == 0)
	{
		enum_edge(cfg, cfg->entry, cfg->exit, 0, create);
	}
	else
	{
		enum_edge(cfg, cfg->entry, &cfg->nodes[0], 0, create);
		for(index = 0; index < cfg->num_nodes; index++)
		{
			enum_node_edges(cfg, &cfg->nodes[index], create);
		}
	}
}

static int
create_edges(_jit_cfg_t cfg, jit_function_t func)
{
	int index;

	if(cfg->num_edges == 0)
	{
		return 1;
	}

	cfg->edges = jit_malloc(cfg->num_edges * sizeof(struct _jit_edge));
	if(!cfg->edges)
	{
		return 0;
	}
	for(index = 0; index < cfg->num_edges; index++)
	{
		init_edge(&cfg->edges[index]);
	}
	for(index = 0; index < cfg->num_nodes; index++)
	{
		if(cfg->nodes[index].num_succs > 0)
		{
			cfg->nodes[index].succs = jit_calloc(cfg->nodes[index].num_succs,
							     sizeof(_jit_edge_t));
			if(!cfg->nodes[index].succs)
			{
				return 0;
			}
			cfg->nodes[index].num_succs = 0;
		}
		if(cfg->nodes[index].num_preds > 0)
		{
			cfg->nodes[index].preds = jit_calloc(cfg->nodes[index].num_preds,
							     sizeof(_jit_edge_t));
			if(!cfg->nodes[index].preds)
			{
				return 0;
			}
			cfg->nodes[index].num_preds = 0;
		}
	}

	cfg->num_edges = 0;
	return 1;
}

static int
build_edges(_jit_cfg_t cfg, jit_function_t func)
{
	enum_all_edges(cfg, func, 0);
	if(!create_edges(cfg, func))
	{
		return 0;
	}
	enum_all_edges(cfg, func, 1);
	return 1;
}

static int
compute_depth_first_order(_jit_cfg_t cfg)
{
	struct stack_entry
	{
		_jit_node_t node;
		int index;
	} *stack;
	_jit_node_t node;
	_jit_node_t succ;
	int post_order_num;
	int sp;
	int index;

	if(cfg->post_order)
	{
		return 1;
	}

	stack = jit_malloc((cfg->num_nodes + 1) * sizeof(struct stack_entry));
	if(!stack)
	{
		return 0;
	}

	cfg->post_order = jit_calloc(cfg->num_nodes, sizeof(_jit_node_t));
	if(!cfg->post_order)
	{
		jit_free(stack);
		return 0;
	}

	post_order_num = 0;

	stack[0].node = cfg->entry;
	stack[0].index = 0;
	sp = 1;

	while(sp)
	{
		node = stack[sp - 1].node;
		index = stack[sp - 1].index;

		succ = node->succs[index]->dst;
		if(succ != cfg->exit && (succ->flags & _JIT_NODE_VISITED) == 0)
		{
			succ->flags |= _JIT_NODE_VISITED;
			if(succ->num_succs > 0)
			{
				stack[sp].node = succ;
				stack[sp].index = 0;
				++sp;
			}
			else
			{
				cfg->post_order[post_order_num++] = succ;
			}
		}
		else
		{
			if(index < node->num_succs)
			{
				stack[sp - 1].index = index + 1;
			}
			else
			{
				if(node != cfg->entry)
				{
					cfg->post_order[post_order_num++] = node;
				}
				--sp;
			}
		}
	}

	jit_free(stack);
	return 1;
}

static jit_value_t
get_dest(jit_insn_t insn)
{
	if(insn->opcode == JIT_OP_NOP
	   || (insn->flags & JIT_INSN_DEST_OTHER_FLAGS) != 0
	   || (insn->dest && insn->dest->is_constant))
	{
		return 0;
	}
	return insn->dest;
}

static jit_value_t
get_value1(jit_insn_t insn)
{
	if(insn->opcode == JIT_OP_NOP
	   || (insn->flags & JIT_INSN_VALUE1_OTHER_FLAGS) != 0
	   || (insn->value1 && insn->value1->is_constant))
	{
		return 0;
	}
	return insn->value1;
}

static jit_value_t
get_value2(jit_insn_t insn)
{
	if(insn->opcode == JIT_OP_NOP
	   || (insn->flags & JIT_INSN_VALUE2_OTHER_FLAGS) != 0
	   || (insn->value2 && insn->value2->is_constant))
	{
		return 0;
	}
	return insn->value2;
}

static int
create_value_entry(_jit_cfg_t cfg, jit_value_t value)
{
	_jit_value_entry_t values;
	int max_values;

	if(value->index >= 0)
	{
		return 1;
	}

	if(cfg->num_values == cfg->max_values)
	{
		if(cfg->max_values == 0)
		{
			max_values = 20;
			values = jit_malloc(max_values * sizeof(struct _jit_value_entry));
		}
		else
		{
			max_values = cfg->max_values * 2;
			values = jit_realloc(cfg->values, max_values * sizeof(struct _jit_value_entry));
		}
		if(!values)
		{
			return 0;
		}
		cfg->values = values;
		cfg->max_values = max_values;
	}

	value->index = cfg->num_values++;
	init_value_entry(&cfg->values[value->index]);

	return 1;
}

static int
use_value(_jit_cfg_t cfg, _jit_node_t node, jit_value_t value)
{
	if(value->index < 0)
	{
		return 1;
	}
	if(_jit_bitset_is_allocated(&node->live_def)
	   && _jit_bitset_test_bit(&node->live_def, value->index))
	{
		return 1;
	}
	if(!_jit_bitset_is_allocated(&node->live_use)
	   && !_jit_bitset_allocate(&node->live_use, cfg->num_values))
	{
		return 0;
	}
	_jit_bitset_set_bit(&node->live_use, value->index);
	return 1;
}

static int
def_value(_jit_cfg_t cfg, _jit_node_t node, jit_value_t value)
{
	if(!_jit_bitset_is_allocated(&node->live_def)
	   && !_jit_bitset_allocate(&node->live_def, cfg->num_values))
	{
		return 0;
	}
	_jit_bitset_set_bit(&node->live_def, value->index);
	return 1;
}

static int
create_value_entries(_jit_cfg_t cfg)
{
	int index;
	_jit_node_t node;
	jit_insn_iter_t iter;
	jit_insn_t insn;
	jit_value_t dest;
	jit_value_t value1;
	jit_value_t value2;

	for(index = 0; index < cfg->num_nodes; index++)
	{
		node = &cfg->nodes[index];
		jit_insn_iter_init(&iter, node->block);
		while((insn = jit_insn_iter_next(&iter)) != 0)
		{
			dest = get_dest(insn);
			value1 = get_value1(insn);
			value2 = get_value1(insn);

			if(dest && !create_value_entry(cfg, dest))
			{
				return 0;
			}
			if(value1 && !create_value_entry(cfg, value1))
			{
				return 0;
			}
			if(value2 && !create_value_entry(cfg, value2))
			{
				return 0;
			}
		}
	}

	return 1;
}

static int
compute_local_live_sets(_jit_cfg_t cfg)
{
	int index;
	_jit_node_t node;
	jit_insn_iter_t iter;
	jit_insn_t insn;
	jit_value_t dest;
	jit_value_t value1;
	jit_value_t value2;

	for(index = 0; index < cfg->num_nodes; index++)
	{
		node = &cfg->nodes[index];
		jit_insn_iter_init(&iter, node->block);
		while((insn = jit_insn_iter_next(&iter)) != 0)
		{
			dest = get_dest(insn);
			value1 = get_value1(insn);
			value2 = get_value2(insn);

			if(value1 && !use_value(cfg, node, value1))
			{
				return 0;
			}
			if(value2 && !use_value(cfg, node, value2))
			{
				return 0;
			}
			if(dest)
			{
				if((insn->flags & JIT_INSN_DEST_IS_VALUE) != 0)
				{
					if(!use_value(cfg, node, dest))
					{
						return 0;
					}
				}
				else
				{
					if(!def_value(cfg, node, dest))
					{
						return 0;
					}
				}
			}
		}
	}

	return 1;
}

static int
compute_global_live_sets(_jit_cfg_t cfg)
{
	int change;
	int index, succ_index;
	_jit_node_t node;
	_jit_node_t succ;
	_jit_bitset_t bitset;

	if(!_jit_bitset_allocate(&bitset, cfg->num_values))
	{
		return 0;
	}

	do
	{
		change = 0;
		for(index = 0; index < cfg->num_nodes; index++)
		{
			node = cfg->post_order[index];
			if(!node)
			{
				continue;
			}

			_jit_bitset_clear(&bitset);
			for(succ_index = 0; succ_index < node->num_succs; succ_index++)
			{
				succ = node->succs[succ_index]->dst;
				if(_jit_bitset_is_allocated(&succ->live_in))
				{
					_jit_bitset_add(&bitset, &succ->live_in);
				}
			}
			if(!_jit_bitset_is_allocated(&node->live_out)
			   && !_jit_bitset_allocate(&node->live_out, cfg->num_values))
			{
				_jit_bitset_free(&bitset);
				return 0;
			}
			if(_jit_bitset_copy(&node->live_out, &bitset))
			{
				change = 1;
			}

			_jit_bitset_sub(&bitset, &node->live_def);
			_jit_bitset_add(&bitset, &node->live_use);
			if(!_jit_bitset_is_allocated(&node->live_in)
			   && !_jit_bitset_allocate(&node->live_in, cfg->num_values))
			{
				_jit_bitset_free(&bitset);
				return 0;
			}
			if(_jit_bitset_copy(&node->live_in, &bitset))
			{
				change = 1;
			}
		}
	}
	while(change);

	_jit_bitset_free(&bitset);
	return 1;
}

void
_jit_cfg_free(_jit_cfg_t cfg)
{
	int index;

	if(cfg->nodes)
	{
		for(index = 0; index < cfg->num_nodes; index++)
		{
			if(cfg->nodes[index].succs)
			{
				jit_free(cfg->nodes[index].succs);
			}
			if(cfg->nodes[index].preds)
			{
				jit_free(cfg->nodes[index].preds);
			}
		}
		jit_free(cfg->nodes);
	}
	if(cfg->edges)
	{
		jit_free(cfg->edges);
	}
	if(cfg->post_order)
	{
		jit_free(cfg->post_order);
	}
	if(cfg->values)
	{
		jit_free(cfg->values);
	}
	jit_free(cfg->entry);
	jit_free(cfg->exit);
	jit_free(cfg);
}

_jit_cfg_t
_jit_cfg_build(jit_function_t func)
{
	_jit_cfg_t cfg;

	cfg = create_cfg(func);
	if(!cfg)
	{
		return 0;
	}
	if(!build_nodes(cfg, func) || !build_edges(cfg, func))
	{
		_jit_cfg_free(cfg);
		return 0;
	}
	if(!compute_depth_first_order(cfg))
	{
		_jit_cfg_free(cfg);
		return 0;
	}

	return cfg;
}

int
_jit_cfg_compute_liveness(_jit_cfg_t cfg)
{
	return (create_value_entries(cfg)
		&& compute_local_live_sets(cfg)
		&& compute_global_live_sets(cfg));
}