#include "gc.h"

static	int	resvreg[nelem(reg)];

static	void	gopcode64(int, Node*, Node*, Node*);
static	void	gori64(int, Node*, Node*, Node*);
static	void	gandi64(int, Node*, Node*, Node*);

void
ginit(void)
{
	Type *t;

	thechar = 'q';
	thestring = "power";
	exregoffset = REGEXT;
	exfregoffset = FREGEXT;
	newvlongcode = 1;
	listinit();
	nstring = 0;
	mnstring = 0;
	nrathole = 0;
	pc = 0;
	breakpc = -1;
	continpc = -1;
	cases = C;
	firstp = P;
	lastp = P;
	tfield = types[TLONG];

	typeswitch = typechlv;

	zprog.link = P;
	zprog.as = AGOK;
	zprog.reg = NREG;
	zprog.from.type = D_NONE;
	zprog.from.name = D_NONE;
	zprog.from.reg = NREG;
	zprog.from3 = zprog.from;
	zprog.to = zprog.from;

	regnode.op = OREGISTER;
	regnode.class = CEXREG;
	regnode.reg = 0;
	regnode.complex = 0;
	regnode.addable = 11;
	regnode.type = types[TLONG];

	constnode.op = OCONST;
	constnode.class = CXXX;
	constnode.complex = 0;
	constnode.addable = 20;
	constnode.type = types[TLONG];

	fconstnode.op = OCONST;
	fconstnode.class = CXXX;
	fconstnode.complex = 0;
	fconstnode.addable = 20;
	fconstnode.type = types[TDOUBLE];

	nodsafe = new(ONAME, Z, Z);
	nodsafe->sym = slookup(".safe");
	nodsafe->type = types[TINT];
	nodsafe->etype = types[TINT]->etype;
	nodsafe->class = CAUTO;
	complex(nodsafe);

	t = typ(TARRAY, types[TCHAR]);
	symrathole = slookup(".rathole");
	symrathole->class = CGLOBL;
	symrathole->type = t;

	nodrat = new(ONAME, Z, Z);
	nodrat->sym = symrathole;
	nodrat->type = types[TIND];
	nodrat->etype = TVOID;
	nodrat->class = CGLOBL;
	complex(nodrat);
	nodrat->type = t;

	com64init();

	memset(reg, 0, sizeof(reg));
	reg[REGZERO] = 1;	/* don't use */
	reg[REGTMP] = 1;
	reg[FREGCVI+NREG] = 1;
	reg[FREGZERO+NREG] = 1;
	reg[FREGHALF+NREG] = 1;
	reg[FREGONE+NREG] = 1;
	reg[FREGTWO+NREG] = 1;
	memmove(resvreg, reg, sizeof(reg));
}

void
gclean(void)
{
	int i;
	Sym *s;

	for(i=0; i<NREG; i++)
		if(reg[i] && !resvreg[i])
			diag(Z, "reg %d left allocated", i);
	for(i=NREG; i<NREG+NREG; i++)
		if(reg[i] && !resvreg[i])
			diag(Z, "freg %d left allocated", i-NREG);
	while(mnstring)
		outstring("", 1L);
	symstring->type->width = nstring;
	symrathole->type->width = nrathole;
	for(i=0; i<NHASH; i++)
	for(s = hash[i]; s != S; s = s->link) {
		if(s->type == T)
			continue;
		if(s->type->width == 0)
			continue;
		if(s->class != CGLOBL && s->class != CSTATIC)
			continue;
		if(s->type == types[TENUM])
			continue;
		gpseudo(AGLOBL, s, nodconst(s->type->width));
	}
	nextpc();
	p->as = AEND;
	outcode();
}

void
nextpc(void)
{

	p = alloc(sizeof(*p));
	*p = zprog;
	p->lineno = nearln;
	pc++;
	if(firstp == P) {
		firstp = p;
		lastp = p;
		return;
	}
	lastp->link = p;
	lastp = p;
}

void
gargs(Node *n, Node *tn1, Node *tn2)
{
	long regs;
	Node fnxargs[20], *fnxp;

	regs = cursafe;

	fnxp = fnxargs;
	garg1(n, tn1, tn2, 0, &fnxp);	/* compile fns to temps */

	curarg = 0;
	fnxp = fnxargs;
	garg1(n, tn1, tn2, 1, &fnxp);	/* compile normal args and temps */

	cursafe = regs;
}

void
garg1(Node *n, Node *tn1, Node *tn2, int f, Node **fnxp)
{
	Node nod;

	if(n == Z)
		return;
	if(n->op == OLIST) {
		garg1(n->left, tn1, tn2, f, fnxp);
		garg1(n->right, tn1, tn2, f, fnxp);
		return;
	}
	if(f == 0) {
		if(n->complex >= FNX) {
			regsalloc(*fnxp, n);
			nod = znode;
			nod.op = OAS;
			nod.left = *fnxp;
			nod.right = n;
			nod.type = n->type;
			cgen(&nod, Z);
			(*fnxp)++;
		}
		return;
	}
	if(typesuv[n->type->etype]) {
		regaalloc(tn2, n);
		if(n->complex >= FNX) {
			cgen(*fnxp, tn2);
			(*fnxp)++;
		} else
			cgen(n, tn2);
		return;
	}
	if(REGARG && curarg == 0 && typechlp[n->type->etype]) {
		regaalloc1(tn1, n);
		if(n->complex >= FNX) {
			cgen(*fnxp, tn1);
			(*fnxp)++;
		} else
			cgen(n, tn1);
		return;
	}
	if(vconst(n) == 0) {
		regaalloc(tn2, n);
		gopcode(OAS, n, Z, tn2);
		return;
	}
	regalloc(tn1, n, Z);
	if(n->complex >= FNX) {
		cgen(*fnxp, tn1);
		(*fnxp)++;
	} else
		cgen(n, tn1);
	regaalloc(tn2, n);
	gopcode(OAS, tn1, Z, tn2);
	regfree(tn1);
}

