/*
 * Copyright (C) 1985-1992  New York University
 * 
 * This file is part of the Ada/Ed-C system.  See the Ada/Ed README file for
 * warranty (none) and distribution info and also the GNU General Public
 * License for more details.

 */
#define GEN

#include "hdr.h"
#include "vars.h"
#include "gvars.h"
#include "ops.h"
#include "genop.h"
#include "genp.h"
#include "peepp.h"

static void code_stack_copy(Op, Op);
static int code_same_arg(Op, Op);
static void flush(int);
static int remov(int);

extern int peep_option; /* defined in gmain.c */

#define STACK_DEPTH 50
#define MATCH(v) (code_stack[index-1].op_code == v)

static Op_s code_stack[STACK_DEPTH + 1]; /* stack of instructions */
static int tos = 0;						 /* top of stack for peep-hole */

/*
 *           P E E P - H O L E   O P T I M I Z E R
 *
 * Algorithm: keep a array of instructions to be optimized in the 
 *            global array code_stack. The global variable tos is
 *            the index of the last instruction entered in the array.
 *
 * The algorithm proceeds in three steps:
 *
 * 1/ check if the array is full and take the appropriate action.
 *    Add the new opcode in the array and increment tos.
 *
 * 2/ loop over the list of codes, using the local variable index
 *    as a pointer to the current instruction.
 *
 *    If an optimization is performed, index is set to point to 
 *    the last untouched instruction or to the first instruction
 *    in the array (0 is not used). At the end of the loop, it
 *    is incremented. If an optimization is performed, index may
 *    be decremented by 1 or left untouched. It is incremented
 *    by 1 otherwise. The loop stops when index points to the last
 *    instruction entered in the array. Note: it is essential that
 *    index verifies the following relation on entry of the loop:
 *                       2 <= index < tos
 *    As a consequence, index must be at least 1 on exit of the loop.
 *
 *    If an instruction is garanteed to prevent any further
 *    optimization, all the instructions which preceed are flushed.
 *    This instruction is kept in the array for the shake of the 
 *    'fence post effect'. (I_NOT followed by I_JUMP_IF_FALSE would
 *    not be catched otherwise).
 *
 * 3/ if the last instruction added to the array was I_END, flush
 *    the list and reset tos to 0.
 *
 */

