/*      $Id: local2.c,v 1.34 2008/12/14 21:16:58 ragge Exp $    */
/*
 * Copyright (c) 2007 Gregory McGarry (g.mcgarry@ieee.org).
 * 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.
 */

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

#include "pass2.h"

extern void defalign(int);

#define	exname(x) x

char *rnames[] = {
	"r0", "r1", "r2", "r3","r4","r5", "r6", "r7",
	"r8", "r9", "r10", "fp", "ip", "sp", "lr", "pc",
	"r0r1", "r1r2", "r2r3", "r3r4", "r4r5", "r5r6",
	"r6r7", "r7r8", "r8r9", "r9r10",
	"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
};

/*
 * Handling of integer constants.  We have 8 bits + an even
 * number of rotates available as a simple immediate.
 * If a constant isn't trivially representable, use an ldr
 * and a subsequent sequence of orr operations.
 */

static int
trepresent(const unsigned int val)
{
	int i;
#define rotate_left(v, n) (v << n | v >> (32 - n))

	for (i = 0; i < 32; i += 2)
		if (rotate_left(val, i) <= 0xff)
			return 1;
	return 0;
}

/*
 * Return values are:
 * 0 - output constant as is (should be covered by trepresent() above)
 * 1 - 4 generate 1-4 instructions as needed.
 */
static int
encode_constant(int constant, int *values)
{
	int tmp = constant;
	int i = 0;
	int first_bit, value;

	while (tmp) {
		first_bit = ffs(tmp);
		first_bit -= 1; /* ffs indexes from 1, not 0 */
		first_bit &= ~1; /* must use even bit offsets */

		value = tmp & (0xff << first_bit);
		values[i++] = value;
		tmp &= ~value;
	}
	return i;
}

#if 0
static void
load_constant(NODE *p)
{
	int v = p->n_lval & 0xffffffff;
	int reg = DECRA(p->n_reg, 1);

	load_constant_into_reg(reg, v);
}
#endif

static void
load_constant_into_reg(int reg, int v)
{
	if (trepresent(v))
		printf("\tmov %s,#%d\n", rnames[reg], v);
	else if (trepresent(-v))
		printf("\tmvn %s,#%d\n", rnames[reg], -v);
	else {
		int vals[4], nc, i;

		nc = encode_constant(v, vals);
		for (i = 0; i < nc; i++) {
			if (i == 0) {
				printf("\tmov %s,#%d" COM "load constant %d\n",
				    rnames[reg], vals[i], v);
			} else {
				printf("\torr %s,%s,#%d\n",	
				    rnames[reg], rnames[reg], vals[i]);
			}
		}
	}
}

static TWORD ftype;

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

#ifdef PCC_DEBUG
	if (x2debug)
		printf("offcalc: p2maxautooff=%d\n", p2maxautooff);
#endif

	addto = p2maxautooff;

#if 0
	addto += 7;
	addto &= ~7;
#endif

#ifdef PCC_DEBUG
	if (x2debug)
		printf("offcalc: addto=%d\n", addto);
#endif

	addto -= AUTOINIT / SZCHAR;

	return addto;
}

