// Inferno utils/cc/scon.c
// http://code.google.com/p/inferno-os/source/browse/utils/cc/scon.c
//
//	Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
//	Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//	Portions Copyright © 1997-1999 Vita Nuova Limited
//	Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
//	Portions Copyright © 2004,2006 Bruce Ellis
//	Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//	Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
//	Portions Copyright © 2009 The Go Authors.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "cc.h"

static Node*
acast(Type *t, Node *n)
{
	if(n->type->etype != t->etype || n->op == OBIT) {
		n = new1(OCAST, n, Z);
		if(nocast(n->left->type, t))
			*n = *n->left;
		n->type = t;
	}
	return n;
}


void
evconst(Node *n)
{
	Node *l, *r;
	int et, isf;
	vlong v;
	double d;

	if(n == Z || n->type == T)
		return;

	et = n->type->etype;
	isf = typefd[et];

	l = n->left;
	r = n->right;

	d = 0;
	v = 0;

	switch(n->op) {
	default:
		return;

	case ONEG:
		if(isf)
			d = -l->fconst;
		else
			v = -l->vconst;
		break;

	case OCOM:
		v = ~l->vconst;
		break;

	case OCAST:
		if(et == TVOID)
			return;
		et = l->type->etype;
		if(isf) {
			if(typefd[et])
				d = l->fconst;
			else
				d = l->vconst;
		} else {
			if(typefd[et])
				v = l->fconst;
			else
				v = convvtox(l->vconst, n->type->etype);
		}
		break;

	case OCONST:
		break;

	case OADD:
		if(isf)
			d = l->fconst + r->fconst;
		else {
			v = l->vconst + r->vconst;
		}
		break;

	case OSUB:
		if(isf)
			d = l->fconst - r->fconst;
		else
			v = l->vconst - r->vconst;
		break;

	case OMUL:
		if(isf)
			d = l->fconst * r->fconst;
		else {
			v = l->vconst * r->vconst;
		}
		break;

	case OLMUL:
		v = (uvlong)l->vconst * (uvlong)r->vconst;
		break;


	case ODIV:
		if(vconst(r) == 0) {
			warn(n, "divide by zero");
			return;
		}
		if(isf)
			d = l->fconst / r->fconst;
		else
			v = l->vconst / r->vconst;
		break;

	case OLDIV:
		if(vconst(r) == 0) {
			warn(n, "divide by zero");
			return;
		}
		v = (uvlong)l->vconst / (uvlong)r->vconst;
		break;

	case OMOD:
		if(vconst(r) == 0) {
			warn(n, "modulo by zero");
			return;
		}
		v = l->vconst % r->vconst;
		break;

	case OLMOD:
		if(vconst(r) == 0) {
			warn(n, "modulo by zero");
			return;
		}
		v = (uvlong)l->vconst % (uvlong)r->vconst;
		break;

	case OAND:
		v = l->vconst & r->vconst;
		break;

	case OOR:
		v = l->vconst | r->vconst;
		break;

	case OXOR:
		v = l->vconst ^ r->vconst;
		break;

	case OLSHR:
		v = (uvlong)l->vconst >> r->vconst;
		break;

	case OASHR:
		v = l->vconst >> r->vconst;
		break;

	case OASHL:
		v = l->vconst << r->vconst;
		break;

	case OLO:
		v = (uvlong)l->vconst < (uvlong)r->vconst;
		break;

	case OLT:
		if(typefd[l->type->etype])
			v = l->fconst < r->fconst;
		else
			v = l->vconst < r->vconst;
		break;

	case OHI:
		v = (uvlong)l->vconst > (uvlong)r->vconst;
		break;

	case OGT:
		if(typefd[l->type->etype])
			v = l->fconst > r->fconst;
		else
			v = l->vconst > r->vconst;
		break;

	case OLS:
		v = (uvlong)l->vconst <= (uvlong)r->vconst;
		break;

	case OLE:
		if(typefd[l->type->etype])
			v = l->fconst <= r->fconst;
		else
			v = l->vconst <= r->vconst;
		break;

	case OHS:
		v = (uvlong)l->vconst >= (uvlong)r->vconst;
		break;

	case OGE:
		if(typefd[l->type->etype])
			v = l->fconst >= r->fconst;
		else
			v = l->vconst >= r->vconst;
		break;

	case OEQ:
		if(typefd[l->type->etype])
			v = l->fconst == r->fconst;
		else
			v = l->vconst == r->vconst;
		break;

	case ONE:
		if(typefd[l->type->etype])
			v = l->fconst != r->fconst;
		else
			v = l->vconst != r->vconst;
		break;

	case ONOT:
		if(typefd[l->type->etype])
			v = !l->fconst;
		else
			v = !l->vconst;
		break;

	case OANDAND:
		if(typefd[l->type->etype])
			v = l->fconst && r->fconst;
		else
			v = l->vconst && r->vconst;
		break;

	case OOROR:
		if(typefd[l->type->etype])
			v = l->fconst || r->fconst;
		else
			v = l->vconst || r->vconst;
		break;
	}
	if(isf) {
		n->fconst = d;
	} else {
		n->vconst = convvtox(v, n->type->etype);
	}
	n->oldop = n->op;
	n->op = OCONST;
}