void peep_hole(Op next)											/*;peep_hole*/
{
	int    index; /* index in the array of instructions: 1 < index < tos */
	Op     temp;  /* temporary op_code */
	char *smalloc();

	if (peep_option == 0) { /* if no optimization assemble immediately */
		assemble(next);
		return;
	}

	if (tos == 50)
		flush(10);

	++tos;

	/* put new instruction in the array */
	code_stack_copy(&code_stack[tos], next);
	for(index = ((tos > 2) ? (tos - 1) : 2); index < tos; index++) {

		switch(code_stack[index].op_code) {

#ifdef TBSL
		case(I_POP):
			if(MATCH(I_DEREF)) {
				temp = &code_stack[index-1];
				temp->op_code = I_INDIRECT_POP;
				index = remov(index);
			}
			break;

		case(I_MOVE):
			if(MATCH(I_DEREF)) {
				temp = &code_stack[index-1];
				temp->op_code = I_INDIRECT_MOVE;
				index = remov(index);
			}
			break;
#endif

		case(I_ADD_IMMEDIATE):
			if(MATCH(I_ADD_IMMEDIATE) || MATCH(I_PUSH_IMMEDIATE)) {
				temp = &code_stack[index-1];
				temp->op_arg.arg_int = 
				  (temp->op_arg.arg_int + code_stack[index].op_arg.arg_int);
				index = remov(index);
			}
			/* next is test for integer zero */
			else if (code_stack[index].op_type == OP_INT &&
			    code_stack[index].op_arg.arg_int == 0) {
				index = remov(index);
			}
			break;

		case(I_DEREF):
			if(MATCH(I_PUSH_EFFECTIVE_ADDRESS)) {
				temp = &code_stack[index-1];
				temp->op_code = I_PUSH;
				temp->op_kind = code_stack[index].op_kind;
				index = remov(index);
			}
			break;

		case(I_DISCARD_ADDR):
			if(code_stack[index+1].op_code == I_DISCARD_ADDR) {
				temp = &code_stack[index];
				temp->op_kind += code_stack[index+1].op_kind;
				temp->op_arg.arg_int = temp->op_kind;
				index = remov(index+1);
			}
			else if(code_stack[index].op_kind == 0) {
				index = remov(index);
			}
			else if(MATCH(I_PUSH_EFFECTIVE_ADDRESS)) {
				temp = &code_stack[index];
				temp->op_kind -= 1;
				temp->op_arg.arg_int  -= 1;
				index = remov(index-1);
			}
			else if(MATCH(I_UPDATE)) {
				temp = &code_stack[index];
				temp->op_kind -= 1;
				temp->op_arg.arg_int  -= 1;
				index -= 1;
				code_stack[index].op_code = I_UPDATE_AND_DISCARD;
			}
			break;

		case(I_FLOAT_POW):
			if(MATCH(I_PUSH_IMMEDIATE)) {
				temp = &code_stack[index-1];
				if (temp->op_arg.arg_int == 2) {
					temp->op_code = I_DUPLICATE;
					temp->op_kind = code_stack[index].op_kind;
					code_stack[index].op_code = I_FLOAT_MUL;
				}
			}
			break;

		case(I_PUSH):
			if (MATCH(I_PUSH)
			  && (code_same_arg(&code_stack[index], &code_stack[index-1]))) {
				code_stack[index].op_code = I_DUPLICATE;
			}
			else if(MATCH(I_POP)
			  && (code_same_arg(&code_stack[index], &code_stack[index-1]))) {
				code_stack[index].op_code = I_POP;
				code_stack[index-1].op_code = I_DUPLICATE;
			}
			break;

			case(I_PUSH_EFFECTIVE_ADDRESS):
			if(MATCH(I_UPDATE_AND_DISCARD)
			  && (code_same_arg(&code_stack[index], &code_stack[index-1]))) {
				temp = &code_stack[index-1];
				temp->op_code = I_UPDATE;
				index = remov(index);
			}
			break;

		case(I_POW):
			if(MATCH(I_PUSH_IMMEDIATE)) {
				temp = &code_stack[index-1];
				if (temp->op_type == OP_INT && temp->op_arg.arg_int == 2) {
					temp->op_code = I_DUPLICATE;
					temp->op_kind = code_stack[index].op_kind;
					code_stack[index].op_code = I_MUL;
				}
			}
			break;

		case(I_STMT):
			if (MATCH(I_STMT)) {
				index = remov(index-1);
			}
			break;

		case(I_NOT):
			temp = &code_stack[index+1];
			if(temp->op_code == I_JUMP_IF_FALSE) {
				temp->op_code = I_JUMP_IF_TRUE;
				index = remov(index);
			}
			else if(temp->op_code == I_JUMP_IF_TRUE) {
				temp->op_code = I_JUMP_IF_FALSE;
				index = remov(index);
			}
			else {
				flush(index);
				index = 1;
			}
			break;

		/*
		 * Here follow the instructions for which no optimization
		 * can be attempted. Note: those should not appear in any 
		 * of the preceeding rules.
		 */
		case(I_ABORT):
		case(I_ABS):
		case(I_ACTIVATE):
		case(I_ACTIVATE_NEW):
		case(I_ADD):
		case(I_AND):
		case(I_ALLOCATE):
		case(I_ALLOCATE_COPY):
		case(I_ARRAY_AND):
		case(I_ARRAY_CATENATE):
		case(I_ARRAY_MOVE):
		case(I_ARRAY_NOT):
		case(I_ARRAY_OR):
		case(I_ARRAY_SLICE):
		case(I_ARRAY_XOR):
		case(I_ATTRIBUTE):
		case(I_CALL):
		case(I_CALL_PREDEF):
		case(I_CASE):
		case(I_CASE_TABLE):
		case(I_COMPARE):
		case(I_COMPARE_STRUC):
		case(I_CONVERT_TO):
		case(I_CREATE):
		case(I_CREATE_COPY):
		case(I_CREATE_COPY_STRUC):
		case(I_CREATE_TASK):
		case(I_CURRENT_TASK):
		case(I_DATA):
		case(I_DECLARE):
		case(I_DIV):
		case(I_END_ACTIVATION):
		case(I_END_FOR_LOOP):
		case(I_END_FORREV_LOOP):
		case(I_END_RENDEZVOUS):
		case(I_ENTER_BLOCK):
		case(I_ENTRY_CALL):
		case(I_FIX_MUL):
		case(I_FIX_DIV):
		case(I_FLOAT_ABS):
		case(I_FLOAT_ADD):
		case(I_FLOAT_COMPARE):
		case(I_FLOAT_DIV):
		case(I_FLOAT_MUL):
		case(I_FLOAT_NEG):
		case(I_FLOAT_SUB):
		case(I_INSTALL_HANDLER):
		case(I_IS_EQUAL):
		case(I_IS_GREATER):
		case(I_IS_GREATER_OR_EQUAL):
		case(I_IS_LESS):
		case(I_IS_LESS_OR_EQUAL):
		case(I_JUMP):
		case(I_JUMP_IF_GREATER):
		case(I_JUMP_IF_GREATER_OR_EQUAL):
		case(I_JUMP_IF_LESS):
		case(I_JUMP_IF_LESS_OR_EQUAL):
		case(I_LABEL):
		case(I_LEAVE_BLOCK):
		case(I_LINK_TASKS_DECLARED):
		case(I_LOAD_EXCEPTION_REGISTER):
		case(I_MEMBERSHIP):
		case(I_MOD):
		case(I_MUL):
		case(I_NEG):
		case(I_NOP):
		case(I_OR):
		case(I_POP_TASKS_DECLARED):
		case(I_QUAL_DISCR):
		case(I_QUAL_INDEX):
		case(I_QUAL_RANGE):
		case(I_QUAL_SUB):
		case(I_RAISE_IN_CALLER):
		case(I_RECORD_MOVE):
		case(I_REM):
		case(I_RESTORE_STACK_POINTER):
		case(I_RETURN):
		case(I_RETURN_STRUC):
		case(I_SAVE_STACK_POINTER):
		case(I_SELECT):
		case(I_SELECTIVE_WAIT):
		case(I_SUB):
		case(I_SUBPROGRAM):
		case(I_SUBSCRIPT):
		case(I_TERMINATE):
		case(I_TEST_EXCEPTION_REGISTER):
		case(I_TIMED_ENTRY_CALL):
		case(I_TYPE_GLOBAL):
		case(I_TYPE_LOCAL):
		case(I_UNCREATE):
		case(I_WAIT):
		case(I_XOR):
		case(I_CALL_INTERFACE):
		case(I_COMPARE_ARRAYS):
		case(I_CHECK_REC_SUBTYPE):
			flush(index-1);
			index = 1;
			break;

		default:
			;
		}
	} /* end for */

	if(next->op_code == I_END)
		flush(tos);
}

