/*	$Id$	 */
/*
 * Copyright (c) 2003 Anders Magnusson (ragge@ludd.luth.se).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * MIPS port by Jan Enoksson (janeno-1@student.ltu.se) and
 * Simon Olsson (simols-1@student.ltu.se) 2005.
 */

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

#include "pass1.h"
#include "pass2.h"

#ifdef TARGET_BIG_ENDIAN
int bigendian = 1;
#else
int bigendian = 0;
#endif

int nargregs = MIPS_O32_NARGREGS;

static int argsiz(NODE *p);

void
deflab(int label)
{
	printf(LABFMT ":\n", label);
}

static int regoff[32];
static TWORD ftype;

/*
 * calculate stack size and offsets
 */
static int
offcalc(struct interpass_prolog * ipp)
{
	int i, j, addto;

	addto = p2maxautooff;

	for (i = p2env.p_regs[0], j = 0; i; i >>= 1, j++) {
		if (i & 1) {
			addto += SZINT / SZCHAR;
			regoff[j] = addto;
		}
	}

        /* round to 8-byte boundary */
        addto += 7;
        addto &= ~7;

	return addto;
}

/*
 * Print out the prolog assembler.
 */
void
prologue(struct interpass_prolog * ipp)
{
	int addto;
	int i, j;

	ftype = ipp->ipp_type;
	printf("\t.align 2\n");
	if (ipp->ipp_vis)
		printf("\t.globl %s\n", ipp->ipp_name);
	printf("\t.ent %s\n", ipp->ipp_name);
	printf("%s:\n", ipp->ipp_name);

	addto = offcalc(ipp);

	/* emit PIC only if -fpic or -fPIC set */
	if (kflag > 0) {
		printf("\t.frame %s,%d,%s\n",
		    rnames[FP], ARGINIT/SZCHAR, rnames[RA]);
		printf("\t.set noreorder\n");
		printf("\t.cpload $25\t# pseudo-op to load GOT ptr into $25\n");
		printf("\t.set reorder\n");
	}

	printf("\t.frame %s,%d,%s\n", rnames[FP], ARGINIT/SZCHAR, rnames[RA]);
	printf("\t.set noreorder\n");
	printf("\t.cpload $25\t# pseudo-op to load GOT ptr into $25\n");
	printf("\t.set reorder\n");

	printf("\tsubu %s,%s,%d\n", rnames[SP], rnames[SP], ARGINIT/SZCHAR);
	/* emit PIC only if -fpic or -fPIC set */
	if (kflag > 0)
		printf("\t.cprestore 8\t# pseudo-op to store GOT ptr at 8(sp)\n");

	printf("\tsw %s,4(%s)\n", rnames[RA], rnames[SP]);
	printf("\tsw %s,(%s)\n", rnames[FP], rnames[SP]);
	printf("\tmove %s,%s\n", rnames[FP], rnames[SP]);

#ifdef notyet
	/* profiling */
	if (pflag) {
		printf("\t.set noat\n");
		printf("\tmove %s,%s\t# save current return address\n",
		    rnames[AT], rnames[RA]);
		printf("\tsubu %s,%s,8\t# _mcount pops 2 words from stack\n",
		    rnames[SP], rnames[SP]);
		printf("\tjal %s\n", exname("_mcount"));
		printf("\tnop\n");
		printf("\t.set at\n");
	}
#endif

	if (addto)
		printf("\tsubu %s,%s,%d\n", rnames[SP], rnames[SP], addto);

	for (i = p2env.p_regs[0], j = 0; i; i >>= 1, j++)
		if (i & 1)
			printf("\tsw %s,-%d(%s) # save permanent\n",
				rnames[j], regoff[j], rnames[FP]);

}

void
eoftn(struct interpass_prolog * ipp)
{
	int i, j;

	(void) offcalc(ipp);

	if (ipp->ipp_ip.ip_lbl == 0)
		return;		/* no code needs to be generated */

	/* return from function code */
	for (i = p2env.p_regs[0], j = 0; i; i >>= 1, j++) {
		if (i & 1)
			printf("\tlw %s,-%d(%s)\n\tnop\n",
				rnames[j], regoff[j], rnames[FP]);
	}

	printf("\taddiu %s,%s,%d\n", rnames[SP], rnames[FP], ARGINIT/SZCHAR);
	printf("\tlw %s,%d(%s)\n", rnames[RA], 4-ARGINIT/SZCHAR,  rnames[SP]);
	printf("\tlw %s,%d(%s)\n", rnames[FP], 0-ARGINIT/SZCHAR,  rnames[SP]);

	printf("\tjr %s\n", rnames[RA]);
	printf("\tnop\n");

#ifdef USE_GAS
	printf("\t.end %s\n", ipp->ipp_name);
	printf("\t.size %s,.-%s\n", ipp->ipp_name, ipp->ipp_name);
#endif
}

/*
 * add/sub/...
 *
 * Param given:
 */