Node*
nod32const(vlong v)
{
	constnode.vconst = v & MASK(32);
	return &constnode;
}

Node*
nodconst(long v)
{
	constnode.vconst = v;
	return &constnode;
}

Node*
nodfconst(double d)
{
	fconstnode.fconst = d;
	return &fconstnode;
}

void
nodreg(Node *n, Node *nn, int reg)
{
	*n = regnode;
	n->reg = reg;
	n->type = nn->type;
	n->lineno = nn->lineno;
}

void
regret(Node *n, Node *nn)
{
	int r;

	r = REGRET;
	if(typefd[nn->type->etype])
		r = FREGRET+NREG;
	nodreg(n, nn, r);
	reg[r]++;
}

void
regalloc(Node *n, Node *tn, Node *o)
{
	int i, j;
	static int lasti;

	switch(tn->type->etype) {
	case TCHAR:
	case TUCHAR:
	case TSHORT:
	case TUSHORT:
	case TINT:
	case TUINT:
	case TLONG:
	case TULONG:
	case TIND:
		if(o != Z && o->op == OREGISTER) {
			i = o->reg;
			if(i > 0 && i < NREG)
				goto out;
		}
		j = lasti + REGRET+1;
		for(i=REGRET+1; i<NREG; i++) {
			if(j >= NREG)
				j = REGRET+1;
			if(reg[j] == 0) {
				i = j;
				goto out;
			}
			j++;
		}
		diag(tn, "out of fixed registers");
		goto err;

	case TFLOAT:
	case TDOUBLE:
		if(o != Z && o->op == OREGISTER) {
			i = o->reg;
			if(i >= NREG && i < NREG+NREG)
				goto out;
		}
		j = lasti + NREG;
		for(i=NREG; i<NREG+NREG; i++) {
			if(j >= NREG+NREG)
				j = NREG;
			if(reg[j] == 0) {
				i = j;
				goto out;
			}
			j++;
		}
		diag(tn, "out of float registers");
		goto err;

	case TVLONG:
	case TUVLONG:
		n->op = OREGPAIR;
		n->complex = 0;	/* already in registers */
		n->addable = 11;
		n->type = tn->type;
		n->lineno = nearln;
		n->left = alloc(sizeof(Node));
		n->right = alloc(sizeof(Node));
		if(o != Z && o->op == OREGPAIR) {
			regalloc(n->left, &regnode, o->left);
			regalloc(n->right, &regnode, o->right);
		} else {
			regalloc(n->left, &regnode, Z);
			regalloc(n->right, &regnode, Z);
		}
		n->right->type = types[TULONG];
		if(tn->type->etype == TUVLONG)
			n->left->type = types[TULONG];	/* TO DO: is this a bad idea? */
		return;
	}
	diag(tn, "unknown type in regalloc: %T", tn->type);
err:
	i = 0;
out:
	if(i)
		reg[i]++;
	lasti++;
	if(lasti >= 5)
		lasti = 0;
	nodreg(n, tn, i);
}

void
regialloc(Node *n, Node *tn, Node *o)
{
	Node nod;

	nod = *tn;
	nod.type = types[TIND];
	regalloc(n, &nod, o);
}

void
regfree(Node *n)
{
	int i;

	if(n->op == OREGPAIR) {
		regfree(n->left);
		regfree(n->right);
		return;
	}
	i = 0;
	if(n->op != OREGISTER && n->op != OINDREG)
		goto err;
	i = n->reg;
	if(i < 0 || i >= sizeof(reg))
		goto err;
	if(reg[i] <= 0)
		goto err;
	reg[i]--;
	return;
err:
	diag(n, "error in regfree: %d [%d]", i, reg[i]);
	prtree(n, "regfree");
}

void
regsalloc(Node *n, Node *nn)
{
	cursafe = align(cursafe+stkoff, nn->type, Aaut3)-stkoff;
	maxargsafe = maxround(maxargsafe, cursafe+curarg);
//	if(nn->type->etype == TDOUBLE || nn->type->etype == TVLONG){
//		extern int hasdoubled;
//		fprint(2, "stkoff=%ld cursafe=%ld curarg=%ld %d\n", stkoff, cursafe, curarg, hasdoubled);
//	}
	*n = *nodsafe;
	n->xoffset = -(stkoff + cursafe);
	n->type = nn->type;
	n->etype = nn->type->etype;
	n->lineno = nn->lineno;
}

void
regaalloc1(Node *n, Node *nn)
{
	nodreg(n, nn, REGARG);
	reg[REGARG]++;
	curarg = align(curarg, nn->type, Aarg1);
	curarg = align(curarg, nn->type, Aarg2);
	maxargsafe = maxround(maxargsafe, cursafe+curarg);
}

void
regaalloc(Node *n, Node *nn)
{
	curarg = align(curarg, nn->type, Aarg1);
	*n = *nn;
	n->op = OINDREG;
	n->reg = REGSP;
	n->xoffset = curarg + SZ_LONG;
	n->complex = 0;
	n->addable = 20;
	curarg = align(curarg, nn->type, Aarg2);
	maxargsafe = maxround(maxargsafe, cursafe+curarg);
}

void
regind(Node *n, Node *nn)
{

	if(n->op != OREGISTER) {
		diag(n, "regind not OREGISTER");
		return;
	}
	n->op = OINDREG;
	n->type = nn->type;
}

void
raddr(Node *n, Prog *p)
{
	Adr a;

	naddr(n, &a);
	if(R0ISZERO && a.type == D_CONST && a.offset == 0) {
		a.type = D_REG;
		a.reg = REGZERO;
	}
	if(a.type != D_REG && a.type != D_FREG) {
		if(n)
			diag(n, "bad in raddr: %O", n->op);
		else
			diag(n, "bad in raddr: <null>");
		p->reg = NREG;
	} else
		p->reg = a.reg;
}

