// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include <u.h>
#include <libc.h>
#include "gg.h"

/*
 * attempt to generate 64-bit
 *	res = n
 * return 1 on success, 0 if op not handled.
 */
void
cgen64(Node *n, Node *res)
{
	Node t1, t2, ax, dx, cx, ex, fx, *l, *r;
	Node lo1, lo2, hi1, hi2;
	Prog *p1, *p2;
	uint64 v;
	uint32 lv, hv;

	if(res->op != OINDREG && res->op != ONAME) {
		dump("n", n);
		dump("res", res);
		fatal("cgen64 %O of %O", n->op, res->op);
	}
	switch(n->op) {
	default:
		fatal("cgen64 %O", n->op);

	case OMINUS:
		cgen(n->left, res);
		split64(res, &lo1, &hi1);
		gins(ANEGL, N, &lo1);
		gins(AADCL, ncon(0), &hi1);
		gins(ANEGL, N, &hi1);
		splitclean();
		return;

	case OCOM:
		cgen(n->left, res);
		split64(res, &lo1, &hi1);
		gins(ANOTL, N, &lo1);
		gins(ANOTL, N, &hi1);
		splitclean();
		return;

	case OADD:
	case OSUB:
	case OMUL:
	case OLSH:
	case ORSH:
	case OAND:
	case OOR:
	case OXOR:
		// binary operators.
		// common setup below.
		break;
	}

	l = n->left;
	r = n->right;
	if(!l->addable) {
		tempname(&t1, l->type);
		cgen(l, &t1);
		l = &t1;
	}
	if(r != N && !r->addable) {
		tempname(&t2, r->type);
		cgen(r, &t2);
		r = &t2;
	}

	nodreg(&ax, types[TINT32], D_AX);
	nodreg(&cx, types[TINT32], D_CX);
	nodreg(&dx, types[TINT32], D_DX);

	// Setup for binary operation.
	split64(l, &lo1, &hi1);
	if(is64(r->type))
		split64(r, &lo2, &hi2);

	// Do op.  Leave result in DX:AX.
	switch(n->op) {
	case OADD:
		// TODO: Constants
		gins(AMOVL, &lo1, &ax);
		gins(AMOVL, &hi1, &dx);
		gins(AADDL, &lo2, &ax);
		gins(AADCL, &hi2, &dx);
		break;

	case OSUB:
		// TODO: Constants.
		gins(AMOVL, &lo1, &ax);
		gins(AMOVL, &hi1, &dx);
		gins(ASUBL, &lo2, &ax);
		gins(ASBBL, &hi2, &dx);
		break;

	case OMUL:
		// let's call the next two EX and FX.
		regalloc(&ex, types[TPTR32], N);
		regalloc(&fx, types[TPTR32], N);

		// load args into DX:AX and EX:CX.
		gins(AMOVL, &lo1, &ax);
		gins(AMOVL, &hi1, &dx);
		gins(AMOVL, &lo2, &cx);
		gins(AMOVL, &hi2, &ex);

		// if DX and EX are zero, use 32 x 32 -> 64 unsigned multiply.
		gins(AMOVL, &dx, &fx);
		gins(AORL, &ex, &fx);
		p1 = gbranch(AJNE, T);
		gins(AMULL, &cx, N);	// implicit &ax
		p2 = gbranch(AJMP, T);
		patch(p1, pc);

		// full 64x64 -> 64, from 32x32 -> 64.
		gins(AIMULL, &cx, &dx);
		gins(AMOVL, &ax, &fx);
		gins(AIMULL, &ex, &fx);
		gins(AADDL, &dx, &fx);
		gins(AMOVL, &cx, &dx);
		gins(AMULL, &dx, N);	// implicit &ax
		gins(AADDL, &fx, &dx);
		patch(p2, pc);

		regfree(&ex);
		regfree(&fx);
		break;

	case OLSH:
		if(r->op == OLITERAL) {
			v = mpgetfix(r->val.u.xval);
			if(v >= 64) {
				if(is64(r->type))
					splitclean();
				splitclean();
				split64(res, &lo2, &hi2);
				gins(AMOVL, ncon(0), &lo2);
				gins(AMOVL, ncon(0), &hi2);
				splitclean();
				goto out;
			}
			if(v >= 32) {
				if(is64(r->type))
					splitclean();
				split64(res, &lo2, &hi2);
				gmove(&lo1, &hi2);
				if(v > 32) {
					gins(ASHLL, ncon(v - 32), &hi2);
				}
				gins(AMOVL, ncon(0), &lo2);
				splitclean();
				splitclean();
				goto out;
			}

			// general shift
			gins(AMOVL, &lo1, &ax);
			gins(AMOVL, &hi1, &dx);
			p1 = gins(ASHLL, ncon(v), &dx);
			p1->from.index = D_AX;	// double-width shift
			p1->from.scale = 0;
			gins(ASHLL, ncon(v), &ax);
			break;
		}

		// load value into DX:AX.
		gins(AMOVL, &lo1, &ax);
		gins(AMOVL, &hi1, &dx);

		// load shift value into register.
		// if high bits are set, zero value.
		p1 = P;
		if(is64(r->type)) {
			gins(ACMPL, &hi2, ncon(0));
			p1 = gbranch(AJNE, T);
			gins(AMOVL, &lo2, &cx);
		} else {
			cx.type = types[TUINT32];
			gmove(r, &cx);
		}

		// if shift count is >=64, zero value
		gins(ACMPL, &cx, ncon(64));
		p2 = gbranch(optoas(OLT, types[TUINT32]), T);
		if(p1 != P)
			patch(p1, pc);
		gins(AXORL, &dx, &dx);
		gins(AXORL, &ax, &ax);
		patch(p2, pc);

		// if shift count is >= 32, zero low.
		gins(ACMPL, &cx, ncon(32));
		p1 = gbranch(optoas(OLT, types[TUINT32]), T);
		gins(AMOVL, &ax, &dx);
		gins(ASHLL, &cx, &dx);	// SHLL only uses bottom 5 bits of count
		gins(AXORL, &ax, &ax);
		p2 = gbranch(AJMP, T);
		patch(p1, pc);

		// general shift
		p1 = gins(ASHLL, &cx, &dx);
		p1->from.index = D_AX;	// double-width shift
		p1->from.scale = 0;
		gins(ASHLL, &cx, &ax);
		patch(p2, pc);
		break;

	case ORSH:
		if(r->op == OLITERAL) {
			v = mpgetfix(r->val.u.xval);
			if(v >= 64) {
				if(is64(r->type))
					splitclean();
				splitclean();
				split64(res, &lo2, &hi2);
				if(hi1.type->etype == TINT32) {
					gmove(&hi1, &lo2);
					gins(ASARL, ncon(31), &lo2);
					gmove(&hi1, &hi2);
					gins(ASARL, ncon(31), &hi2);
				} else {
					gins(AMOVL, ncon(0), &lo2);
					gins(AMOVL, ncon(0), &hi2);
				}
				splitclean();
				goto out;
			}
			if(v >= 32) {
				if(is64(r->type))
					splitclean();
				split64(res, &lo2, &hi2);
				gmove(&hi1, &lo2);
				if(v > 32)
					gins(optoas(ORSH, hi1.type), ncon(v-32), &lo2);
				if(hi1.type->etype == TINT32) {
					gmove(&hi1, &hi2);
					gins(ASARL, ncon(31), &hi2);
				} else
					gins(AMOVL, ncon(0), &hi2);
				splitclean();
				splitclean();
				goto out;
			}

			// general shift
			gins(AMOVL, &lo1, &ax);
			gins(AMOVL, &hi1, &dx);
			p1 = gins(ASHRL, ncon(v), &ax);
			p1->from.index = D_DX;	// double-width shift
			p1->from.scale = 0;
			gins(optoas(ORSH, hi1.type), ncon(v), &dx);
			break;
		}

		// load value into DX:AX.
		gins(AMOVL, &lo1, &ax);
		gins(AMOVL, &hi1, &dx);

		// load shift value into register.
		// if high bits are set, zero value.
		p1 = P;
		if(is64(r->type)) {
			gins(ACMPL, &hi2, ncon(0));
			p1 = gbranch(AJNE, T);
			gins(AMOVL, &lo2, &cx);
		} else {
			cx.type = types[TUINT32];
			gmove(r, &cx);
		}

		// if shift count is >=64, zero or sign-extend value
		gins(ACMPL, &cx, ncon(64));
		p2 = gbranch(optoas(OLT, types[TUINT32]), T);
		if(p1 != P)
			patch(p1, pc);
		if(hi1.type->etype == TINT32) {
			gins(ASARL, ncon(31), &dx);
			gins(AMOVL, &dx, &ax);
		} else {
			gins(AXORL, &dx, &dx);
			gins(AXORL, &ax, &ax);
		}
		patch(p2, pc);

		// if shift count is >= 32, sign-extend hi.
		gins(ACMPL, &cx, ncon(32));
		p1 = gbranch(optoas(OLT, types[TUINT32]), T);
		gins(AMOVL, &dx, &ax);
		if(hi1.type->etype == TINT32) {
			gins(ASARL, &cx, &ax);	// SARL only uses bottom 5 bits of count
			gins(ASARL, ncon(31), &dx);
		} else {
			gins(ASHRL, &cx, &ax);
			gins(AXORL, &dx, &dx);
		}
		p2 = gbranch(AJMP, T);
		patch(p1, pc);

		// general shift
		p1 = gins(ASHRL, &cx, &ax);
		p1->from.index = D_DX;	// double-width shift
		p1->from.scale = 0;
		gins(optoas(ORSH, hi1.type), &cx, &dx);
		patch(p2, pc);
		break;

	case OXOR:
	case OAND:
	case OOR:
		// make constant the right side (it usually is anyway).
		if(lo1.op == OLITERAL) {
			nswap(&lo1, &lo2);
			nswap(&hi1, &hi2);
		}
		if(lo2.op == OLITERAL) {
			// special cases for constants.
			lv = mpgetfix(lo2.val.u.xval);
			hv = mpgetfix(hi2.val.u.xval);
			splitclean();	// right side
			split64(res, &lo2, &hi2);
			switch(n->op) {
			case OXOR:
				gmove(&lo1, &lo2);
				gmove(&hi1, &hi2);
				switch(lv) {
				case 0:
					break;
				case 0xffffffffu:
					gins(ANOTL, N, &lo2);
					break;
				default:
					gins(AXORL, ncon(lv), &lo2);
					break;
				}
				switch(hv) {
				case 0:
					break;
				case 0xffffffffu:
					gins(ANOTL, N, &hi2);
					break;
				default:
					gins(AXORL, ncon(hv), &hi2);
					break;
				}
				break;

			case OAND:
				switch(lv) {
				case 0:
					gins(AMOVL, ncon(0), &lo2);
					break;
				default:
					gmove(&lo1, &lo2);
					if(lv != 0xffffffffu)
						gins(AANDL, ncon(lv), &lo2);
					break;
				}
				switch(hv) {
				case 0:
					gins(AMOVL, ncon(0), &hi2);
					break;
				default:
					gmove(&hi1, &hi2);
					if(hv != 0xffffffffu)
						gins(AANDL, ncon(hv), &hi2);
					break;
				}
				break;

			case OOR:
				switch(lv) {
				case 0:
					gmove(&lo1, &lo2);
					break;
				case 0xffffffffu:
					gins(AMOVL, ncon(0xffffffffu), &lo2);
					break;
				default:
					gmove(&lo1, &lo2);
					gins(AORL, ncon(lv), &lo2);
					break;
				}
				switch(hv) {
				case 0:
					gmove(&hi1, &hi2);
					break;
				case 0xffffffffu:
					gins(AMOVL, ncon(0xffffffffu), &hi2);
					break;
				default:
					gmove(&hi1, &hi2);
					gins(AORL, ncon(hv), &hi2);
					break;
				}
				break;
			}
			splitclean();
			splitclean();
			goto out;
		}
		gins(AMOVL, &lo1, &ax);
		gins(AMOVL, &hi1, &dx);
		gins(optoas(n->op, lo1.type), &lo2, &ax);
		gins(optoas(n->op, lo1.type), &hi2, &dx);
		break;
	}
	if(is64(r->type))
		splitclean();
	splitclean();

	split64(res, &lo1, &hi1);
	gins(AMOVL, &ax, &lo1);
	gins(AMOVL, &dx, &hi1);
	splitclean();

out:;
}