void
prologue(struct interpass_prolog *ipp)
{
	int addto;
	int vals[4], nc, i;

#ifdef PCC_DEBUG
	if (x2debug)
		printf("prologue: type=%d, lineno=%d, name=%s, vis=%d, ipptype=%d, regs=0x%x, autos=%d, tmpnum=%d, lblnum=%d\n",
			ipp->ipp_ip.type,
			ipp->ipp_ip.lineno,
			ipp->ipp_name,
			ipp->ipp_vis,
			ipp->ipp_type,
			ipp->ipp_regs[0],
			ipp->ipp_autos,
			ipp->ip_tmpnum,
			ipp->ip_lblnum);
#endif

	ftype = ipp->ipp_type;

#if 0
	printf("\t.align 2\n");
	if (ipp->ipp_vis)
		printf("\t.global %s\n", exname(ipp->ipp_name));
	printf("\t.type %s,%%function\n", exname(ipp->ipp_name));
#endif
	printf("%s:\n", exname(ipp->ipp_name));

	/*
	 * We here know what register to save and how much to 
	 * add to the stack.
	 */
	addto = offcalc(ipp);

	printf("\tsub %s,%s,#%d\n", rnames[SP], rnames[SP], 16);
	printf("\tmov %s,%s\n", rnames[IP], rnames[SP]);
	printf("\tstmfd %s!,{%s,%s,%s,%s}\n", rnames[SP], rnames[FP],
	    rnames[IP], rnames[LR], rnames[PC]);
	printf("\tsub %s,%s,#4\n", rnames[FP], rnames[IP]);

	if (addto == 0)
		return;

	if (trepresent(addto)) {
		printf("\tsub %s,%s,#%d\n", rnames[SP], rnames[SP], addto);
	} else {
		nc = encode_constant(addto, vals);
		for (i = 0; i < nc; i++)
			printf("\tsub %s,%s,#%d\n",
			    rnames[SP], rnames[SP], vals[i]);
	}
}

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

	/* struct return needs special treatment */
	if (ftype == STRTY || ftype == UNIONTY) {
		assert(0);
	} else {
		printf("\tldmea %s,{%s,%s,%s}\n", rnames[FP], rnames[FP],
		    rnames[SP], rnames[PC]);
		printf("\tadd %s,%s,#%d\n", rnames[SP], rnames[SP], 16);
	}
	printf("\t.size %s,.-%s\n", exname(ipp->ipp_name),
	    exname(ipp->ipp_name));
}


/*
 * these mnemonics match the order of the preprocessor decls
 * EQ, NE, LE, LT, GE, GT, ULE, ULT, UGE, UGT
 */

static char *
ccbranches[] = {
	"beq",		/* branch if equal */
	"bne",		/* branch if not-equal */
	"ble",		/* branch if less-than-or-equal */
	"blt",		/* branch if less-than */
	"bge",		/* branch if greater-than-or-equal */
	"bgt",		/* branch if greater-than */
	/* what should these be ? */
	"bls",		/* branch if lower-than-or-same */
	"blo",		/* branch if lower-than */
	"bhs",		/* branch if higher-than-or-same */
	"bhi",		/* branch if higher-than */
};

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

	switch (o) {
	case PLUS:
		str = "add";
		break;
	case MINUS:
		str = "sub";
		break;
	case AND:
		str = "and";
		break;
	case OR:
		str = "orr";
		break;
	case ER:
		str = "eor";
		break;
	default:
		comperr("hopcode2: %d", o);
		str = 0; /* XXX gcc */
	}
	printf("%s%c", str, f);
}

/*
 * Return type size in bytes.  Used by R2REGS, arg 2 to offset().
 */
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;
		}
}

/*
 * 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, "\tcmp UR,UL" COM "compare 64-bit values (upper)\n");
	if (cb1) cbgen(cb1, s);
	if (cb2) cbgen(cb2, e);
	expand(p, 0, "\tcmp AR,AL" COM "(and lower)\n");
	cbgen(p->n_op, e);
	deflab(s);
}

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

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

	if (features(FEATURE_BIGENDIAN))
		shft = SZINT - UPKFSZ(p->n_rval) - UPKFOFF(p->n_rval);
	else
		shft = UPKFOFF(p->n_rval);

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


/*
 * Structure assignment.
 */