void
naddr(Node *n, Adr *a)
{
	long v;

	a->type = D_NONE;
	if(n == Z)
		return;
	switch(n->op) {
	default:
	bad:
		diag(n, "bad in naddr: %O", n->op);
		break;

	case OREGISTER:
		a->type = D_REG;
		a->sym = S;
		a->reg = n->reg;
		if(a->reg >= NREG) {
			a->type = D_FREG;
			a->reg -= NREG;
		}
		break;

	case OIND:
		naddr(n->left, a);
		a->offset += n->xoffset;	/* little hack for reglcgenv */
		if(a->type == D_REG) {
			a->type = D_OREG;
			break;
		}
		if(a->type == D_CONST) {
			a->type = D_OREG;
			break;
		}
		goto bad;

	case OINDREG:
		a->type = D_OREG;
		a->sym = S;
		a->offset = n->xoffset;
		a->reg = n->reg;
		break;

	case ONAME:
		a->etype = n->etype;
		a->type = D_OREG;
		a->name = D_STATIC;
		a->sym = n->sym;
		a->offset = n->xoffset;
		if(n->class == CSTATIC)
			break;
		if(n->class == CEXTERN || n->class == CGLOBL) {
			a->name = D_EXTERN;
			break;
		}
		if(n->class == CAUTO) {
			a->name = D_AUTO;
			break;
		}
		if(n->class == CPARAM) {
			a->name = D_PARAM;
			break;
		}
		goto bad;

	case OCONST:
		a->sym = S;
		a->reg = NREG;
		if(typefd[n->type->etype]) {
			a->type = D_FCONST;
			a->dval = n->fconst;
		} else {
			a->type = D_CONST;
			a->offset = n->vconst;
		}
		break;

	case OADDR:
		naddr(n->left, a);
		if(a->type == D_OREG) {
			a->type = D_CONST;
			break;
		}
		goto bad;

	case OADD:
		if(n->left->op == OCONST) {
			naddr(n->left, a);
			v = a->offset;
			naddr(n->right, a);
		} else {
			naddr(n->right, a);
			v = a->offset;
			naddr(n->left, a);
		}
		a->offset += v;
		break;

	}
}

void
gloadhi(Node *f, Node *t, int c)
{
	Type *ot;

	if(f->op == OCONST){
		f = nodconst((long)(f->vconst>>32));
		if(c==1 && sconst(f) || c==2 && uconst(f)){
			if(t->op == OREGISTER)
				regfree(t);
			*t = *f;
			return;
		}
	}
	if(f->op == OREGPAIR) {
		gmove(f->left, t);
		return;
	}
	ot = f->type;
	f->type = types[TLONG];
	gmove(f, t);
	f->type = ot;
}

void
gloadlo(Node *f, Node *t, int c)
{
	Type *ot;

	if(f->op == OCONST){
		f = nodconst((long)f->vconst);
		if(c && uconst(f)){
			if(t->op == OREGISTER)
				regfree(t);
			*t = *f;
			return;
		}
	}
	if(f->op == OREGPAIR) {
		gmove(f->right, t);
		return;
	}
	ot = f->type;
	f->type = types[TLONG];
	f->xoffset += SZ_LONG;
	if(0){
		prtree(f, "gloadlo f"); prtree(t, "gloadlo t");
	}
	gmove(f, t);
	f->xoffset -= SZ_LONG;
	f->type = ot;
}

void
fop(int as, int f1, int f2, Node *t)
{
	Node nod1, nod2, nod3;

	nodreg(&nod1, t, NREG+f1);
	nodreg(&nod2, t, NREG+f2);
	regalloc(&nod3, t, t);
	gopcode(as, &nod1, &nod2, &nod3);
	gmove(&nod3, t);
	regfree(&nod3);
}

