/*
 * C compiler
 * ==========
 *
 * Copyright 1989, 1990, 1991 Christoph van Wuellen.
 * Credits to Matthew Brandt.
 * All commercial rights reserved.
 *
 * This compiler may be redistributed as long there is no
 * commercial interest. The compiler must not be redistributed
 * without its full sources. This notice must stay intact.
 *
 * History:
 *
 * 1989   starting an 68000 C compiler, starting with material
 *        originally by M. Brandt
 * 1990   68000 C compiler further bug fixes
 *        started i386 port (December)
 * 1991   i386 port finished (January)
 *        further corrections in the front end and in the 68000
 *        code generator.
 *        The next port will be a SPARC port
 */

#include "chdr.h"
#ifdef CPU_DEFINED
#include "expr.h"
#include "cglbdec.h"
#include "proto.h"
#include "outproto.h"

static LABEL breaklab;
static LABEL contlab;
static LABEL retlab;

#if defined(__STDC__) || defined(__cplusplus)
#define P_(s) s
#else
#define P_(s) ()
#endif

static	void	genwhile	P_((const STMT *));
static	void	genfor		P_((const STMT *));
static	void	genif		P_((const STMT *));
static	void	gendo		P_((const STMT *));
static	void	genswitch	P_((const STMT *));
static	void	gencase		P_((const STMT *));
static	void	genxswitch	P_((const STMT *));
static	void	genreturn	P_((const STMT *));
static	void	genstmt		P_((const STMT *));

#undef P_

static BOOL
is_nolazy P2(const STMT*, stmt, BOOL, all_jumps)
{
    for (; stmt ; stmt = stmt->next) {
	switch (stmt->stype) {
	  case st_for:
	  case st_while:
	  case st_do:
	    if (is_nolazy(stmt->s1, FALSE))
		return TRUE;
	    break;
	  case st_goto:
	    return TRUE;
	  case st_continue:
	  case st_break:
#ifdef ASM
	  case st_asm:		/* assume the worst! */
#endif /* ASM */
	    return all_jumps;
	  case st_if:
	    if (is_nolazy(stmt->v1.s, all_jumps))
		return TRUE;
	    /*FALLTHRU*/
	  case st_compound:
	  case st_switch:
	    if (is_nolazy(stmt->s1, all_jumps))
		return TRUE;
	    break;
	  case st_case:
	  case st_default:
	  case st_label:
	    if (is_nolazy(stmt->v1.s, all_jumps))
		return TRUE;
	    break;
	  case st_expr:
	  case st_return:
	    break;
	  default:
	    CANNOT_REACH_HERE();
	    break;
	}
    }
    return FALSE;
}

/*
**	save the current offset of delayed stack optimisations and reset the
**	pointer to 0.
*/
static SIZE
lazystack P1(BOOL, nolazy)
{
    SIZE	offset;
    if (nolazy)
	return 0L;
    offset = stack_offset;
    stack_offset = 0L;
    return offset;
}

/*
**	restore the delayed stack operations back to a previously saved value
*/
static void
reset_lazystack P2(BOOL, nolazy, SIZE, offset)
{
    if (!nolazy) {
	g_stack(stack_offset);
	stack_offset = offset;
    }
}


/*
 * generate code to evaluate a while statement.
 */
static void
genwhile P1(const STMT *, stmt)
{
     LABEL   lab1 = contlab;		/* save old continue label */
     LABEL   lab2 = breaklab;		/* save old break label */
     LABEL   looplab = nextlabel++;	/* loop pabel */
     BOOL    nolazy = is_nolazy(stmt, TRUE);
     SIZE    offset = lazystack(nolazy);

    contlab = nextlabel++;		/* new continue label */
    breaklab = nextlabel++;		/* new break label */
    g_branch(contlab);
    g_label(looplab);
    genstmt(stmt->s1);
    g_label(contlab);
    g_jtrue(stmt->exp, looplab);
    g_label(breaklab);
    breaklab = lab2;			/* restore old break label */
    contlab = lab1;			/* restore old continue label */
    reset_lazystack(nolazy, offset);
}

/*
 * generate code to evaluate a for loop
 */
static void
genfor P1(const STMT *, stmt)
{
     LABEL    old_break = breaklab;
     LABEL    old_cont = contlab;
     LABEL    loop_label = nextlabel++;
     LABEL    entry_label = nextlabel++;
     BOOL     nolazy = is_nolazy(stmt, TRUE);
     SIZE     offset = lazystack(nolazy);

    breaklab = nextlabel++;
    contlab = nextlabel++;
    g_expression(stmt->exp);
    g_branch(entry_label);
    g_label(loop_label);
    genstmt(stmt->s1);
    g_label(contlab);
    g_expression(stmt->v2.e);
    g_label(entry_label);
    if (stmt->v1.e == NIL_EXPR) {
	g_branch(loop_label);
    } else {
	g_jtrue(stmt->v1.e, loop_label);
    }
    g_label(breaklab);
    breaklab = old_break;
    contlab = old_cont;
    reset_lazystack(nolazy, offset);
}