void
acom(Node *n)
{
	Type *t;
	Node *l, *r;
	int i;

	switch(n->op)
	{

	case ONAME:
	case OCONST:
	case OSTRING:
	case OINDREG:
	case OREGISTER:
		return;

	case ONEG:
		l = n->left;
		if(addo(n) && addo(l))
			break;
		acom(l);
		return;

	case OADD:
	case OSUB:
	case OMUL:
		l = n->left;
		r = n->right;
		if(addo(n)) {
			if(addo(r))
				break;
			if(addo(l))
				break;
		}
		acom(l);
		acom(r);
		return;

	default:
		l = n->left;
		r = n->right;
		if(l != Z)
			acom(l);
		if(r != Z)
			acom(r);
		return;
	}

	/* bust terms out */
	t = n->type;
	term[0].mult = 0;
	term[0].node = Z;
	nterm = 1;
	acom1(1, n);
	if(debug['m'])
	for(i=0; i<nterm; i++) {
		print("%d %3lld ", i, term[i].mult);
		prtree1(term[i].node, 1, 0);
	}
	if(nterm < NTERM)
		acom2(n, t);
	n->type = t;
}

int
acomcmp1(const void *a1, const void *a2)
{
	vlong c1, c2;
	Term *t1, *t2;

	t1 = (Term*)a1;
	t2 = (Term*)a2;
	c1 = t1->mult;
	if(c1 < 0)
		c1 = -c1;
	c2 = t2->mult;
	if(c2 < 0)
		c2 = -c2;
	if(c1 > c2)
		return 1;
	if(c1 < c2)
		return -1;
	c1 = 1;
	if(t1->mult < 0)
		c1 = 0;
	c2 = 1;
	if(t2->mult < 0)
		c2 = 0;
	if(c2 -= c1)
		return c2;
	if(t2 > t1)
		return 1;
	return -1;
}

int
acomcmp2(const void *a1, const void *a2)
{
	vlong c1, c2;
	Term *t1, *t2;

	t1 = (Term*)a1;
	t2 = (Term*)a2;
	c1 = t1->mult;
	c2 = t2->mult;
	if(c1 > c2)
		return 1;
	if(c1 < c2)
		return -1;
	if(t2 > t1)
		return 1;
	return -1;
}