void
gmove(Node *f, Node *t)
{
	int ft, tt, a;
	Node nod, fxc0, fxc1, fxc2, fxrat;
	Prog *p1;
	double d;

	ft = f->type->etype;
	tt = t->type->etype;

	if(ft == TDOUBLE && f->op == OCONST) {
		d = f->fconst;
		if(d == 0.0) {
			a = FREGZERO;
			goto ffreg;
		}
		if(d == 0.5) {
			a = FREGHALF;
			goto ffreg;
		}
		if(d == 1.0) {
			a = FREGONE;
			goto ffreg;
		}
		if(d == 2.0) {
			a = FREGTWO;
			goto ffreg;
		}
		if(d == -.5) {
			fop(OSUB, FREGHALF, FREGZERO, t);
			return;
		}
		if(d == -1.0) {
			fop(OSUB, FREGONE, FREGZERO, t);
			return;
		}
		if(d == -2.0) {
			fop(OSUB, FREGTWO, FREGZERO, t);
			return;
		}
		if(d == 1.5) {
			fop(OADD, FREGONE, FREGHALF, t);
			return;
		}
		if(d == 2.5) {
			fop(OADD, FREGTWO, FREGHALF, t);
			return;
		}
		if(d == 3.0) {
			fop(OADD, FREGTWO, FREGONE, t);
			return;
		}
	}
	if(ft == TFLOAT && f->op == OCONST) {
		d = f->fconst;
		if(d == 0) {
			a = FREGZERO;
		ffreg:
			nodreg(&nod, f, NREG+a);
			gmove(&nod, t);
			return;
		}
	}
	if((ft == TVLONG || ft == TUVLONG) && f->op == OCONST && t->op == OREGPAIR) {
		if(align(0, types[TCHAR], Aarg1))	/* isbigendian */
			gmove(nod32const(f->vconst>>32), t->left);
		else
			gmove(nod32const(f->vconst), t->left);
		if(align(0, types[TCHAR], Aarg1))	/* isbigendian */
			gmove(nod32const(f->vconst), t->right);
		else
			gmove(nod32const(f->vconst>>32), t->right);
		return;
	}
	/*
	 * a load --
	 * put it into a register then
	 * worry what to do with it.
	 */
	if(f->op == ONAME || f->op == OINDREG || f->op == OIND) {
		switch(ft) {
		default:
			a = AMOVW;
			break;
		case TFLOAT:
			a = AFMOVS;
			break;
		case TDOUBLE:
			a = AFMOVD;
			break;
		case TCHAR:
			a = AMOVB;
			break;
		case TUCHAR:
			a = AMOVBZ;
			break;
		case TSHORT:
			a = AMOVH;
			break;
		case TUSHORT:
			a = AMOVHZ;
			break;
		}
		if(typev[ft]) {
			if(typev[tt]) {
				regalloc(&nod, f, t);
				/* low order first, because its value will be used first */
				f->xoffset += SZ_LONG;
				gins(AMOVW, f, nod.right);
				f->xoffset -= SZ_LONG;
				gins(AMOVW, f, nod.left);
			} else {
				/* assumed not float or double */
				regalloc(&nod, &regnode, t);
				f->xoffset += SZ_LONG;
				gins(AMOVW, f, &nod);
				f->xoffset -= SZ_LONG;
			}
		} else {
			regalloc(&nod, f, t);
			gins(a, f, &nod);
		}
		gmove(&nod, t);
		regfree(&nod);
		return;
	}

	/*
	 * a store --
	 * put it into a register then
	 * store it.
	 */
	if(t->op == ONAME || t->op == OINDREG || t->op == OIND) {
		switch(tt) {
		default:
			a = AMOVW;
			break;
		case TUCHAR:
			a = AMOVBZ;
			break;
		case TCHAR:
			a = AMOVB;
			break;
		case TUSHORT:
			a = AMOVHZ;
			break;
		case TSHORT:
			a = AMOVH;
			break;
		case TFLOAT:
			a = AFMOVS;
			break;
		case TDOUBLE:
			a = AFMOVD;
			break;
		}
		if(R0ISZERO && !typefd[ft] && vconst(f) == 0) {
			gins(a, f, t);
			if(typev[tt]) {
				t->xoffset += SZ_LONG;
				gins(a, f, t);
				t->xoffset -= SZ_LONG;
			}
			return;
		}
		if(ft == tt)
			regalloc(&nod, t, f);
		else
			regalloc(&nod, t, Z);
		gmove(f, &nod);
		if(typev[tt]) {
			t->xoffset += SZ_LONG;
			gins(a, nod.right, t);
			t->xoffset -= SZ_LONG;
			gins(a, nod.left, t);
		} else
			gins(a, &nod, t);
		regfree(&nod);
		return;
	}

	/*
	 * type x type cross table
	 */
	a = AGOK;
	switch(ft) {
	case TDOUBLE:
	case TFLOAT:
		switch(tt) {
		case TDOUBLE:
			a = AFMOVD;
			if(ft == TFLOAT)
				a = AFMOVS;	/* AFMOVSD */
			break;
		case TFLOAT:
			a = AFRSP;
			if(ft == TFLOAT)
				a = AFMOVS;
			break;
		case TINT:
		case TUINT:
		case TLONG:
		case TULONG:
		case TIND:
		case TSHORT:
		case TUSHORT:
		case TCHAR:
		case TUCHAR:
			/* BUG: not right for unsigned long */
			regalloc(&nod, f, Z);	/* should be type float */
			regsalloc(&fxrat, &fconstnode);
			gins(AFCTIWZ, f, &nod);
			gins(AFMOVD, &nod, &fxrat);
			regfree(&nod);
			fxrat.type = nodrat->type;
			fxrat.etype = nodrat->etype;
			fxrat.xoffset += 4;
			gins(AMOVW, &fxrat, t);
			gmove(t, t);
			return;
		}
		break;
	case TINT:
	case TUINT:
	case TLONG:
	case TULONG:
	case TIND:
		switch(tt) {
		case TDOUBLE:
		case TFLOAT:
			goto fxtofl;
		case TINT:
		case TUINT:
		case TLONG:
		case TULONG:
		case TIND:
		case TSHORT:
		case TUSHORT:
		case TCHAR:
		case TUCHAR:
			a = AMOVW;
			break;
		}
		break;
	case TSHORT:
		switch(tt) {
		case TDOUBLE:
		case TFLOAT:
			goto fxtofl;
		case TINT:
		case TUINT:
		case TLONG:
		case TULONG:
		case TIND:
			a = AMOVH;
			break;
		case TSHORT:
		case TUSHORT:
		case TCHAR:
		case TUCHAR:
			a = AMOVW;
			break;
		}
		break;
	case TUSHORT:
		switch(tt) {
		case TDOUBLE:
		case TFLOAT:
			goto fxtofl;
		case TINT:
		case TUINT:
		case TLONG:
		case TULONG:
		case TIND:
			a = AMOVHZ;
			break;
		case TSHORT:
		case TUSHORT:
		case TCHAR:
		case TUCHAR:
			a = AMOVW;
			break;
		}
		break;
	case TCHAR:
		switch(tt) {
		case TDOUBLE:
		case TFLOAT:
			goto fxtofl;
		case TINT:
		case TUINT:
		case TLONG:
		case TULONG:
		case TIND:
		case TSHORT:
		case TUSHORT:
			a = AMOVB;
			break;
		case TCHAR:
		case TUCHAR:
			a = AMOVW;
			break;
		}
		break;
	case TUCHAR:
		switch(tt) {
		case TDOUBLE:
		case TFLOAT:
		fxtofl:
			/*
			 * rat[0] = 0x43300000; rat[1] = f^0x80000000;
			 * t = *(double*)rat - FREGCVI;
			 * is-unsigned(t) => if(t<0) t += 2^32;
			 * could be streamlined for int-to-float
			 */
			regalloc(&fxc0, f, Z);
			regalloc(&fxc2, f, Z);
			regsalloc(&fxrat, &fconstnode);	/* should be type float */
			gins(AMOVW, nodconst(0x43300000L), &fxc0);
			gins(AMOVW, f, &fxc2);
			gins(AMOVW, &fxc0, &fxrat);
			gins(AXOR, nodconst(0x80000000L), &fxc2);
			fxc1 = fxrat;
			fxc1.type = nodrat->type;
			fxc1.etype = nodrat->etype;
			fxc1.xoffset += SZ_LONG;
			gins(AMOVW, &fxc2, &fxc1);
			regfree(&fxc2);
			regfree(&fxc0);
			regalloc(&nod, t, t);	/* should be type float */
			gins(AFMOVD, &fxrat, &nod);
			nodreg(&fxc1, t, NREG+FREGCVI);
			gins(AFSUB, &fxc1, &nod);
			a = AFMOVD;
			if(tt == TFLOAT)
				a = AFRSP;
			gins(a, &nod, t);
			regfree(&nod);
			if(ft == TULONG) {
				regalloc(&nod, t, Z);
				if(tt == TFLOAT) {
					gins(AFCMPU, t, Z);
					p->to.type = D_FREG;
					p->to.reg = FREGZERO;
					gins(ABGE, Z, Z);
					p1 = p;
					gins(AFMOVS, nodfconst(4294967296.), &nod);
					gins(AFADDS, &nod, t);
				} else {
					gins(AFCMPU, t, Z);
					p->to.type = D_FREG;
					p->to.reg = FREGZERO;
					gins(ABGE, Z, Z);
					p1 = p;
					gins(AFMOVD, nodfconst(4294967296.), &nod);
					gins(AFADD, &nod, t);
				}
				patch(p1, pc);
				regfree(&nod);
			}
			return;
		case TINT:
		case TUINT:
		case TLONG:
		case TULONG:
		case TIND:
		case TSHORT:
		case TUSHORT:
			a = AMOVBZ;
			break;
		case TCHAR:
		case TUCHAR:
			a = AMOVW;
			break;
		}
		break;
	case TVLONG:
	case TUVLONG:
		switch(tt) {
		case TVLONG:
		case TUVLONG:
			a = AMOVW;
			break;
		}
		break;
	}
	if(a == AGOK)
		diag(Z, "bad opcode in gmove %T -> %T", f->type, t->type);
	if(a == AMOVW || a == AFMOVS || a == AFMOVD)
	if(samaddr(f, t))
		return;
	if(typev[ft]) {
		if(f->op != OREGPAIR || t->op != OREGPAIR)
			diag(Z, "bad vlong in gmove (%O->%O)", f->op, t->op);
		gins(a, f->left, t->left);
		gins(a, f->right, t->right);
	} else
		gins(a, f, t);
}