static void
stasg(NODE *p)
{
	NODE *l = p->n_left;
	int val = l->n_lval;

	/* R0 = dest, R1 = src, R2 = len */
	load_constant_into_reg(R2, p->n_stsize);
	if (l->n_op == OREG) {
		if (R2TEST(regno(l))) {
			int r = regno(l);
			printf("\tadd %s,%s,lsl #%d\n",
			    rnames[R0], rnames[R2UPK2(r)], R2UPK3(r));
			printf("\tadd %s,%s,%s\n", rnames[R0], rnames[R0],
			    rnames[R2UPK1(r)]);
		} else  {
			if (trepresent(val)) {
				printf("\tadd %s,%s,#%d\n",
				    rnames[R0], rnames[regno(l)], val);
			} else {
				load_constant_into_reg(R0, val);
				printf("\tadd %s,%s,%s\n", rnames[R0],
				    rnames[R0], rnames[regno(l)]);
			}
		}
	} else if (l->n_op == NAME) {
		cerror("not implemented");
	}

	printf("\tbl %s\n", exname("memcpy"));
}

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

	if (p->n_op == LS && r->n_op == ICON && r->n_lval < 32) {
		expand(p, INBREG, "\tmov A1,AL,lsr ");
		printf(CONFMT COM "64-bit left-shift\n", 32 - r->n_lval);
		expand(p, INBREG, "\tmov U1,UL,asl AR\n");
		expand(p, INBREG, "\torr U1,U1,A1\n");
		expand(p, INBREG, "\tmov A1,AL,asl AR\n");
	} else if (p->n_op == LS && r->n_op == ICON && r->n_lval < 64) {
		expand(p, INBREG, "\tmov A1,#0" COM "64-bit left-shift\n");
		expand(p, INBREG, "\tmov U1,AL");
		if (r->n_lval - 32 != 0)
			printf(",asl " CONFMT, r->n_lval - 32);
		printf("\n");
	} else if (p->n_op == LS && r->n_op == ICON) {
		expand(p, INBREG, "\tmov A1,#0" COM "64-bit left-shift\n");
		expand(p, INBREG, "\tmov U1,#0\n");
	} else if (p->n_op == RS && r->n_op == ICON && r->n_lval < 32) {
		expand(p, INBREG, "\tmov U1,UL,asl ");
		printf(CONFMT COM "64-bit right-shift\n", 32 - r->n_lval);
		expand(p, INBREG, "\tmov A1,AL,lsr AR\n");
		expand(p, INBREG, "\torr A1,A1,U1\n");
		if (ty == LONGLONG)
			expand(p, INBREG, "\tmov U1,UL,asr AR\n");
		else
			expand(p, INBREG, "\tmov U1,UL,lsr AR\n");
	} else if (p->n_op == RS && r->n_op == ICON && r->n_lval < 64) {
		if (ty == LONGLONG) {
			expand(p, INBREG, "\tmvn U1,#1" COM "64-bit right-shift\n");
			expand(p, INBREG, "\tmov A1,UL");
			shifttype = "asr";
		}else {
			expand(p, INBREG, "\tmov U1,#0" COM "64-bit right-shift\n");
			expand(p, INBREG, "\tmov A1,UL");
			shifttype = "lsr";
		}
		if (r->n_lval - 32 != 0)
			printf(",%s " CONFMT, shifttype, r->n_lval - 32);
		printf("\n");
	} else if (p->n_op == RS && r->n_op == ICON) {
		expand(p, INBREG, "\tmov A1,#0" COM "64-bit right-shift\n");
		expand(p, INBREG, "\tmov U1,#0\n");
	}
}

/*
 * http://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html#Soft-float-library-routines
 */