static void code_stack_copy(Op opnew, Op opold)				/*;code_stack_copy*/
{
	/* copy operation values */
	register int i;
	register char *newc, *old;
	old = (char *) opold;
	newc = (char *) opnew;
	for (i = sizeof(Op_s); i; i--)
		*newc++ = *old++;
}

static int code_same_arg(Op op1, Op op2)					 /*;code_same_arg*/
{
	/* see if operations i1 and i2 in code_stack have same argument */
	if (op1->op_type != op2->op_type)
		return FALSE;

	switch (op1->op_type) {
	case OP_FIX :
		return (op1->op_arg.arg_fix == op2->op_arg.arg_fix);
	case OP_FLT :
		return (op1->op_arg.arg_flt == op2->op_arg.arg_flt);
	case OP_INT :
		return (op1->op_arg.arg_int == op2->op_arg.arg_int);
	case OP_SYM :
		return (op1->op_arg.arg_sym == op2->op_arg.arg_sym);
	}
	return FALSE;
}

static void flush(int n)			                                 /*;flush*/
{
	/* Flush n instructions from the array code_stack and move
	 * the remaining instructions n positions to the beginning
	 * of the array. Note: n should be positive. The global 
	 * variable tos is set accordingly. If the argument is equal
	 * to tos, all the instructions of the array are passed to
	 * assemble and tos is set to 0 (peep-hole called with I_END).
	 */

	int i, j;

	for (i = 1; i <= n; i++)
		assemble(&code_stack[i]);

	for (j = 1, i = n; i++ < tos; j++)
		code_stack_copy(&code_stack[j], &code_stack[i]);

	tos -= n;
}

static int remov(int n)				                                /*;remov*/
{
	/*
	 * Remove the instruction of rank n from the array.
	 * The instructions with greater values of the index, if
	 * any, are shifted one position toward the lower indices.
	 * This function returns the last untouched index or 1.
	 */

	int i;

	for (i = n; i < tos; i++)
		code_stack_copy(&code_stack[i], &code_stack[i+1]);

	tos -= 1;
	return ((n > 1) ? (n-1) : 1);
}