void
gins(int a, Node *f, Node *t)
{

	nextpc();
	p->as = a;
	if(f != Z)
		naddr(f, &p->from);
	if(t != Z)
		naddr(t, &p->to);
	if(debug['g'])
		print("%P\n", p);
}

void
gins3(int a, Node *f1, Node *f2, Node *t)
{
	Adr ta;

	nextpc();
	p->as = a;
	if(f1 != Z)
		naddr(f1, &p->from);
	if(f2 != Z && (f2->op != OREGISTER || !samaddr(f2, t))) {
		ta = zprog.from;	/* TO DO */
		naddr(f2, &ta);
		p->reg = ta.reg;
		if(ta.type == D_CONST && ta.offset == 0) {
			if(R0ISZERO)
				p->reg = REGZERO;
			else
				diag(Z, "REGZERO in gins3 %A", a);
		}else if(ta.type == D_CONST)
			p->from3 = ta;
	}
	if(t != Z)
		naddr(t, &p->to);
	if(debug['g'])
		print("%P\n", p);
}

void
gins4(int a, Node *f1, Node *f2, Node *f3, Node *t)
{
	Adr ta;

	nextpc();
	p->as = a;
	naddr(f1, &p->from);
	if(f2->op != OREGISTER && (f2->op != OCONST || vconst(f2) != 0))
		diag(f2, "invalid gins4");
	naddr(f2, &ta);
	p->reg = ta.reg;
	if(ta.type == D_CONST && ta.offset == 0)
		p->reg = REGZERO;
	naddr(f3, &p->from3);
	naddr(t, &p->to);
	if(debug['g'])
		print("%P\n", p);
}