static void
fpemul(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 = "adddf3";

	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 = "subdf3";

	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 = "muldf3";

	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 = "divdf3";

	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 = "negdf2";

	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 = "eqdf2";

	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 = "nedf2";

	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 = "gedf2";

	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 = "ledf2";

	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 = "gtdf2";

	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 = "ltdf2";

	else if (p->n_op == SCONV && p->n_type == FLOAT) {
		if (l->n_type == DOUBLE) ch = "truncdfsf2";
		else if (l->n_type == LDOUBLE) ch = "truncdfsf2";
		else if (l->n_type == ULONGLONG) ch = "floatunsdisf";
		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 = "extendsfdf2";
		else if (l->n_type == DOUBLE) ch = "extenddftd2";
		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 == 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 = "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 == 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);

	printf("\tbl __%s" COM "softfloat operation\n", exname(ch));

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


/*
 * http://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html#Integer-library-routines
 */

static void
emul(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) ch = "ashlsi3";
	else if (p->n_op == LS && 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) ch = "lshrsi3";
	else if (p->n_op == RS && p->n_type == UNSIGNED) ch = "lshrsi3";

	else if (p->n_op == RS && p->n_type == LONGLONG) ch = "ashrdi3";
	else if (p->n_op == RS && p->n_type == LONG) ch = "ashrsi3";
	else if (p->n_op == RS && 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) ch = "divsi3";
	else if (p->n_op == DIV && 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) ch = "udivsi3";
	else if (p->n_op == DIV && 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) ch = "modsi3";
	else if (p->n_op == MOD && 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) ch = "umodsi3";
	else if (p->n_op == MOD && 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) ch = "mulsi3";
	else if (p->n_op == MUL && 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 if (p->n_op == UMINUS && p->n_type == INT) ch = "negsi2";

	else ch = 0, comperr("ZE");
	printf("\tbl __%s" COM "emulated operation\n", exname(ch));
}

static void
halfword(NODE *p)
{
        NODE *r = getlr(p, 'R');
        NODE *l = getlr(p, 'L');
	int idx0 = 0, idx1 = 1;

	if (features(FEATURE_BIGENDIAN)) {
		idx0 = 1;
		idx1 = 0;
	}

	if (p->n_op == ASSIGN && r->n_op == OREG) {
                /* load */
                expand(p, 0, "\tldrb A1,");
                printf("[%s," CONFMT "]\n", rnames[r->n_rval], r->n_lval+idx0);
                expand(p, 0, "\tldrb AL,");
                printf("[%s," CONFMT "]\n", rnames[r->n_rval], r->n_lval+idx1);
                expand(p, 0, "\torr AL,A1,AL,asl #8\n");
        } else if (p->n_op == ASSIGN && l->n_op == OREG) {
                /* store */
                expand(p, 0, "\tstrb AR,");
                printf("[%s," CONFMT "]\n", rnames[l->n_rval], l->n_lval+idx0);
                expand(p, 0, "\tmov A1,AR,asr #8\n");
                expand(p, 0, "\tstrb A1,");
                printf("[%s," CONFMT "]\n", rnames[l->n_rval], l->n_lval+idx1);
        } else if (p->n_op == SCONV || p->n_op == UMUL) {
                /* load */
                expand(p, 0, "\tldrb A1,");
                printf("[%s," CONFMT "]\n", rnames[l->n_rval], l->n_lval+idx0);
                expand(p, 0, "\tldrb A2,");
                printf("[%s," CONFMT "]\n", rnames[l->n_rval], l->n_lval+idx1);
                expand(p, 0, "\torr A1,A1,A2,asl #8\n");
        } else if (p->n_op == NAME || p->n_op == ICON || p->n_op == OREG) {
                /* load */
                expand(p, 0, "\tldrb A1,");
                printf("[%s," CONFMT "]\n", rnames[p->n_rval], p->n_lval+idx0);
                expand(p, 0, "\tldrb A2,");
                printf("[%s," CONFMT "]\n", rnames[p->n_rval], p->n_lval+idx1);
                expand(p, 0, "\torr A1,A1,A2,asl #8\n");
	} else {
		comperr("halfword");
        }
}

static void
bfext(NODE *p)
{
        int sz;

        if (ISUNSIGNED(p->n_right->n_type))
                return;
        sz = 32 - UPKFSZ(p->n_left->n_rval);

	expand(p, 0, "\tmov AD,AD,asl ");
        printf("#%d\n", sz);
	expand(p, 0, "\tmov AD,AD,asr ");
        printf("#%d\n", sz);
}

static int
argsiz(NODE *p)
{
	TWORD t = p->n_type;

	if (t < LONGLONG || t == FLOAT || t > BTMASK)
		return 4;
	if (t == LONGLONG || t == ULONGLONG)
		return 8;
	if (t == DOUBLE || t == LDOUBLE)
		return 8;
	if (t == STRTY || t == UNIONTY)
		return p->n_stsize;
	comperr("argsiz");
	return 0;
}

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

	switch (c) {

	case 'B': /* bit-field sign extension */
		bfext(p);
		break;

	case 'C':  /* remove from stack after subroutine call */
		pr = p->n_qual;
#if 0
		if (p->n_op == STCALL || p->n_op == USTCALL)
			pr += 4;
#endif
		if (p->n_op == UCALL)
			return; /* XXX remove ZC from UCALL */
		if (pr > 0)
			printf("\tadd %s,%s,#%d\n", rnames[SP], rnames[SP], pr);
		break;

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

	case 'E': /* print out emulated ops */
		emul(p);
                break;

	case 'F': /* print out emulated floating-point ops */
		fpemul(p);
		break;

	case 'H':		/* do halfword access */
		halfword(p);
		break;

	case 'I':		/* init constant */
		if (p->n_name[0] != '\0')
			comperr("named init");
		load_constant_into_reg(DECRA(p->n_reg, 1),
		    p->n_lval & 0xffffffff);
		break;

	case 'J':		/* init longlong constant */
		load_constant_into_reg(DECRA(p->n_reg, 1) - R0R1,
		    p->n_lval & 0xffffffff);
		load_constant_into_reg(DECRA(p->n_reg, 1) - R0R1 + 1,
                    (p->n_lval >> 32));
                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);
}

/*
 * 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)
{
	char *s;
	int val = p->n_lval;

	switch (p->n_op) {
	case ICON:
#if 0
		if (p->n_sp)
			printf(" [class=%d,level=%d] ", p->n_sp->sclass, p->n_sp->slevel);
#endif
#ifdef notdef	/* ICON cannot ever use sp here */
		/* If it does, it's a giant bug */
		if (p->n_sp == NULL || (
		   (p->n_sp->sclass == STATIC && p->n_sp->slevel > 0)))
			s = p->n_name;
		else
			s = exname(p->n_name);
#else
		s = p->n_name;
#endif
			
		if (*s != '\0') {
			fprintf(fp, "%s", s);
			if (val > 0)
				fprintf(fp, "+%d", val);
			else if (val < 0)
				fprintf(fp, "-%d", -val);
		} else
			fprintf(fp, CONFMT, (CONSZ)val);
		return;

	default:
		comperr("illegal conput, p %p", p);
	}
}

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