void
hopcode(int f, int o)
{
	char *str;

	switch (o) {
	case EQ:
		str = "beqz";	/* pseudo-op */
		break;
	case NE:
		str = "bnez";	/* pseudo-op */
		break;
	case ULE:
	case LE:
		str = "blez";
		break;
	case ULT:
	case LT:
		str = "bltz";
		break;
	case UGE:
	case GE:
		str = "bgez";
		break;
	case UGT:
	case GT:
		str = "bgtz";
		break;
	case PLUS:
		str = "add";
		break;
	case MINUS:
		str = "sub";
		break;
	case AND:
		str = "and";
		break;
	case OR:
		str = "or";
		break;
	case ER:
		str = "xor";
		break;
	default:
		comperr("hopcode2: %d", o);
		str = 0;	/* XXX gcc */
	}

	printf("%s%c", str, f);
}

char *
rnames[] = {
#ifdef USE_GAS
	/* gnu assembler */
	"$zero", "$at", "$2", "$3", "$4", "$5", "$6", "$7",
	"$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15",
	"$16", "$17", "$18", "$19", "$20", "$21", "$22", "$23",
	"$24", "$25",
	"$kt0", "$kt1", "$gp", "$sp", "$fp", "$ra",
	"$2!!$3!!",
	"$4!!$5!!", "$5!!$6!!", "$6!!$7!!", "$7!!$8!!",
	"$8!!$9!!", "$9!!$10!", "$10!$11!", "$11!$12!",
	"$12!$13!", "$13!$14!", "$14!$15!", "$15!$24!", "$24!$25!",
	"$16!$17!", "$17!$18!", "$18!$19!", "$19!$20!",
	"$20!$21!", "$21!$22!", "$22!$23!",
#else
	/* mips assembler */
	 "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3",
	"$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7",
	"$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7",
	"$t8", "$t9",
	"$k0", "$k1", "$gp", "$sp", "$fp", "$ra",
	"$v0!$v1!",
	"$a0!$a1!", "$a1!$a2!", "$a2!$a3!", "$a3!$t0!",
	"$t0!$t1!", "$t1!$t2!", "$t2!$t3!", "$t3!$t4!",
	"$t4!$t5!", "$t5!$t6!", "$t6!$t7!", "$t7!$t8!", "$t8!$t9!",
	"$s0!$s1!", "$s1!$s2!", "$s2!$s3!", "$s3!$s4!",
	"$s4!$s5!", "$s5!$s6!", "$s6!$s7!",
#endif
	"$f0!$f1!", "$f2!$f3!", "$f4!$f5!", "$f6!$f7!",
	"$f8!$f9!", "$f10$f11", "$f12$f13", "$f14$f15",
	"$f16$f17", "$f18$f19", "$f20$f21", "$f22$f23",
	"$f24$f25", "$f26$f27", "$f28$f29", "$f30$f31",
};

char *
rnames_n32[] = {
	/* mips assembler */
	"$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3",
	"$a4", "$a5", "$a6", "$a7", "$t0", "$t1", "$t2", "$t3",
	"$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7",
	"$t8", "$t9",
	"$k0", "$k1", "$gp", "$sp", "$fp", "$ra",
	"$v0!$v1!",
	"$a0!$a1!", "$a1!$a2!", "$a2!$a3!", "$a3!$a4!",
	"$a4!$a5!", "$a5!$a6!", "$a6!$a7!", "$a7!$t0!",
	"$t0!$t1!", "$t1!$t2!", "$t2!$t3!", "$t3!$t8!", "$t8!$t9!",
	"$s0!$s1!", "$s1!$s2!", "$s2!$s3!", "$s3!$s4!",
	"$s4!$s5!", "$s5!$s6!", "$s6!$s7!",
	"$f0!$f1!", "$f2!$f3!", "$f4!$f5!", "$f6!$f7!",
	"$f8!$f9!", "$f10$f11", "$f12$f13", "$f14$f15",
	"$f16$f17", "$f18$f19", "$f20$f21", "$f22$f23",
	"$f24$f25", "$f26$f27", "$f28$f29", "$f30$f31",
};

int
tlen(NODE *p)
{
	switch (p->n_type) {
	case CHAR:
	case UCHAR:
		return (1);

	case SHORT:
	case USHORT:
		return (SZSHORT / SZCHAR);

	case DOUBLE:
		return (SZDOUBLE / SZCHAR);

	case INT:
	case UNSIGNED:
	case LONG:
	case ULONG:
		return (SZINT / SZCHAR);

	case LONGLONG:
	case ULONGLONG:
		return SZLONGLONG / SZCHAR;

	default:
		if (!ISPTR(p->n_type))
			comperr("tlen type %d not pointer");
		return SZPOINT(p->n_type) / SZCHAR;
	}
}


/*
 * Push a structure on stack as argument.
 */
static void
starg(NODE *p)
{
	int sz = attr_find(p->n_ap, ATTR_P2STRUCT)->iarg(0);
	//assert(p->n_rval == A1);
	printf("\tsubu %s,%s,%d\n", rnames[SP], rnames[SP], sz);
	/* A0 = dest, A1 = src, A2 = len */
	printf("\tmove %s,%s\n", rnames[A0], rnames[SP]);
	printf("\tli %s,%d\t# structure size\n", rnames[A2], sz);
	printf("\tsubu %s,%s,16\n", rnames[SP], rnames[SP]);
	printf("\tjal %s\t# structure copy\n", exname("memcpy"));
	printf("\tnop\n");
	printf("\taddiu %s,%s,16\n", rnames[SP], rnames[SP]);
}

/*
 * Structure assignment.
 */