void
gopcode(int o, Node *f1, Node *f2, Node *t)
{
	int a, et, uns;

	if(o == OAS) {
		gmove(f1, t);
		return;
	}
	et = TLONG;
	if(f1 != Z && f1->type != T) {
		if(f1->op == OCONST && t != Z && t->type != T)
			et = t->type->etype;
		else
			et = f1->type->etype;
	}
	if((typev[et] || t->type != T && typev[t->type->etype]) && o != OFUNC) {
		gopcode64(o, f1, f2, t);
		return;
	}
	uns = 0;
	a = AGOK;
	switch(o) {

	case OASADD:
	case OADD:
		a = AADD;
		if(et == TFLOAT)
			a = AFADDS;
		else
		if(et == TDOUBLE)
			a = AFADD;
		break;

	case OASSUB:
	case OSUB:
		a = ASUB;
		if(et == TFLOAT)
			a = AFSUBS;
		else
		if(et == TDOUBLE)
			a = AFSUB;
		break;

	case OASOR:
	case OOR:
		a = AOR;
		break;

	case OASAND:
	case OAND:
		a = AAND;
		if(f1->op == OCONST)
			a = AANDCC;
		break;

	case OASXOR:
	case OXOR:
		a = AXOR;
		break;

	case OASLSHR:
	case OLSHR:
		a = ASRW;
		break;

	case OASASHR:
	case OASHR:
		a = ASRAW;
		break;

	case OASASHL:
	case OASHL:
		a = ASLW;
		break;

	case OFUNC:
		a = ABL;
		break;

	case OASLMUL:
	case OLMUL:
	case OASMUL:
	case OMUL:
		if(et == TFLOAT) {
			a = AFMULS;
			break;
		} else
		if(et == TDOUBLE) {
			a = AFMUL;
			break;
		}
		a = AMULLW;
		break;

	case OASDIV:
	case ODIV:
		if(et == TFLOAT) {
			a = AFDIVS;
			break;
		} else
		if(et == TDOUBLE) {
			a = AFDIV;
			break;
		}
		a = ADIVW;
		break;

	case OASMOD:
	case OMOD:
		a = AREM;
		break;

	case OASLMOD:
	case OLMOD:
		a = AREMU;
		break;

	case OASLDIV:
	case OLDIV:
		a = ADIVWU;
		break;

	case OCOM:
		a = ANOR;
		break;

	case ONEG:
		a = ANEG;
		if(et == TFLOAT || et == TDOUBLE)
			a = AFNEG;
		break;

	case OEQ:
		a = ABEQ;
		if(t->op == OCONST && t->vconst >= (1<<15))
			goto cmpu;
		goto cmp;

	case ONE:
		a = ABNE;
		if(t->op == OCONST && t->vconst >= (1<<15))
			goto cmpu;
		goto cmp;

	case OLT:
		a = ABLT;
		goto cmp;

	case OLE:
		a = ABLE;
		goto cmp;

	case OGE:
		a = ABGE;
		goto cmp;

	case OGT:
		a = ABGT;
		goto cmp;

	case OLO:
		a = ABLT;
		goto cmpu;

	case OLS:
		a = ABLE;
		goto cmpu;

	case OHS:
		a = ABGE;
		goto cmpu;

	case OHI:
		a = ABGT;
		goto cmpu;

	cmpu:
		uns = 1;
	cmp:
		nextpc();
		p->as = uns? ACMPU: ACMP;
		if(et == TFLOAT)
			p->as = AFCMPU;
		else
		if(et == TDOUBLE)
			p->as = AFCMPU;
		if(f1 != Z)
			naddr(f1, &p->from);
		if(t != Z)
			naddr(t, &p->to);
		if(f1 == Z || t == Z || f2 != Z)
			diag(Z, "bad cmp in gopcode %O", o);
		if(debug['g'])
			print("%P\n", p);
		f1 = Z;
		f2 = Z;
		t = Z;
		break;
	}
	if(a == AGOK)
		diag(Z, "bad in gopcode %O", o);
	gins3(a, f1, f2, t);
}