void
acom2(Node *n, Type *t)
{
	Node *l, *r;
	Term trm[NTERM];
	int et, nt, i, j;
	vlong c1, c2;

	/*
	 * copy into automatic
	 */
	c2 = 0;
	nt = nterm;
	for(i=0; i<nt; i++)
		trm[i] = term[i];
	/*
	 * recur on subtrees
	 */
	j = 0;
	for(i=1; i<nt; i++) {
		c1 = trm[i].mult;
		if(c1 == 0)
			continue;
		l = trm[i].node;
		if(l != Z) {
			j = 1;
			acom(l);
		}
	}
	c1 = trm[0].mult;
	if(j == 0) {
		n->oldop = n->op;
		n->op = OCONST;
		n->vconst = c1;
		return;
	}
	et = t->etype;

	/*
	 * prepare constant term,
	 * combine it with an addressing term
	 */
	if(c1 != 0) {
		l = new1(OCONST, Z, Z);
		l->type = t;
		l->vconst = c1;
		trm[0].mult = 1;
		for(i=1; i<nt; i++) {
			if(trm[i].mult != 1)
				continue;
			r = trm[i].node;
			if(r->op != OADDR)
				continue;
			r->type = t;
			l = new1(OADD, r, l);
			l->type = t;
			trm[i].mult = 0;
			break;
		}
		trm[0].node = l;
	}
	/*
	 * look for factorable terms
	 * c1*i + c1*c2*j -> c1*(i + c2*j)
	 */
	qsort(trm+1, nt-1, sizeof(trm[0]), acomcmp1);
	for(i=nt-1; i>=0; i--) {
		c1 = trm[i].mult;
		if(c1 < 0)
			c1 = -c1;
		if(c1 <= 1)
			continue;
		for(j=i+1; j<nt; j++) {
			c2 = trm[j].mult;
			if(c2 < 0)
				c2 = -c2;
			if(c2 <= 1)
				continue;
			if(c2 % c1)
				continue;
			r = trm[j].node;
			if(r->type->etype != et)
				r = acast(t, r);
			c2 = trm[j].mult/trm[i].mult;
			if(c2 != 1 && c2 != -1) {
				r = new1(OMUL, r, new(OCONST, Z, Z));
				r->type = t;
				r->right->type = t;
				r->right->vconst = c2;
			}
			l = trm[i].node;
			if(l->type->etype != et)
				l = acast(t, l);
			r = new1(OADD, l, r);
			r->type = t;
			if(c2 == -1)
				r->op = OSUB;
			trm[i].node = r;
			trm[j].mult = 0;
		}
	}
	if(debug['m']) {
		print("\n");
		for(i=0; i<nt; i++) {
			print("%d %3lld ", i, trm[i].mult);
			prtree1(trm[i].node, 1, 0);
		}
	}

	/*
	 * put it all back together
	 */
	qsort(trm+1, nt-1, sizeof(trm[0]), acomcmp2);
	l = Z;
	for(i=nt-1; i>=0; i--) {
		c1 = trm[i].mult;
		if(c1 == 0)
			continue;
		r = trm[i].node;
		if(r->type->etype != et || r->op == OBIT)
			r = acast(t, r);
		if(c1 != 1 && c1 != -1) {
			r = new1(OMUL, r, new(OCONST, Z, Z));
			r->type = t;
			r->right->type = t;
			if(c1 < 0) {
				r->right->vconst = -c1;
				c1 = -1;
			} else {
				r->right->vconst = c1;
				c1 = 1;
			}
		}
		if(l == Z) {
			l = r;
			c2 = c1;
			continue;
		}
		if(c1 < 0)
			if(c2 < 0)
				l = new1(OADD, l, r);
			else
				l = new1(OSUB, l, r);
		else
			if(c2 < 0) {
				l = new1(OSUB, r, l);
				c2 = 1;
			} else
				l = new1(OADD, l, r);
		l->type = t;
	}
	if(c2 < 0) {
		r = new1(OCONST, 0, 0);
		r->vconst = 0;
		r->type = t;
		l = new1(OSUB, r, l);
		l->type = t;
	}
	*n = *l;
}

void
acom1(vlong v, Node *n)
{
	Node *l, *r;

	if(v == 0 || nterm >= NTERM)
		return;
	if(!addo(n)) {
		if(n->op == OCONST)
		if(!typefd[n->type->etype]) {
			term[0].mult += v*n->vconst;
			return;
		}
		term[nterm].mult = v;
		term[nterm].node = n;
		nterm++;
		return;
	}
	switch(n->op) {

	case OCAST:
		acom1(v, n->left);
		break;

	case ONEG:
		acom1(-v, n->left);
		break;

	case OADD:
		acom1(v, n->left);
		acom1(v, n->right);
		break;

	case OSUB:
		acom1(v, n->left);
		acom1(-v, n->right);
		break;

	case OMUL:
		l = n->left;
		r = n->right;
		if(l->op == OCONST)
		if(!typefd[n->type->etype]) {
			acom1(v*l->vconst, r);
			break;
		}
		if(r->op == OCONST)
		if(!typefd[n->type->etype]) {
			acom1(v*r->vconst, l);
			break;
		}
		break;

	default:
		diag(n, "not addo");
	}
}

int
addo(Node *n)
{

	if(n != Z)
	if(!typefd[n->type->etype])
	if(!typev[n->type->etype] || ewidth[TVLONG] == ewidth[TIND])
	switch(n->op) {

	case OCAST:
		if(nilcast(n->left->type, n->type))
			return 1;
		break;

	case ONEG:
	case OADD:
	case OSUB:
		return 1;

	case OMUL:
		if(n->left->op == OCONST)
			return 1;
		if(n->right->op == OCONST)
			return 1;
	}
	return 0;
}