static void
stasg(NODE *p)
{
	assert(p->n_right->n_rval == A1);
	/* A0 = dest, A1 = src, A2 = len */
	printf("\tli %s,%d\t# structure size\n", rnames[A2],
	    attr_find(p->n_ap, ATTR_P2STRUCT)->iarg(0));
	if (p->n_left->n_op == OREG) {
		printf("\taddiu %s,%s," CONFMT "\t# dest address\n",
		    rnames[A0], rnames[p->n_left->n_rval],
		    getlval(p->n_left));
	} else if (p->n_left->n_op == NAME) {
		printf("\tla %s,", rnames[A0]);
		adrput(stdout, p->n_left);
		printf("\n");
	}
	printf("\tsubu %s,%s,16\n", rnames[SP], rnames[SP]);
	printf("\tjal %s\t# structure copy\n", exname("memcpy"));
	printf("\tnop\n");
	printf("\taddiu %s,%s,16\n", rnames[SP], rnames[SP]);
}

static void
shiftop(NODE *p)
{
	NODE *r = p->n_right;
	TWORD ty = p->n_type;

	if (p->n_op == LS && r->n_op == ICON && getlval(r) < 32) {
		expand(p, INBREG, "\tsrl A1,AL,");
		printf(CONFMT "\t# 64-bit left-shift\n", 32 - getlval(r));
		expand(p, INBREG, "\tsll U1,UL,AR\n");
		expand(p, INBREG, "\tor U1,U1,A1\n");
		expand(p, INBREG, "\tsll A1,AL,AR\n");
	} else if (p->n_op == LS && r->n_op == ICON && getlval(r) < 64) {
		expand(p, INBREG, "\tli A1,0\t# 64-bit left-shift\n");
		expand(p, INBREG, "\tsll U1,AL,");
		printf(CONFMT "\n", getlval(r) - 32);
	} else if (p->n_op == LS && r->n_op == ICON) {
		expand(p, INBREG, "\tli A1,0\t# 64-bit left-shift\n");
		expand(p, INBREG, "\tli U1,0\n");
	} else if (p->n_op == RS && r->n_op == ICON && getlval(r) < 32) {
		expand(p, INBREG, "\tsll U1,UL,");
		printf(CONFMT "\t# 64-bit right-shift\n", 32 - getlval(r));
		expand(p, INBREG, "\tsrl A1,AL,AR\n");
		expand(p, INBREG, "\tor A1,A1,U1\n");
		if (ty == LONGLONG)
			expand(p, INBREG, "\tsra U1,UL,AR\n");
		else
			expand(p, INBREG, "\tsrl U1,UL,AR\n");
	} else if (p->n_op == RS && r->n_op == ICON && getlval(r) < 64) {
		if (ty == LONGLONG) {
			expand(p, INBREG, "\tsra U1,UL,31\t# 64-bit right-shift\n");
			expand(p, INBREG, "\tsra A1,UL,");
		}else {
			expand(p, INBREG, "\tli U1,0\t# 64-bit right-shift\n");
			expand(p, INBREG, "\tsrl A1,UL,");
		}
		printf(CONFMT "\n", getlval(r) - 32);
	} else if (p->n_op == LS && r->n_op == ICON) {
		expand(p, INBREG, "\tli A1,0\t# 64-bit right-shift\n");
		expand(p, INBREG, "\tli U1,0\n");
	} else {
		comperr("shiftop");
	}
}

/*
 * http://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html#Soft-float-library-routines
 */