/*
 * 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:
		fprintf(stdout, "%s", rnames[p->n_rval-R0R1+1]);
		break;

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

void
adrput(FILE *io, NODE *p)
{
	int r;
	/* 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 (p->n_lval != 0)
				fprintf(io, "+%lld", p->n_lval);
		} else
			fprintf(io, CONFMT, p->n_lval);
		return;

	case OREG:
		r = p->n_rval;
                if (R2TEST(r))
			fprintf(io, "[%s, %s, lsl #%d]",
				rnames[R2UPK1(r)],
				rnames[R2UPK2(r)],
				R2UPK3(r));
		else
			fprintf(io, "[%s,#%d]", rnames[p->n_rval], (int)p->n_lval);
		return;

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

	case REG:
		switch (p->n_type) {
		case DOUBLE:
		case LDOUBLE:
			if (features(FEATURE_HARDFLOAT)) {
				fprintf(io, "%s", rnames[p->n_rval]);
				break;
			}
			/* FALLTHROUGH */
		case LONGLONG:
		case ULONGLONG:
			fprintf(stdout, "%s", rnames[p->n_rval-R0R1]);
			break;
		default:
			fprintf(io, "%s", rnames[p->n_rval]);
		}
		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)
{
	if (o < EQ || o > UGT)
		comperr("bad conditional branch: %s", opst[o]);
	printf("\t%s " LABFMT COM "conditional branch\n",
	    ccbranches[o-EQ], lab);
}