/*
 * generate code to evaluate an if statement.
 */
static void
genif P1(const STMT *, stmt)
{
     LABEL    lab1 = nextlabel++;
     BOOL     nolazy = is_nolazy(stmt, TRUE);
     SIZE     offset = lazystack(nolazy);
    g_jfalse(stmt->exp, lab1);
    genstmt(stmt->s1);
    if (stmt->v1.s != NIL_STMT) {	/* else part exists */
	 LABEL lab2 = nextlabel++;	/* exit label */
	g_branch(lab2);
	g_label(lab1);
	genstmt(stmt->v1.s);
	g_label(lab2);
    } else {				/* no else code */
	g_label(lab1);
    }
    reset_lazystack(nolazy, offset);
}

/*
 * generate code for a do - while loop.
 */
static void
gendo P1(const STMT *, stmt)
{
     LABEL    oldcont = contlab;
     LABEL    oldbreak = breaklab;
     LABEL    dolab = nextlabel++;
     BOOL     nolazy = is_nolazy(stmt, TRUE);
     SIZE     offset = lazystack(nolazy);

    contlab = nextlabel++;
    breaklab = nextlabel++;
    g_label(dolab);
    genstmt(stmt->s1);		/* generate body */
    g_label(contlab);
    g_jtrue(stmt->exp, dolab);
    g_label(breaklab);
    breaklab = oldbreak;
    contlab = oldcont;
    reset_lazystack(nolazy, offset);
}

/*
 * The full range of case labels has no defined minimum or maximum, because
 * you can look at them as signed or as unsigned integers.  But they can be
 * compared, if close in value, using a special "<=":			(kjb)
 */
#define caselab_leq(lab1, lab2)	\
	(((UVAL) (lab2) - (UVAL) (lab1)) < (UVAL)Ox80000000UL)

/*
 * generate a switch statement.
 */
static void
genswitch P1(const STMT *, stmt)
{
    LABEL	 deflab;
    UVAL 	 min_caselabel, max_caselabel, nxt_caselabel;
    UVAL	 i;
    UVAL	 number_of_cases;
    int          number_of_ranges;
    LABEL	*labels;
    STMT	*s, *head;
    STMT	*defcase = NIL_STMT;
    EXPR *	stmtexp = stmt->exp;

    head = stmt->s1;
    /*
     * analyze the switch statement
     */
    s = head;
    if (s != NIL_STMT && s->stype == st_default) {
	defcase = s;
	s = s->s1;
    }
    /* s points to the first case label */
    number_of_cases = (UVAL)1;
    number_of_ranges = 1;
    if (s != NIL_STMT) {
	/*
	 * look for a suitable minimum case label
	 */
	min_caselabel = (UVAL)s->v2.i;
	for (s = s->s1; s != NIL_STMT; s = s->s1) {
	    if (s->stype == st_default) {
		defcase = s;
	    } else {
		if (!caselab_leq(min_caselabel, s->v2.i))
		    min_caselabel = (UVAL)s->v2.i;
		number_of_cases++;
		if (s->s1 && (s->s1 != s->v1.s || s->s1->v2.i != s->v2.i+1L))
		    number_of_ranges++;
	    }
	}
	/*
	 * find the maximum using min_caselabel as "zero"
	 */
	max_caselabel = min_caselabel;
	for (s = head; s != NIL_STMT; s = s->s1) {
	    if (s->stype != st_default) {
		    if (((UVAL)s->v2.i-min_caselabel) > (max_caselabel-min_caselabel))
		    max_caselabel = (UVAL)s->v2.i;
	    }
	}
    }

    if (defcase == NIL_STMT) {
	deflab = breaklab;
    } else {
	deflab = nextlabel++;
	defcase->v2.l = deflab;
    }

    if ((number_of_cases > (UVAL)7) &&
	(number_of_ranges > 4) &&
	((max_caselabel-min_caselabel)/number_of_cases <= (UVAL)5)) {
	/*
	 * search the cases: look for min_caselabel. nxt_caselabel is set to
	 * the label looked for in the next pass
	 */
	struct swtab   *sw = (struct swtab *)xalloc((size_t)sizeof(struct swtab));
	sw->tablab = nextlabel++;
	sw->beglab = IandD_option ? nextlabel++ : sw->tablab;
	sw->deflab = deflab;
	sw->numlabs = (LABEL) (max_caselabel-min_caselabel + 1UL);
	sw->labels = labels = (LABEL *)xalloc(sizeof(LABEL) * (size_t)sw->numlabs);
	sw->next = swtables;
	swtables = sw;

	g_switch_table (stmtexp, sw, min_caselabel, max_caselabel);

	while (caselab_leq(min_caselabel, max_caselabel)) {
	    nxt_caselabel = max_caselabel + 1UL;
	    for (s = head; s != NIL_STMT; s = s->s1) {
		if (s->stype != st_default) {
		    if ((UVAL)s->v2.i == min_caselabel) {
			s->v2.l = *labels++ = nextlabel++;
			/* remove statment from the list */
			s->stype = st_default;
		    } else if (!caselab_leq(nxt_caselabel, s->v2.i))
			nxt_caselabel = (UVAL)s->v2.i;
		}
	    }
	    /* fill the holes */
	    for (i = min_caselabel + (UVAL)1; !caselab_leq(nxt_caselabel, i); i++) {
		*labels++ = deflab;
	    }
	    min_caselabel = nxt_caselabel;
	}
    } else {
	/*
	 * generate sequential compare instructions this is not very
	 * intelligent, but quicker than a lookup in a (value,label) table
	 * and has no startup time, this makes it fast if there are few
	 * labels.
	 */
	g_switch_compare(stmtexp, head);
	g_branch(deflab);
    }
}