static void
fpemulop(NODE *p)
{
	NODE *l = p->n_left;
	char *ch = NULL;

	if (p->n_op == PLUS && p->n_type == FLOAT) ch = "addsf3";
	else if (p->n_op == PLUS && p->n_type == DOUBLE) ch = "adddf3";
	else if (p->n_op == PLUS && p->n_type == LDOUBLE) ch = "addtf3";

	else if (p->n_op == MINUS && p->n_type == FLOAT) ch = "subsf3";
	else if (p->n_op == MINUS && p->n_type == DOUBLE) ch = "subdf3";
	else if (p->n_op == MINUS && p->n_type == LDOUBLE) ch = "subtf3";

	else if (p->n_op == MUL && p->n_type == FLOAT) ch = "mulsf3";
	else if (p->n_op == MUL && p->n_type == DOUBLE) ch = "muldf3";
	else if (p->n_op == MUL && p->n_type == LDOUBLE) ch = "multf3";

	else if (p->n_op == DIV && p->n_type == FLOAT) ch = "divsf3";
	else if (p->n_op == DIV && p->n_type == DOUBLE) ch = "divdf3";
	else if (p->n_op == DIV && p->n_type == LDOUBLE) ch = "divtf3";

	else if (p->n_op == UMINUS && p->n_type == FLOAT) ch = "negsf2";
	else if (p->n_op == UMINUS && p->n_type == DOUBLE) ch = "negdf2";
	else if (p->n_op == UMINUS && p->n_type == LDOUBLE) ch = "negtf2";

	else if (p->n_op == EQ && l->n_type == FLOAT) ch = "eqsf2";
	else if (p->n_op == EQ && l->n_type == DOUBLE) ch = "eqdf2";
	else if (p->n_op == EQ && l->n_type == LDOUBLE) ch = "eqtf2";

	else if (p->n_op == NE && l->n_type == FLOAT) ch = "nesf2";
	else if (p->n_op == NE && l->n_type == DOUBLE) ch = "nedf2";
	else if (p->n_op == NE && l->n_type == LDOUBLE) ch = "netf2";

	else if (p->n_op == GE && l->n_type == FLOAT) ch = "gesf2";
	else if (p->n_op == GE && l->n_type == DOUBLE) ch = "gedf2";
	else if (p->n_op == GE && l->n_type == LDOUBLE) ch = "getf2";

	else if (p->n_op == LE && l->n_type == FLOAT) ch = "lesf2";
	else if (p->n_op == LE && l->n_type == DOUBLE) ch = "ledf2";
	else if (p->n_op == LE && l->n_type == LDOUBLE) ch = "letf2";

	else if (p->n_op == GT && l->n_type == FLOAT) ch = "gtsf2";
	else if (p->n_op == GT && l->n_type == DOUBLE) ch = "gtdf2";
	else if (p->n_op == GT && l->n_type == LDOUBLE) ch = "gttf2";

	else if (p->n_op == LT && l->n_type == FLOAT) ch = "ltsf2";
	else if (p->n_op == LT && l->n_type == DOUBLE) ch = "ltdf2";
	else if (p->n_op == LT && l->n_type == LDOUBLE) ch = "lttf2";

	else if (p->n_op == SCONV && p->n_type == FLOAT) {
		if (l->n_type == DOUBLE) ch = "truncdfsf2";
		else if (l->n_type == LDOUBLE) ch = "trunctfsf2";
		else if (l->n_type == ULONGLONG) ch = "floatdisf"; /**/
		else if (l->n_type == LONGLONG) ch = "floatdisf";
		else if (l->n_type == LONG) ch = "floatsisf";
		else if (l->n_type == ULONG) ch = "floatunsisf";
		else if (l->n_type == INT) ch = "floatsisf";
		else if (l->n_type == UNSIGNED) ch = "floatunsisf";
	} else if (p->n_op == SCONV && p->n_type == DOUBLE) {
		if (l->n_type == FLOAT) ch = "extendsfdf2";
		else if (l->n_type == LDOUBLE) ch = "trunctfdf2";
		else if (l->n_type == ULONGLONG) ch = "floatunsdidf";
		else if (l->n_type == LONGLONG) ch = "floatdidf";
		else if (l->n_type == LONG) ch = "floatsidf";
		else if (l->n_type == ULONG) ch = "floatunsidf";
		else if (l->n_type == INT) ch = "floatsidf";
		else if (l->n_type == UNSIGNED) ch = "floatunsidf";
	} else if (p->n_op == SCONV && p->n_type == LDOUBLE) {
		if (l->n_type == FLOAT) ch = "extendsftf2";
		else if (l->n_type == DOUBLE) ch = "extenddfdf2";
		else if (l->n_type == ULONGLONG) ch = "floatunsdidf";
		else if (l->n_type == LONGLONG) ch = "floatdidf";
		else if (l->n_type == LONG) ch = "floatsidf";
		else if (l->n_type == ULONG) ch = "floatunssidf";
		else if (l->n_type == INT) ch = "floatsidf";
		else if (l->n_type == UNSIGNED) ch = "floatunsidf";
	} else if (p->n_op == SCONV && p->n_type == ULONGLONG) {
		if (l->n_type == FLOAT) ch = "fixunssfdi";
		else if (l->n_type == DOUBLE) ch = "fixunsdfdi";
		else if (l->n_type == LDOUBLE) ch = "fixunsdfdi";
	} else if (p->n_op == SCONV && p->n_type == LONGLONG) {
		if (l->n_type == FLOAT) ch = "fixsfdi";
		else if (l->n_type == DOUBLE) ch = "fixdfdi";
		else if (l->n_type == LDOUBLE) ch = "fixdfdi";
	} else if (p->n_op == SCONV && p->n_type == LONG) {
		if (l->n_type == FLOAT) ch = "fixsfsi";
		else if (l->n_type == DOUBLE) ch = "fixdfsi";
		else if (l->n_type == LDOUBLE) ch = "fixdfsi";
	} else if (p->n_op == SCONV && p->n_type == ULONG) {
		if (l->n_type == FLOAT) ch = "fixunssfsi";
		else if (l->n_type == DOUBLE) ch = "fixunsdfsi";
		else if (l->n_type == LDOUBLE) ch = "fixunsdfsi";
	} else if (p->n_op == SCONV && p->n_type == INT) {
		if (l->n_type == FLOAT) ch = "fixsfsi";
		else if (l->n_type == DOUBLE) ch = "fixdfsi";
		else if (l->n_type == LDOUBLE) ch = "fixdfsi";
	} else if (p->n_op == SCONV && p->n_type == UNSIGNED) {
		if (l->n_type == FLOAT) ch = "fixunssfsi";
		else if (l->n_type == DOUBLE) ch = "fixunsdfsi";
		else if (l->n_type == LDOUBLE) ch = "fixunsdfsi";
	}

	if (ch == NULL) comperr("ZF: op=0x%x (%d)\n", p->n_op, p->n_op);

	if (p->n_op == SCONV) {
		if (l->n_type == FLOAT) {
			printf("\tmfc1 %s,", rnames[A0]);
			adrput(stdout, l);
			printf("\n\tnop\n");
		}  else if (l->n_type == DOUBLE || l->n_type == LDOUBLE) {
			printf("\tmfc1 %s,", rnames[A1]);
			upput(l, 0);
			printf("\n\tnop\n");
			printf("\tmfc1 %s,", rnames[A0]);
			adrput(stdout, l);
			printf("\n\tnop\n");
		}
	} else {
		comperr("ZF: incomplete softfloat - put args in registers");
	}

	printf("\tjal __%s\t# softfloat operation\n", exname(ch));
	printf("\tnop\n");

	if (p->n_op >= EQ && p->n_op <= GT)
		printf("\tcmp %s,0\n", rnames[V0]);
}