/*
 * The arm can only address 4k to get a NAME, so there must be some
 * rewriting here.  Strategy:
 * For first 1000 nodes found, print out the word directly.
 * For the following 1000 nodes, group them together in asm statements
 * and create a jump over.
 * For the last <1000 statements, print out the words last.
 */
struct addrsymb {
	SLIST_ENTRY(addrsymb) link;
	char *name;	/* symbol name */
	int num;	/* symbol offset */
	char *str;	/* replace label */
};
SLIST_HEAD(, addrsymb) aslist;
static struct interpass *ipbase;
static int prtnumber, nodcnt, notfirst;
#define	PRTLAB	".LY%d"	/* special for here */

static struct interpass *
anode(char *p)
{
	extern int thisline;
	struct interpass *ip = tmpalloc(sizeof(struct interpass));

	ip->ip_asm = p;
	ip->type = IP_ASM;
	ip->lineno = thisline;
	return ip;
}

static void
flshlab(void)
{
	struct interpass *ip;
	struct addrsymb *el;
	int lab = prtnumber++;
	char *c;

	if (SLIST_FIRST(&aslist) == NULL)
		return;

	snprintf(c = tmpalloc(32), 32, "\tb " PRTLAB "\n", lab);
	ip = anode(c);
	DLIST_INSERT_BEFORE(ipbase, ip, qelem);

	SLIST_FOREACH(el, &aslist, link) {
		/* insert each node as asm */
		int l = 32+strlen(el->name);
		c = tmpalloc(l);
		if (el->num)
			snprintf(c, l, "%s:\n\t.word %s+%d\n",
			    el->str, el->name, el->num);
		else
			snprintf(c, l, "%s:\n\t.word %s\n", el->str, el->name);
		ip = anode(c);
		DLIST_INSERT_BEFORE(ipbase, ip, qelem);
	}
	/* generate asm label */
	snprintf(c = tmpalloc(32), 32, PRTLAB ":\n", lab);
	ip = anode(c);
	DLIST_INSERT_BEFORE(ipbase, ip, qelem);
}

static void
prtaddr(NODE *p, void *arg)
{
	NODE *l = p->n_left;
	struct addrsymb *el;
	int found = 0;
	int lab;

	nodcnt++;

	if (p->n_op == ASSIGN && p->n_right->n_op == ICON &&
	    p->n_right->n_name[0] != '\0') {
		/* named constant */
		p = p->n_right;

		/* Restore addrof */
		l = mklnode(NAME, p->n_lval, 0, 0);
		l->n_name = p->n_name;
		p->n_left = l;
		p->n_op = ADDROF;
	}

	if (p->n_op != ADDROF || l->n_op != NAME)
		return;

	/* if we passed 1k nodes printout list */
	if (nodcnt > 1000) {
		if (notfirst)
			flshlab();
		SLIST_INIT(&aslist);
		notfirst = 1;
		nodcnt = 0;
	}

	/* write address to byte stream */

	SLIST_FOREACH(el, &aslist, link) {
		if (el->num == l->n_lval && el->name[0] == l->n_name[0] &&
		    strcmp(el->name, l->n_name) == 0) {
			found = 1;
			break;
		}
	}

	if (!found) {
		/* we know that this is text segment */
		lab = prtnumber++;
		if (nodcnt <= 1000 && notfirst == 0) {
			if (l->n_lval)
				printf(PRTLAB ":\n\t.word %s+%lld\n",
				    lab, l->n_name, l->n_lval);
			else
				printf(PRTLAB ":\n\t.word %s\n",
				    lab, l->n_name);
		}
		el = tmpalloc(sizeof(struct addrsymb));
		el->num = l->n_lval;
		el->name = l->n_name;
		el->str = tmpalloc(32);
		snprintf(el->str, 32, PRTLAB, lab);
		SLIST_INSERT_LAST(&aslist, el, link);
	}

	nfree(l);
	p->n_op = NAME;
	p->n_lval = 0;
	p->n_name = el->str;
}

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

	SLIST_INIT(&aslist);
	notfirst = nodcnt = 0;

	DLIST_FOREACH(ip, ipole, qelem) {
		switch (ip->type) {
		case IP_NODE:
			lineno = ip->lineno;
			ipbase = ip;
			walkf(ip->ip_node, prtaddr, 0);
			break;
		case IP_EPILOG:
			ipbase = ip;
			if (notfirst)
				flshlab();
			break;
		default:
			break;
		}
	}
	if (x2debug)
		printip(ipole);
}