/*
 * generate comparison of nl, nr, both 64-bit.
 * nl is memory; nr is constant or memory.
 */
void
cmp64(Node *nl, Node *nr, int op, Prog *to)
{
	Node lo1, hi1, lo2, hi2, rr;
	Prog *br;
	Type *t;

	split64(nl, &lo1, &hi1);
	split64(nr, &lo2, &hi2);

	// compare most significant word;
	// if they differ, we're done.
	t = hi1.type;
	if(nl->op == OLITERAL || nr->op == OLITERAL)
		gins(ACMPL, &hi1, &hi2);
	else {
		regalloc(&rr, types[TINT32], N);
		gins(AMOVL, &hi1, &rr);
		gins(ACMPL, &rr, &hi2);
		regfree(&rr);
	}
	br = P;
	switch(op) {
	default:
		fatal("cmp64 %O %T", op, t);
	case OEQ:
		// cmp hi
		// jne L
		// cmp lo
		// jeq to
		// L:
		br = gbranch(AJNE, T);
		break;
	case ONE:
		// cmp hi
		// jne to
		// cmp lo
		// jne to
		patch(gbranch(AJNE, T), to);
		break;
	case OGE:
	case OGT:
		// cmp hi
		// jgt to
		// jlt L
		// cmp lo
		// jge to (or jgt to)
		// L:
		patch(gbranch(optoas(OGT, t), T), to);
		br = gbranch(optoas(OLT, t), T);
		break;
	case OLE:
	case OLT:
		// cmp hi
		// jlt to
		// jgt L
		// cmp lo
		// jle to (or jlt to)
		// L:
		patch(gbranch(optoas(OLT, t), T), to);
		br = gbranch(optoas(OGT, t), T);
		break;
	}

	// compare least significant word
	t = lo1.type;
	if(nl->op == OLITERAL || nr->op == OLITERAL)
		gins(ACMPL, &lo1, &lo2);
	else {
		regalloc(&rr, types[TINT32], N);
		gins(AMOVL, &lo1, &rr);
		gins(ACMPL, &rr, &lo2);
		regfree(&rr);
	}

	// jump again
	patch(gbranch(optoas(op, t), T), to);

	// point first branch down here if appropriate
	if(br != P)
		patch(br, pc);

	splitclean();
	splitclean();
}