/*
 * http://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html#Integer-library-routines
 */
static void
emulop(NODE *p)
{
	char *ch = NULL;

	if (p->n_op == LS && DEUNSIGN(p->n_type) == LONGLONG) ch = "ashldi3";
	else if (p->n_op == LS && (DEUNSIGN(p->n_type) == LONG ||
	    DEUNSIGN(p->n_type) == INT))
		ch = "ashlsi3";

	else if (p->n_op == RS && p->n_type == ULONGLONG) ch = "lshrdi3";
	else if (p->n_op == RS && (p->n_type == ULONG || p->n_type == INT))
		ch = "lshrsi3";

	else if (p->n_op == RS && p->n_type == LONGLONG) ch = "ashrdi3";
	else if (p->n_op == RS && (p->n_type == LONG || p->n_type == INT))
		ch = "ashrsi3";
	
	else if (p->n_op == DIV && p->n_type == LONGLONG) ch = "divdi3";
	else if (p->n_op == DIV && (p->n_type == LONG || p->n_type == INT))
		ch = "divsi3";

	else if (p->n_op == DIV && p->n_type == ULONGLONG) ch = "udivdi3";
	else if (p->n_op == DIV && (p->n_type == ULONG ||
	    p->n_type == UNSIGNED))
		ch = "udivsi3";

	else if (p->n_op == MOD && p->n_type == LONGLONG) ch = "moddi3";
	else if (p->n_op == MOD && (p->n_type == LONG || p->n_type == INT))
		ch = "modsi3";

	else if (p->n_op == MOD && p->n_type == ULONGLONG) ch = "umoddi3";
	else if (p->n_op == MOD && (p->n_type == ULONG ||
	    p->n_type == UNSIGNED))
		ch = "umodsi3";

	else if (p->n_op == MUL && p->n_type == LONGLONG) ch = "muldi3";
	else if (p->n_op == MUL && (p->n_type == LONG || p->n_type == INT))
		ch = "mulsi3";

	else if (p->n_op == UMINUS && p->n_type == LONGLONG) ch = "negdi2";
	else if (p->n_op == UMINUS && p->n_type == LONG) ch = "negsi2";

	else ch = 0, comperr("ZE");
	printf("\tsubu %s,%s,16\n", rnames[SP], rnames[SP]);
	printf("\tjal __%s\t# emulated operation\n", exname(ch));
	printf("\tnop\n");
	printf("\taddiu %s,%s,16\n", rnames[SP], rnames[SP]);
}

/*
 * Emit code to compare two longlong numbers.
 */
static void
twollcomp(NODE *p)
{
	int o = p->n_op;
	int s = getlab2();
	int e = p->n_label;
	int cb1, cb2;

	if (o >= ULE)
		o -= (ULE-LE);
	switch (o) {
	case NE:
		cb1 = 0;
		cb2 = NE;
		break;
	case EQ:
		cb1 = NE;
		cb2 = 0;
		break;
	case LE:
	case LT:
		cb1 = GT;
		cb2 = LT;
		break;
	case GE:
	case GT:
		cb1 = LT;
		cb2 = GT;
		break;
	
	default:
		cb1 = cb2 = 0; /* XXX gcc */
	}
	if (p->n_op >= ULE)
		cb1 += 4, cb2 += 4;
	expand(p, 0, "\tsub A1,UL,UR\t# compare 64-bit values (upper)\n");
	if (cb1) {
		printf("\t");
		hopcode(' ', cb1);
		expand(p, 0, "A1");
		printf("," LABFMT "\n", s);
		printf("\tnop\n");
	}
	if (cb2) {
		printf("\t");
		hopcode(' ', cb2);
		expand(p, 0, "A1");
		printf("," LABFMT "\n", e);
		printf("\tnop\n");
	}
	expand(p, 0, "\tsub A1,AL,AR\t# (and lower)\n");
	printf("\t");
	hopcode(' ', o);
	expand(p, 0, "A1");
	printf("," LABFMT "\n", e);
	printf("\tnop\n");
	deflab(s);
}