static void
gopcode64(int o, Node *f1, Node *f2, Node *t)
{
	int a1, a2;
	Node nod, nod1, nod2, sh;
	ulong m;
	Prog *p1;

	if(t->op != OREGPAIR || f2 != Z && f2->op != OREGPAIR) {
		diag(Z, "bad f2/dest in gopcode64 %O", o);
		return;
	}
	if(f1->op != OCONST &&
	   (typev[f1->type->etype] && f1->op != OREGPAIR || !typev[f1->type->etype] && f1->op != OREGISTER)) {
		diag(Z, "bad f1[%O] in gopcode64 %O", f1->op, o);
		return;
	}
	/* a1 for low-order, a2 for high-order */
	a1 = AGOK;
	a2 = AGOK;
	switch(o) {
	case OASADD:
	case OADD:
		if(f1->op == OCONST && sconst(f1)) {
			if(f2 == Z)
				f2 = t;
			gins3(AADDC, f1, f2->right, t->right);
			if((f1->vconst>>32) == 0)
				gins(AADDZE, f2->left, t->left);
			else if((f1->vconst>>32) == -1)
				gins(AADDME, f2->left, t->left);
			else
				diag(t, "odd vlong ADD: %lld", f1->vconst);
			return;
		}
		a1 = AADDC;
		a2 = AADDE;
		break;

	case OASSUB:
	case OSUB:
		a1 = ASUBC;
		a2 = ASUBE;
		break;

	case OASOR:
	case OOR:
		if(f1->op == OCONST) {
			gori64(AOR, f1, f2, t);
			return;
		}
		a1 = a2 = AOR;
		break;

	case OASAND:
	case OAND:
		if(f1->op == OCONST) {
			gandi64(AANDCC, f1, f2, t);
			return;
		}
		a1 = a2 = AAND;
		break;

	case OASXOR:
	case OXOR:
		if(f1->op == OCONST) {
			gori64(AXOR, f1, f2, t);
			return;
		}
		a1 = a2 = AXOR;
		break;

	case OASLSHR:
	case OLSHR:
		if(f2 == Z)
			f2 = t;
		if(f1->op == OCONST) {
			if(f1->vconst >= 32) {
				if(f1->vconst == 32)
					gmove(f2->left, t->right);
				else if(f1->vconst < 64)
					gins3(ASRW, nodconst(f1->vconst-32), f2->left, t->right);
				else
					gmove(nodconst(0), t->right);
				gmove(nodconst(0), t->left);
				return;
			}
			if(f1->vconst <= 0) {
				if(f2 != t)
					gmove(f2, t);
				return;
			}
			sh = *nodconst(32 - f1->vconst);
			m = 0xFFFFFFFFUL >> f1->vconst;
			gins4(ARLWNM, &sh, f2->right, nodconst(m), t->right);
			gins4(ARLWMI, &sh, f2->left, nodconst(~m), t->right);
			gins4(ARLWNM, &sh, f2->left, nodconst(m), t->left);
			return;
		}
		regalloc(&nod, &regnode, Z);
		gins3(ASUBC, f1, nodconst(32), &nod);
		gins3(ASRW, f1, f2->right, t->right);
		regalloc(&nod1, &regnode, Z);
		gins3(ASLW, &nod, f2->left, &nod1);
		gins(AOR, &nod1, t->right);
		gins3(AADD, nodconst(-32), f1, &nod);
		gins3(ASRW, &nod, f2->left, &nod1);
		gins(AOR, &nod1, t->right);
		gins3(ASRW, f1, f2->left, t->left);
		regfree(&nod);
		regfree(&nod1);
		return;

	case OASASHR:
	case OASHR:
		if(f2 == Z)
			f2 = t;
		if(f1->op == OCONST) {
			if(f1->vconst >= 32) {
				if(f1->vconst == 32)
					gmove(f2->left, t->right);
				else if(f1->vconst < 64)
					gins3(ASRAW, nodconst(f1->vconst-32), f2->left, t->right);
				gins3(ASRAW, nodconst(31), f2->left, t->left);
				if(f1->vconst >= 64) {
					gmove(t->left, t->right);
					return;
				}
				return;
			}
			if(f1->vconst <= 0) {
				if(f2 != t)
					gmove(f2, t);
				return;
			}
			sh = *nodconst(32 - f1->vconst);
			m = 0xFFFFFFFFUL >> f1->vconst;
			gins4(ARLWNM, &sh, f2->right, nodconst(m), t->right);
			gins4(ARLWMI, &sh, f2->left, nodconst(~m), t->right);
			gins3(ASRAW, &sh, f2->left, t->left);
			return;
		}
		regalloc(&nod, &regnode, Z);
		gins3(ASUBC, f1, nodconst(32), &nod);
		gins3(ASRW, f1, f2->right, t->right);
		regalloc(&nod1, &regnode, Z);
		gins3(ASLW, &nod, f2->left, &nod1);
		gins(AOR, &nod1, t->right);
		gins3(AADDCCC, nodconst(-32), f1, &nod);
		gins3(ASRAW, &nod, f2->left, &nod1);
		gins(ABLE, Z, Z);
		p1 = p;
		gins(AMOVW, &nod1, t->right);
		patch(p1, pc);
		gins3(ASRAW, f1, f2->left, t->left);
		regfree(&nod);
		regfree(&nod1);
		return;

	case OASASHL:
	case OASHL:
		if(f2 == Z)
			f2 = t;
		if(f1->op == OCONST) {
			if(f1->vconst >= 32) {
				if(f1->vconst == 32)
					gmove(f2->right, t->left);
				else if(f1->vconst >= 64)
					gmove(nodconst(0), t->left);
				else
					gins3(ASLW, nodconst(f1->vconst-32), f2->right, t->left);
				gmove(nodconst(0), t->right);
				return;
			}
			if(f1->vconst <= 0) {
				if(f2 != t)
					gmove(f2, t);
				return;
			}
			m = 0xFFFFFFFFUL << f1->vconst;
			gins4(ARLWNM, f1, f2->left, nodconst(m), t->left);
			gins4(ARLWMI, f1, f2->right, nodconst(~m), t->left);
			gins4(ARLWNM, f1, f2->right, nodconst(m), t->right);
			return;
		}
		regalloc(&nod, &regnode, Z);
		gins3(ASUBC, f1, nodconst(32), &nod);
		gins3(ASLW, f1, f2->left, t->left);
		regalloc(&nod1, &regnode, Z);
		gins3(ASRW, &nod, f2->right, &nod1);
		gins(AOR, &nod1, t->left);
		gins3(AADD, nodconst(-32), f1, &nod);
		gins3(ASLW, &nod, f2->right, &nod1);
		gins(AOR, &nod1, t->left);
		gins3(ASLW, f1, f2->right, t->right);
		regfree(&nod);
		regfree(&nod1);
		return;

	case OASLMUL:
	case OLMUL:
	case OASMUL:
	case OMUL:
		if(f2 == Z)
			f2 = t;
		regalloc(&nod, &regnode, Z);
		gins3(AMULLW, f1->right, f2->right, &nod);	/* lo(f2.low*f1.low) */
		regalloc(&nod1, &regnode, Z);
		gins3(AMULHWU, f1->right, f2->right, &nod1);		/* hi(f2.low*f1.low) */
		regalloc(&nod2, &regnode, Z);
		gins3(AMULLW, f2->right, f1->left, &nod2);	/* lo(f2.low*f1.high) */
		gins(AADD, &nod2, &nod1);
		gins3(AMULLW, f1->right, f2->left, &nod2);	/* lo(f2.high*f1.low) */
		gins(AADD, &nod2, &nod1);
		regfree(&nod2);
		gmove(&nod, t->right);
		gmove(&nod1, t->left);
		regfree(&nod);
		regfree(&nod1);
		return;

	case OCOM:
		a1 = a2 = ANOR;
		break;

	case ONEG:
		gins3(ASUBC, t->right, nodconst(0), t->right);
		gins(ASUBZE, t->left, t->left);
		return;
	}
	if(a1 == AGOK || a2 == AGOK)
		diag(Z, "bad in gopcode64 %O", o);
	if(f1->op == OCONST) {
		if(f2 != Z & f2 != t)
			diag(Z, "bad const in gopcode64 %O", o);
		gins(a1, nod32const(f1->vconst), t->right);
		gins(a2, nod32const(f1->vconst>>32), t->left);
	} else {
		if(f2 != Z && f2 != t) {
			gins3(a1, f1->right, f2->right, t->right);
			gins3(a2, f1->left, f2->left, t->left);
		} else {
			gins(a1, f1->right, t->right);
			gins(a2, f1->left, t->left);
		}
	}
}