/*
 * 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 *ipp)
{
}

/*
 * Register move: move contents of register 's' to register 'r'.
 */
void
rmove(int s, int d, TWORD t)
{
        switch (t) {
	case DOUBLE:
	case LDOUBLE:
		if (features(FEATURE_HARDFLOAT)) {
			printf("\tfmr %s,%s" COM "rmove\n",
				rnames[d], rnames[s]);
			break;
		}
		/* FALLTHROUGH */
        case LONGLONG:
        case ULONGLONG:
#define LONGREG(x, y) rnames[(x)-(R0R1-(y))]
                if (s == d+1) {
                        /* dh = sl, copy low word first */
                        printf("\tmov %s,%s" COM "rmove\n",
			    LONGREG(d,0), LONGREG(s,0));
                        printf("\tmov %s,%s\n",
			    LONGREG(d,1), LONGREG(s,1));
                } else {
                        /* copy high word first */
                        printf("\tmov %s,%s" COM "rmove\n",
			    LONGREG(d,1), LONGREG(s,1));
                        printf("\tmov %s,%s\n",
			    LONGREG(d,0), LONGREG(s,0));
                }
#undef LONGREG
                break;
	case FLOAT:
		if (features(FEATURE_HARDFLOAT)) {
			printf("\tmr %s,%s" COM "rmove\n",
				rnames[d], rnames[s]);
			break;
		}
		/* FALLTHROUGH */
        default:
		printf("\tmov %s,%s" COM "rmove\n", rnames[d], rnames[s]);
        }
}

/*
 * Can we assign a register from class 'c', given the set
 * of number of assigned registers in each class 'r'.
 *
 * On ARM, we have:
 *	11  CLASSA registers (32-bit hard registers)
 *	10  CLASSB registers (64-bit composite registers)
 *	8 or 32 CLASSC registers (floating-point)
 *
 *  There is a problem calculating the available composite registers
 *  (ie CLASSB).  The algorithm below assumes that given any two
 *  registers, we can make a composite register.  But this isn't true
 *  here (or with other targets), since the number of combinations
 *  of register pairs could become very large.  Additionally,
 *  having so many combinations really isn't so practical, since
 *  most register pairs cannot be used to pass function arguments.
 *  Consequently, when there is pressure composite registers,
 *  "beenhere" compilation failures are common.
 *
 *  [We need to know which registers are allocated, not simply
 *  the number in each class]
 */
int
COLORMAP(int c, int *r)
{
	int num = 0;	/* number of registers used */

#if 0
	static const char classes[] = { 'X', 'A', 'B', 'C', 'D' };
	printf("COLORMAP: requested class %c\n", classes[c]);
	printf("COLORMAP: class A: %d\n", r[CLASSA]);
	printf("COLORMAP: class B: %d\n", r[CLASSB]);
#endif

	switch (c) {
	case CLASSA:
		num += r[CLASSA];
		num += 2*r[CLASSB];
		return num < 11;
	case CLASSB:
		num += 2*r[CLASSB];
		num += r[CLASSA];
		return num < 6;  /* XXX see comments above */
	case CLASSC:
		num += r[CLASSC];
		if (features(FEATURE_FPA))
			return num < 8;
		else if (features(FEATURE_VFP))
			return num < 8;
		else
			cerror("colormap 1");
	}
	cerror("colormap 2");
	return 0; /* XXX gcc */
}