static void
fpcmpops(NODE *p)
{
	NODE *l = p->n_left;

	switch (p->n_op) {
	case EQ:
		if (l->n_type == FLOAT)
			expand(p, 0, "\tc.eq.s AL,AR\n");
		else
			expand(p, 0, "\tc.eq.d AL,AR\n");
		expand(p, 0, "\tnop\n\tbc1t LC\n");
		break;
	case NE:
		if (l->n_type == FLOAT)
			expand(p, 0, "\tc.eq.s AL,AR\n");
		else
			expand(p, 0, "\tc.eq.d AL,AR\n");
		expand(p, 0, "\tnop\n\tbc1f LC\n");
		break;
	case LT:
		if (l->n_type == FLOAT)
			expand(p, 0, "\tc.lt.s AL,AR\n");
		else
			expand(p, 0, "\tc.lt.d AL,AR\n");
		expand(p, 0, "\tnop\n\tbc1t LC\n");
		break;
	case GE:
		if (l->n_type == FLOAT)
			expand(p, 0, "\tc.lt.s AL,AR\n");
		else
			expand(p, 0, "\tc.lt.d AL,AR\n");
		expand(p, 0, "\tnop\n\tbc1f LC\n");
		break;
	case LE:
		if (l->n_type == FLOAT)
			expand(p, 0, "\tc.le.s AL,AR\n");
		else
			expand(p, 0, "\tc.le.d AL,AR\n");
		expand(p, 0, "\tnop\n\tbc1t LC\n");
		break;
	case GT:
		if (l->n_type == FLOAT)
			expand(p, 0, "\tc.le.s AL,AR\n");
		else
			expand(p, 0, "\tc.le.d AL,AR\n");
		expand(p, 0, "\tnop\n\tbc1f LC\n");
		break;
	}
	printf("\tnop\n\tnop\n");
}

void
zzzcode(NODE * p, int c)
{
	int sz;

	switch (c) {

	case 'C':	/* remove arguments from stack after subroutine call */
		sz = p->n_qual > 16 ? p->n_qual : 16;
		printf("\taddiu %s,%s,%d\n", rnames[SP], rnames[SP], sz);
		break;

	case 'D':	/* long long comparison */
		twollcomp(p);
		break;

	case 'E':	/* emit emulated ops */
		emulop(p);
		break;

	case 'F':	/* emit emulate floating point ops */
		fpemulop(p);
		break;

	case 'G':	/* emit hardware floating-point compare op */
		fpcmpops(p);
		break;

	case 'H':	/* structure argument */
		starg(p);
		break;

	case 'I':		/* high part of init constant */
		if (p->n_name[0] != '\0')
			comperr("named highword");
		printf(CONFMT, (getlval(p) >> 32) & 0xffffffff);
		break;

        case 'O': /* 64-bit left and right shift operators */
		shiftop(p);
		break;

	case 'Q':		/* emit struct assign */
		stasg(p);
		break;

	default:
		comperr("zzzcode %c", c);
	}
}

/* ARGSUSED */
int
rewfld(NODE * p)
{
	return (1);
}

int
fldexpand(NODE *p, int cookie, char **cp)
{
        CONSZ val;

        if (p->n_op == ASSIGN)
                p = p->n_left;
        switch (**cp) {
        case 'S':
                printf("%d", UPKFSZ(p->n_rval));
                break;
        case 'H':
                printf("%d", UPKFOFF(p->n_rval));
                break;
        case 'M':
        case 'N':
                val = (CONSZ)1 << UPKFSZ(p->n_rval);
                --val;
                val <<= UPKFOFF(p->n_rval);
                printf("0x%llx", (**cp == 'M' ? val : ~val)  & 0xffffffff);
                break;
        default:
                comperr("fldexpand");
        }
        return 1;
}

/*
 * Does the bitfield shape match?
 */
int
flshape(NODE * p)
{
	int o = p->n_op;

	if (o == OREG || o == REG || o == NAME)
		return SRDIR;	/* Direct match */
	if (o == UMUL && shumul(p->n_left, SOREG))
		return SROREG;	/* Convert into oreg */
	return SRREG;		/* put it into a register */
}

/* INTEMP shapes must not contain any temporary registers */
/* XXX should this go away now? */
int
shtemp(NODE * p)
{
	return 0;
#if 0
	int r;

	if (p->n_op == STARG)
		p = p->n_left;

	switch (p->n_op) {
	case REG:
		return (!istreg(p->n_rval));

	case OREG:
		r = p->n_rval;
		if (R2TEST(r)) {
			if (istreg(R2UPK1(r)))
				return (0);
			r = R2UPK2(r);
		}
		return (!istreg(r));

	case UMUL:
		p = p->n_left;
		return (p->n_op != UMUL && shtemp(p));
	}

	if (optype(p->n_op) != LTYPE)
		return (0);
	return (1);
#endif
}

void
adrcon(CONSZ val)
{
	printf(CONFMT, val);
}

void
conput(FILE *fp, NODE *p)
{
	int val = getlval(p);

	switch (p->n_op) {
	case ICON:
		if (p->n_name[0] != '\0') {
			fprintf(fp, "%s", p->n_name);
			if (getlval(p))
				fprintf(fp, "+%d", val);
		} else
			fprintf(fp, "%d", val);
		return;

	default:
		comperr("illegal conput");
	}
}

/* ARGSUSED */
void
insput(NODE * p)
{
	comperr("insput");
}

/*
 * Print lower or upper name of 64-bit register.
 */
static void
print_reg64name(FILE *fp, int rval, int hi)
{
        int off = 4 * (hi != 0);
	char *regname = rnames[rval];

        fprintf(fp, "%c%c",
                 regname[off],
                 regname[off + 1]);
        if (regname[off + 2] != '!')
                fputc(regname[off + 2], fp);
        if (regname[off + 3] != '!')
                fputc(regname[off + 3], fp);
}

/*
 * Write out the upper address, like the upper register of a 2-register
 * reference, or the next memory location.
 */