/*
 * generate the label and statement for the case statement.  Also generates
 * the label and statement for label statements.
 */
static void
gencase P1(const STMT *, stmt)
{
    g_label(stmt->v2.l);
    genstmt(stmt->v1.s);
}

/*
 * analyze and generate best switch statement.
 */
static void
genxswitch P1(const STMT *, stmt)
{
    LABEL    oldbreak = breaklab;
    SIZE     offset;			/* delayed stack offset */
    BOOL     nolazy;

    nolazy = is_nolazy(stmt, TRUE);
    offset = lazystack(nolazy);
    breaklab = nextlabel++;
    genswitch(stmt);
    genstmt(stmt->v1.s);
    g_label(breaklab);
    breaklab = oldbreak;
    reset_lazystack(nolazy,offset);
}

/*
 * generate a return statement.
 */
static void
genreturn P1(const STMT *, stmt)
{
    if (stmt->exp != NIL_EXPR) {
	g_return(stmt->exp, ret_type);
    }
    if (retlab == UNDEF_LABEL)
	retlab = nextlabel++;
    g_branch(retlab);
}

/*
 * genstmt will generate a statement and follow the next pointer until the
 * block is generated.
 */
static void
genstmt P1(const STMT*, stmt)
{
    for ( ; stmt != NIL_STMT; stmt = stmt->next) {
#ifdef DEBUGOPT
	if (debug_option)
	    g_line(stmt->line, stmt->linetxt);
#endif /*DEBUGOPT*/
	switch (stmt->stype) {
	  case st_goto:
	    g_branch(stmt->v2.l);
	    break;
#ifdef ASM
	  case st_asm:
#endif /* ASM */
	  case st_expr:
	    g_expression(stmt->exp);
	    break;
	  case st_return:
	    genreturn(stmt);
	    break;
	  case st_if:
	    genif(stmt);
	    break;
	  case st_while:
	    genwhile(stmt);
	    break;
	  case st_do:
	    gendo(stmt);
	    break;
	  case st_for:
	    genfor(stmt);
	    break;
	  case st_continue:
	    g_branch(contlab);
	    break;
	  case st_break:
	    g_branch(breaklab);
	    break;
	  case st_switch:
	    genxswitch(stmt);
	    break;
	  case st_compound:
	    genstmt(stmt->s1);
	    break;
	  case st_label:
	  case st_case:
	  case st_default:
	    gencase(stmt);
	    break;
	  default:
	    FATAL ((__FILE__,"genstmt","unknown statement %d",stmt->stype));
	    break;
	}
    }
}

/*
 * generate a function body.
 */
void
genfunc P4(STMT*, stmt, CSE *, cse, LINE, line, CHAR *, linetxt)
{
    LABEL	startlab = nextlabel++;
    LABEL	mainlab = nextlabel++;

    retlab = contlab = breaklab = UNDEF_LABEL;
    max_scratch = 0L;
    stack_offset = 0L;
    if (lc_auto > lc_auto_max)
	lc_auto_max = lc_auto;
    g_auto_align();

#ifdef DEBUGOPT
    if (debug_option)
	g_line(line, linetxt);
#endif /*DEBUGOPT*/
    g_branch(startlab);

    g_label(mainlab);
    g_preload(cse);		/* load any necessary expression into regs */
    genstmt(stmt);
    if (retlab != UNDEF_LABEL)
	g_label(retlab);
    g_epilogue();

    g_label(startlab);
    lc_auto_max += max_scratch;
    g_entry(lc_auto_max);
    g_branch(mainlab);
}
#endif /* CPU_DEFINED */