/*
 * Return a class suitable for a specific type.
 */
int
gclass(TWORD t)
{
	if (t == DOUBLE || t == LDOUBLE) {
		if (features(FEATURE_HARDFLOAT))
			return CLASSC;
		else
			return CLASSB;
	}
	if (t == FLOAT) {
		if (features(FEATURE_HARDFLOAT))
			return CLASSC;
		else
			return CLASSA;
	}
	if (DEUNSIGN(t) == LONGLONG)
		return CLASSB;
	return CLASSA;
}

int
retreg(int t)
{
	int c = gclass(t);
	if (c == CLASSB)
		return R0R1;
	else if (c == CLASSC)
		return F0;
	return R0;
}

/*
 * Calculate argument sizes.
 */
void
lastcall(NODE *p)
{
	NODE *op = p;
	int size = 0;

	p->n_qual = 0;
	if (p->n_op != CALL && p->n_op != FORTCALL && p->n_op != STCALL)
		return;
	for (p = p->n_right; p->n_op == CM; p = p->n_left)
		size += argsiz(p->n_right);
	size += argsiz(p);
	op->n_qual = size - 16; /* XXX */
}

/*
 * Special shapes.
 */
int
special(NODE *p, int shape)
{
	return SRNOPE;
}

/*
 * default to ARMv2
 */
#ifdef TARGET_BIG_ENDIAN
#define DEFAULT_FEATURES	FEATURE_BIGENDIAN | FEATURE_MUL
#else
#define DEFAULT_FEATURES	FEATURE_MUL
#endif

static int fset = DEFAULT_FEATURES;

/*
 * Target-dependent command-line options.
 */
void
mflags(char *str)
{
	if (strcasecmp(str, "little-endian") == 0) {
		fset &= ~FEATURE_BIGENDIAN;
	} else if (strcasecmp(str, "big-endian") == 0) {
		fset |= FEATURE_BIGENDIAN;
	} else if (strcasecmp(str, "fpe=fpa") == 0) {
		fset &= ~(FEATURE_VFP | FEATURE_FPA);
		fset |= FEATURE_FPA;
	} else if (strcasecmp(str, "fpe=vfp") == 0) {
		fset &= ~(FEATURE_VFP | FEATURE_FPA);
		fset |= FEATURE_VFP;
	} else if (strcasecmp(str, "soft-float") == 0) {
		fset &= ~(FEATURE_VFP | FEATURE_FPA);
	} else if (strcasecmp(str, "arch=armv1") == 0) {
		fset &= ~FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset &= ~FEATURE_MUL;
		fset &= ~FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv2") == 0) {
		fset &= ~FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset &= ~FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv2a") == 0) {
		fset &= ~FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset &= ~FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv3") == 0) {
		fset &= ~FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset &= ~FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv4") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv4t") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv4tej") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv5") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv5te") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv5tej") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset &= ~FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv6") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset |= FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv6t2") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset |= FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv6kz") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset |= FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv6k") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset |= FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else if (strcasecmp(str, "arch=armv7") == 0) {
		fset |= FEATURE_HALFWORDS;
		fset |= FEATURE_EXTEND;
		fset |= FEATURE_MUL;
		fset |= FEATURE_MULL;
	} else {
		fprintf(stderr, "unknown m option '%s'\n", str);
		exit(1);
	}
}

int
features(int mask)
{
	if (mask == FEATURE_HARDFLOAT)
		return ((fset & mask) != 0);
	return ((fset & mask) == mask);
}

/*
 * Define the current location as an internal label.
 */
void
deflab(int label)
{
	printf(LABFMT ":\n", label);
}

/*
 * 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;
}