void
upput(NODE * p, int size)
{

	size /= SZCHAR;
	switch (p->n_op) {
	case REG:
		if (GCLASS(p->n_rval) == CLASSB || GCLASS(p->n_rval) == CLASSC)
			print_reg64name(stdout, p->n_rval, 1);
		else
			printf("%s", rnames[p->n_rval]);
		break;

	case NAME:
	case OREG:
		setlval(p, getlval(p) + size);
		adrput(stdout, p);
		setlval(p, getlval(p) - size);
		break;
	case ICON:
		printf(CONFMT, getlval(p) >> 32);
		break;
	default:
		comperr("upput bad op %d size %d", p->n_op, size);
	}
}

void
adrput(FILE * io, NODE * p)
{
	/* output an address, with offsets, from p */

	if (p->n_op == FLD)
		p = p->n_left;

	switch (p->n_op) {

	case NAME:
		if (p->n_name[0] != '\0')
			fputs(p->n_name, io);
		if (getlval(p) != 0)
			fprintf(io, "+" CONFMT, getlval(p));
		return;

	case OREG:
		if (getlval(p))
			fprintf(io, "%d", (int) getlval(p));
		fprintf(io, "(%s)", rnames[p->n_rval]);
		return;

	case ICON:
		/* addressable value of the constant */
		conput(io, p);
		return;

	case REG:
		if (GCLASS(p->n_rval) == CLASSB || GCLASS(p->n_rval) == CLASSC)
			print_reg64name(io, p->n_rval, 0);
		else
			fputs(rnames[p->n_rval], io);
		return;

	default:
		comperr("illegal address, op %d, node %p", p->n_op, p);
		return;

	}
}

/* printf conditional and unconditional branches */
void
cbgen(int o, int lab)
{
}

void
myreader(struct interpass * ipole)
{
}

#if 0
/*
 *  Calculate the stack size for arguments
 */
static int stacksize;

static void
calcstacksize(NODE *p, void *arg)
{
	int sz;

	printf("op=%d\n", p->n_op);

	if (p->n_op != CALL && p->n_op != STCALL)
		return;

	sz = argsiz(p->n_right);
	if (sz > stacksize)
		stacksize = sz;

#ifdef PCC_DEBUG
	if (x2debug)
		printf("stacksize: %d\n", stacksize);
#endif
}
#endif

/*
 * If we're big endian, then all OREG loads of a type
 * larger than the destination, must have the
 * offset changed to point to the correct bytes in memory.
 */
static void
offchg(NODE *p, void *arg)
{
	NODE *l;

	if (p->n_op != SCONV)
		return;

	l = p->n_left;

	if (l->n_op != OREG)
		return;

	switch (l->n_type) {
	case SHORT:
	case USHORT:
		if (DEUNSIGN(p->n_type) == CHAR)
			setlval(l, getlval(l) + 1);
		break;
	case LONG:
	case ULONG:
	case INT:
	case UNSIGNED:
		if (DEUNSIGN(p->n_type) == CHAR)
			setlval(l, getlval(l + 3));
		else if (DEUNSIGN(p->n_type) == SHORT)
			setlval(l, getlval(l + 2));
		break;
	case LONGLONG:
	case ULONGLONG:
		if (DEUNSIGN(p->n_type) == CHAR)
			setlval(l, getlval(l + 7));
		else if (DEUNSIGN(p->n_type) == SHORT)
			setlval(l, getlval(l + 6));
		else if (DEUNSIGN(p->n_type) == INT ||
		    DEUNSIGN(p->n_type) == LONG)
			setlval(l, getlval(l + 4));
		break;
	default:
		comperr("offchg: unknown type");
		break;
	}
}

/*
 * Remove some PCONVs after OREGs are created.
 */
static void
pconv2(NODE * p, void *arg)
{
	NODE *q;

	if (p->n_op == PLUS) {
		if (p->n_type == (PTR | SHORT) || p->n_type == (PTR | USHORT)) {
			if (p->n_right->n_op != ICON)
				return;
			if (p->n_left->n_op != PCONV)
				return;
			if (p->n_left->n_left->n_op != OREG)
				return;
			q = p->n_left->n_left;
			nfree(p->n_left);
			p->n_left = q;
			/*
		         * This will be converted to another OREG later.
		         */
		}
	}
}

void
mycanon(NODE * p)
{
	walkf(p, pconv2, 0);
}

void
myoptim(struct interpass * ipole)
{
	struct interpass *ip;

#ifdef PCC_DEBUG
	if (x2debug)
		printf("myoptim:\n");
#endif

#if 0
	stacksize = 0;
#endif

	DLIST_FOREACH(ip, ipole, qelem) {
		if (ip->type != IP_NODE)
			continue;
		if (bigendian)
			walkf(ip->ip_node, offchg, 0);
#if 0
		walkf(ip->ip_node, calcstacksize, 0);
#endif
	}
}

/*
 * Move data between registers.  While basic registers aren't a problem,
 * we have to handle the special case of overlapping composite registers.
 */