int
samaddr(Node *f, Node *t)
{

	if(f->op != t->op)
		return 0;
	switch(f->op) {

	case OREGISTER:
		if(f->reg != t->reg)
			break;
		return 1;

	case OREGPAIR:
		return samaddr(f->left, t->left) && samaddr(f->right, t->right);
	}
	return 0;
}

static void
gori64(int a, Node *f1, Node *f2, Node *t)
{
	ulong lo, hi;

	if(f2 == Z)
		f2 = t;
	lo = f1->vconst & MASK(32);
	hi = (f1->vconst >> 32) & MASK(32);
	if(lo & 0xFFFF)
		gins3(a, nodconst(lo & 0xFFFF), f2->right, t->right);
	if((lo >> 16) != 0)
		gins3(a, nodconst(lo & 0xFFFF0000UL), f2->right, t->right);
	if(hi & 0xFFFF)
		gins3(a, nodconst(hi & 0xFFFF), f2->left, t->left);
	if((hi >> 16) != 0)
		gins3(a, nodconst(hi & 0xFFFF0000UL), f2->left, t->left);
}

static void
gandi64(int a, Node *f1, Node *f2, Node *t)
{
	ulong lo, hi;

	if(f2 == Z)
		f2 = t;
	lo = f1->vconst & MASK(32);
	hi = (f1->vconst >> 32) & MASK(32);
	if(lo == 0)
		gins(AMOVW, nodconst(0), t->right);
	else
		gins3(a, nodconst(lo), f2->right, t->right);
	if(hi == 0)
		gins(AMOVW, nodconst(0), t->left);
	else
		gins3(a, nodconst(hi), f2->left, t->left);
}

void
gbranch(int o)
{
	int a;

	a = AGOK;
	switch(o) {
	case ORETURN:
		a = ARETURN;
		break;
	case OGOTO:
		a = ABR;
		break;
	}
	nextpc();
	if(a == AGOK) {
		diag(Z, "bad in gbranch %O",  o);
		nextpc();
	}
	p->as = a;
}

void
patch(Prog *op, long pc)
{

	op->to.offset = pc;
	op->to.type = D_BRANCH;
}

void
gpseudo(int a, Sym *s, Node *n)
{

	nextpc();
	p->as = a;
	p->from.type = D_OREG;
	p->from.sym = s;
	if(a == ATEXT)
		p->reg = (profileflg ? 0 : NOPROF);
	p->from.name = D_EXTERN;
	if(s->class == CSTATIC)
		p->from.name = D_STATIC;
	naddr(n, &p->to);
	if(a == ADATA || a == AGLOBL)
		pc--;
}

int
sval(long v)
{

	if(v >= -(1<<15) && v < (1<<15))
		return 1;
	return 0;
}

int
sconst(Node *n)
{
	vlong vv;

	if(n->op == OCONST) {
		if(!typefd[n->type->etype]) {
			vv = n->vconst;
			if(vv >= -(((vlong)1)<<15) && vv < (((vlong)1)<<15))
				return 1;
		}
	}
	return 0;
}

int
uconst(Node *n)
{
	vlong vv;

	if(n->op == OCONST) {
		if(!typefd[n->type->etype]) {
			vv = n->vconst;
			if(vv >= 0 && vv < (((vlong)1)<<16))
				return 1;
		}
	}
	return 0;
}

long
exreg(Type *t)
{
	long o;

	if(typechlp[t->etype]) {
		if(exregoffset <= 3)
			return 0;
		o = exregoffset;
		exregoffset--;
		return o;
	}
	if(typefd[t->etype]) {
		if(exfregoffset <= 16)
			return 0;
		o = exfregoffset + NREG;
		exfregoffset--;
		return o;
	}
	return 0;
}

schar	ewidth[NTYPE] =
{
	-1,		/* [TXXX] */
	SZ_CHAR,	/* [TCHAR] */
	SZ_CHAR,	/* [TUCHAR] */
	SZ_SHORT,	/* [TSHORT] */
	SZ_SHORT,	/* [TUSHORT] */
	SZ_INT,		/* [TINT] */
	SZ_INT,		/* [TUINT] */
	SZ_LONG,	/* [TLONG] */
	SZ_LONG,	/* [TULONG] */
	SZ_VLONG,	/* [TVLONG] */
	SZ_VLONG,	/* [TUVLONG] */
	SZ_FLOAT,	/* [TFLOAT] */
	SZ_DOUBLE,	/* [TDOUBLE] */
	SZ_IND,		/* [TIND] */
	0,		/* [TFUNC] */
	-1,		/* [TARRAY] */
	0,		/* [TVOID] */
	-1,		/* [TSTRUCT] */
	-1,		/* [TUNION] */
	SZ_INT,		/* [TENUM] */
};
long	ncast[NTYPE] =
{
	0,				/* [TXXX] */
	BCHAR|BUCHAR,			/* [TCHAR] */
	BCHAR|BUCHAR,			/* [TUCHAR] */
	BSHORT|BUSHORT,			/* [TSHORT] */
	BSHORT|BUSHORT,			/* [TUSHORT] */
	BINT|BUINT|BLONG|BULONG|BIND,	/* [TINT] */
	BINT|BUINT|BLONG|BULONG|BIND,	/* [TUINT] */
	BINT|BUINT|BLONG|BULONG|BIND,	/* [TLONG] */
	BINT|BUINT|BLONG|BULONG|BIND,	/* [TULONG] */
	BVLONG|BUVLONG,			/* [TVLONG] */
	BVLONG|BUVLONG,			/* [TUVLONG] */
	BFLOAT,				/* [TFLOAT] */
	BDOUBLE,			/* [TDOUBLE] */
	BLONG|BULONG|BIND,		/* [TIND] */
	0,				/* [TFUNC] */
	0,				/* [TARRAY] */
	0,				/* [TVOID] */
	BSTRUCT,			/* [TSTRUCT] */
	BUNION,				/* [TUNION] */
	0,				/* [TENUM] */
};