void
rmove(int s, int d, TWORD t)
{
        switch (t) {
        case LONGLONG:
        case ULONGLONG:
                if (s == d+1) {
                        /* dh = sl, copy low word first */
                        printf("\tmove ");
			print_reg64name(stdout, d, 0);
			printf(",");
			print_reg64name(stdout, s, 0);
			printf("\t# 64-bit rmove\n");
                        printf("\tmove ");
			print_reg64name(stdout, d, 1); 
			printf(",");
			print_reg64name(stdout, s, 1);
			printf("\n");
                } else {
                        /* copy high word first */
                        printf("\tmove ");
			print_reg64name(stdout, d, 1);
			printf(",");
			print_reg64name(stdout, s, 1);
			printf(" # 64-bit rmove\n");
                        printf("\tmove ");
			print_reg64name(stdout, d, 0);
			printf(",");
			print_reg64name(stdout, s, 0);
			printf("\n");
                }
                break;
	case FLOAT:
	case DOUBLE:
        case LDOUBLE:
		if (t == FLOAT)
			printf("\tmov.s ");
		else
			printf("\tmov.d ");
		print_reg64name(stdout, d, 0);
		printf(",");
		print_reg64name(stdout, s, 0);
		printf("\t# float/double rmove\n");
                break;
        default:
                printf("\tmove %s,%s\t# default rmove\n", rnames[d], rnames[s]);
        }
}


/*
 * For class c, find worst-case displacement of the number of
 * registers in the array r[] indexed by class.
 *
 * On MIPS, we have:
 *
 * 32 32-bit registers (8 reserved)
 * 26 64-bit pseudo registers (1 unavailable)
 * 16 floating-point register pairs
 */
int
COLORMAP(int c, int *r)
{
	int num = 0;

        switch (c) {
        case CLASSA:
                num += r[CLASSA];
                num += 2*r[CLASSB];
                return num < 24;
        case CLASSB:
                num += 2*r[CLASSB];
                num += r[CLASSA];
                return num < 25;
	case CLASSC:
		num += r[CLASSC];
		return num < 6;
        }
	comperr("COLORMAP");
        return 0; /* XXX gcc */
}

/*
 * Return a class suitable for a specific type.
 */
int
gclass(TWORD t)
{
	if (t == LONGLONG || t == ULONGLONG)
		return CLASSB;
	if (t >= FLOAT && t <= LDOUBLE)
		return CLASSC;
	return CLASSA;
}

/*
 * Calculate argument sizes.
 */
void
lastcall(NODE *p)
{
	int sz;

#ifdef PCC_DEBUG
	if (x2debug)
		printf("lastcall:\n");
#endif

	p->n_qual = 0;
	if (p->n_op != CALL && p->n_op != FORTCALL && p->n_op != STCALL)
		return;

	sz = argsiz(p->n_right);

	if ((sz > 4*nargregs) && (sz & 7) != 0) {
		printf("\tsubu %s,%s,4\t# align stack\n",
		    rnames[SP], rnames[SP]);
		sz += 4;
		assert((sz & 7) == 0);
	}

	p->n_qual = sz; /* XXX */
}

static int
argsiz(NODE *p)
{
	TWORD t;
	int size = 0;
	int sz = 0;

	if (p->n_op == CM) {
		size = argsiz(p->n_left);
		p = p->n_right;
	}

	t = p->n_type;
	if (t < LONGLONG || t > BTMASK)
		sz = 4;
	else if (DEUNSIGN(t) == LONGLONG)
		sz = 8;
	else if (t == DOUBLE || t == LDOUBLE)
		sz = 8;
	else if (t == FLOAT)
		sz = 4;
	else if (t == STRTY || t == UNIONTY)
		sz = attr_find(p->n_ap, ATTR_P2STRUCT)->iarg(0);

	if (p->n_type == STRTY || p->n_type == UNIONTY) {
		return (size + sz);
	}

	/* alignment */
	if (sz == 8 && (size & 7) != 0)
		sz += 4;

//	printf("size=%d, sz=%d -> %d\n", size, sz, size + sz);
	return (size + sz);
}

/*
 * Special shapes.
 */
int
special(NODE *p, int shape)
{
	int o = p->n_op;

	if (o != ICON || p->n_name[0] != 0)
		return SRNOPE;

	switch(shape) {
	case SPCON:
		if ((getlval(p) & ~0xffff) == 0)
			return SRDIR;
		break;
	}

	return SRNOPE;
}

/*
 * Target-dependent command-line options.
 */
void
mflags(char *str)
{
	if (strcasecmp(str, "big-endian") == 0) {
		bigendian = 1;
	} else if (strcasecmp(str, "little-endian") == 0) {
		bigendian = 0;
	} else {
		fprintf(stderr, "unknown m option '%s'\n", str);
		exit(1);
	}

#if 0
	 else if (strcasecmp(str, "ips2")) {
	} else if (strcasecmp(str, "ips2")) {
	} else if (strcasecmp(str, "ips3")) {
	} else if (strcasecmp(str, "ips4")) {
	} else if (strcasecmp(str, "hard-float")) {
	} else if (strcasecmp(str, "soft-float")) {
	} else if (strcasecmp(str, "abi=32")) {
		nargregs = MIPS_O32_NARGREGS;
	} else if (strcasecmp(str, "abi=n32")) {
		nargregs = MIPS_N32_NARGREGS;
	} else if (strcasecmp(str, "abi=64")) {
		nargregs = MIPS_N32_NARGREGS;
	}
#endif
}
/*
 * Do something target-dependent for xasm arguments.
 * Supposed to find target-specific constraints and rewrite them.
 */
int
myxasm(struct interpass *ip, NODE *p)
{
	return 0;
}
