// Inferno's libkern/vlrt-arm.c
// http://code.google.com/p/inferno-os/source/browse/libkern/vlrt-arm.c
//
//         Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
//         Revisions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com).  All rights reserved.
//         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 "../../cmd/ld/textflag.h"

// declared here to avoid include of runtime.h
void	runtime·panicstring(char*);
void	runtime·panicdivide(void);

typedef unsigned long   ulong;
typedef unsigned int    uint;
typedef unsigned short  ushort;
typedef unsigned char   uchar;
typedef signed char     schar;

#define SIGN(n) (1UL<<(n-1))

typedef struct  Vlong   Vlong;
struct  Vlong
{
	union
	{
		struct
		{
			ulong   lo;
			ulong   hi;
		};
		struct
		{
			ushort lols;
			ushort loms;
			ushort hils;
			ushort hims;
		};
	};
};

void    runtime·abort(void);

#pragma textflag NOSPLIT
void
_addv(Vlong *r, Vlong a, Vlong b)
{
	r->lo = a.lo + b.lo;
	r->hi = a.hi + b.hi;
	if(r->lo < a.lo)
		r->hi++;
}

#pragma textflag NOSPLIT
void
_subv(Vlong *r, Vlong a, Vlong b)
{
	r->lo = a.lo - b.lo;
	r->hi = a.hi - b.hi;
	if(r->lo > a.lo)
		r->hi--;
}

void
_d2v(Vlong *y, double d)
{
	union { double d; struct Vlong; } x;
	ulong xhi, xlo, ylo, yhi;
	int sh;

	x.d = d;

	xhi = (x.hi & 0xfffff) | 0x100000;
	xlo = x.lo;
	sh = 1075 - ((x.hi >> 20) & 0x7ff);

	ylo = 0;
	yhi = 0;
	if(sh >= 0) {
		/* v = (hi||lo) >> sh */
		if(sh < 32) {
			if(sh == 0) {
				ylo = xlo;
				yhi = xhi;
			} else {
				ylo = (xlo >> sh) | (xhi << (32-sh));
				yhi = xhi >> sh;
			}
		} else {
			if(sh == 32) {
				ylo = xhi;
			} else
			if(sh < 64) {
				ylo = xhi >> (sh-32);
			}
		}
	} else {
		/* v = (hi||lo) << -sh */
		sh = -sh;
		if(sh <= 11) {
			ylo = xlo << sh;
			yhi = (xhi << sh) | (xlo >> (32-sh));
		} else {
			/* overflow */
			yhi = d;        /* causes something awful */
		}
	}
	if(x.hi & SIGN(32)) {
		if(ylo != 0) {
			ylo = -ylo;
			yhi = ~yhi;
		} else
			yhi = -yhi;
	}

	y->hi = yhi;
	y->lo = ylo;
}

void
_f2v(Vlong *y, float f)
{
	_d2v(y, f);
}

void
runtime·float64toint64(double d, Vlong y)
{
	_d2v(&y, d);
}

void
runtime·float64touint64(double d, Vlong y)
{
	_d2v(&y, d);
}

double
_ul2d(ulong u)
{
	// compensate for bug in c
	if(u & SIGN(32)) {
		u ^= SIGN(32);
		return 2147483648. + u;
	}
	return u;
}

double
_v2d(Vlong x)
{
	if(x.hi & SIGN(32)) {
		if(x.lo) {
			x.lo = -x.lo;
			x.hi = ~x.hi;
		} else
			x.hi = -x.hi;
		return -(_ul2d(x.hi)*4294967296. + _ul2d(x.lo));
	}
	return x.hi*4294967296. + _ul2d(x.lo);
}

float
_v2f(Vlong x)
{
	return _v2d(x);
}

void
runtime·int64tofloat64(Vlong y, double d)
{
	d = _v2d(y);
	USED(&d); // FLUSH
}

void
runtime·uint64tofloat64(Vlong y, double d)
{
	d = _ul2d(y.hi)*4294967296. + _ul2d(y.lo);
	USED(&d); // FLUSH
}

static void
dodiv(Vlong num, Vlong den, Vlong *q, Vlong *r)
{
	ulong numlo, numhi, denhi, denlo, quohi, quolo, t;
	int i;

	numhi = num.hi;
	numlo = num.lo;
	denhi = den.hi;
	denlo = den.lo;

	/*
	 * get a divide by zero
	 */
	if(denlo==0 && denhi==0) {
		runtime·panicdivide();
	}

	/*
	 * set up the divisor and find the number of iterations needed
	 */
	if(numhi >= SIGN(32)) {
		quohi = SIGN(32);
		quolo = 0;
	} else {
		quohi = numhi;
		quolo = numlo;
	}
	i = 0;
	while(denhi < quohi || (denhi == quohi && denlo < quolo)) {
		denhi = (denhi<<1) | (denlo>>31);
		denlo <<= 1;
		i++;
	}

	quohi = 0;
	quolo = 0;
	for(; i >= 0; i--) {
		quohi = (quohi<<1) | (quolo>>31);
		quolo <<= 1;
		if(numhi > denhi || (numhi == denhi && numlo >= denlo)) {
			t = numlo;
			numlo -= denlo;
			if(numlo > t)
				numhi--;
			numhi -= denhi;
			quolo |= 1;
		}
		denlo = (denlo>>1) | (denhi<<31);
		denhi >>= 1;
	}

	if(q) {
		q->lo = quolo;
		q->hi = quohi;
	}
	if(r) {
		r->lo = numlo;
		r->hi = numhi;
	}
}

void
_divvu(Vlong *q, Vlong n, Vlong d)
{

	if(n.hi == 0 && d.hi == 0) {
		q->hi = 0;
		q->lo = n.lo / d.lo;
		return;
	}
	dodiv(n, d, q, 0);
}

void
runtime·uint64div(Vlong n, Vlong d, Vlong q)
{
	_divvu(&q, n, d);
}

void
_modvu(Vlong *r, Vlong n, Vlong d)
{

	if(n.hi == 0 && d.hi == 0) {
		r->hi = 0;
		r->lo = n.lo % d.lo;
		return;
	}
	dodiv(n, d, 0, r);
}

void
runtime·uint64mod(Vlong n, Vlong d, Vlong q)
{
	_modvu(&q, n, d);
}

static void
vneg(Vlong *v)
{

	if(v->lo == 0) {
		v->hi = -v->hi;
		return;
	}
	v->lo = -v->lo;
	v->hi = ~v->hi;
}

void
_divv(Vlong *q, Vlong n, Vlong d)
{
	long nneg, dneg;

	if(n.hi == (((long)n.lo)>>31) && d.hi == (((long)d.lo)>>31)) {
		if((long)n.lo == -0x80000000 && (long)d.lo == -1) {
			// special case: 32-bit -0x80000000 / -1 causes wrong sign
			q->lo = 0x80000000;
			q->hi = 0;
			return;
		}
		q->lo = (long)n.lo / (long)d.lo;
		q->hi = ((long)q->lo) >> 31;
		return;
	}
	nneg = n.hi >> 31;
	if(nneg)
		vneg(&n);
	dneg = d.hi >> 31;
	if(dneg)
		vneg(&d);
	dodiv(n, d, q, 0);
	if(nneg != dneg)
		vneg(q);
}

void
runtime·int64div(Vlong n, Vlong d, Vlong q)
{
	_divv(&q, n, d);
}

void
_modv(Vlong *r, Vlong n, Vlong d)
{
	long nneg, dneg;

	if(n.hi == (((long)n.lo)>>31) && d.hi == (((long)d.lo)>>31)) {
		r->lo = (long)n.lo % (long)d.lo;
		r->hi = ((long)r->lo) >> 31;
		return;
	}
	nneg = n.hi >> 31;
	if(nneg)
		vneg(&n);
	dneg = d.hi >> 31;
	if(dneg)
		vneg(&d);
	dodiv(n, d, 0, r);
	if(nneg)
		vneg(r);
}

void
runtime·int64mod(Vlong n, Vlong d, Vlong q)
{
	_modv(&q, n, d);
}

void
_rshav(Vlong *r, Vlong a, int b)
{
	long t;

	t = a.hi;
	if(b >= 32) {
		r->hi = t>>31;
		if(b >= 64) {
			/* this is illegal re C standard */
			r->lo = t>>31;
			return;
		}
		r->lo = t >> (b-32);
		return;
	}
	if(b <= 0) {
		r->hi = t;
		r->lo = a.lo;
		return;
	}
	r->hi = t >> b;
	r->lo = (t << (32-b)) | (a.lo >> b);
}

void
_rshlv(Vlong *r, Vlong a, int b)
{
	ulong t;

	t = a.hi;
	if(b >= 32) {
		r->hi = 0;
		if(b >= 64) {
			/* this is illegal re C standard */
			r->lo = 0;
			return;
		}
		r->lo = t >> (b-32);
		return;
	}
	if(b <= 0) {
		r->hi = t;
		r->lo = a.lo;
		return;
	}
	r->hi = t >> b;
	r->lo = (t << (32-b)) | (a.lo >> b);
}

#pragma textflag NOSPLIT
void
_lshv(Vlong *r, Vlong a, int b)
{
	if(b >= 32) {
		r->lo = 0;
		if(b >= 64) {
			/* this is illegal re C standard */
			r->hi = 0;
			return;
		}
		r->hi = a.lo << (b-32);
		return;
	}
	if(b <= 0) {
		r->lo = a.lo;
		r->hi = a.hi;
		return;
	}
	r->lo = a.lo << b;
	r->hi = (a.lo >> (32-b)) | (a.hi << b);
}

void
_andv(Vlong *r, Vlong a, Vlong b)
{
	r->hi = a.hi & b.hi;
	r->lo = a.lo & b.lo;
}

void
_orv(Vlong *r, Vlong a, Vlong b)
{
	r->hi = a.hi | b.hi;
	r->lo = a.lo | b.lo;
}

void
_xorv(Vlong *r, Vlong a, Vlong b)
{
	r->hi = a.hi ^ b.hi;
	r->lo = a.lo ^ b.lo;
}

void
_vpp(Vlong *l, Vlong *r)
{

	l->hi = r->hi;
	l->lo = r->lo;
	r->lo++;
	if(r->lo == 0)
		r->hi++;
}

void
_vmm(Vlong *l, Vlong *r)
{

	l->hi = r->hi;
	l->lo = r->lo;
	if(r->lo == 0)
		r->hi--;
	r->lo--;
}

void
_ppv(Vlong *l, Vlong *r)
{

	r->lo++;
	if(r->lo == 0)
		r->hi++;
	l->hi = r->hi;
	l->lo = r->lo;
}

void
_mmv(Vlong *l, Vlong *r)
{

	if(r->lo == 0)
		r->hi--;
	r->lo--;
	l->hi = r->hi;
	l->lo = r->lo;
}

void
_vasop(Vlong *ret, void *lv, void fn(Vlong*, Vlong, Vlong), int type, Vlong rv)
{
	Vlong t, u;

	u = *ret;
	switch(type) {
	default:
		runtime·abort();
		break;

	case 1: /* schar */
		t.lo = *(schar*)lv;
		t.hi = t.lo >> 31;
		fn(&u, t, rv);
		*(schar*)lv = u.lo;
		break;

	case 2: /* uchar */
		t.lo = *(uchar*)lv;
		t.hi = 0;
		fn(&u, t, rv);
		*(uchar*)lv = u.lo;
		break;

	case 3: /* short */
		t.lo = *(short*)lv;
		t.hi = t.lo >> 31;
		fn(&u, t, rv);
		*(short*)lv = u.lo;
		break;

	case 4: /* ushort */
		t.lo = *(ushort*)lv;
		t.hi = 0;
		fn(&u, t, rv);
		*(ushort*)lv = u.lo;
		break;

	case 9: /* int */
		t.lo = *(int*)lv;
		t.hi = t.lo >> 31;
		fn(&u, t, rv);
		*(int*)lv = u.lo;
		break;

	case 10:        /* uint */
		t.lo = *(uint*)lv;
		t.hi = 0;
		fn(&u, t, rv);
		*(uint*)lv = u.lo;
		break;

	case 5: /* long */
		t.lo = *(long*)lv;
		t.hi = t.lo >> 31;
		fn(&u, t, rv);
		*(long*)lv = u.lo;
		break;

	case 6: /* ulong */
		t.lo = *(ulong*)lv;
		t.hi = 0;
		fn(&u, t, rv);
		*(ulong*)lv = u.lo;
		break;

	case 7: /* vlong */
	case 8: /* uvlong */
		fn(&u, *(Vlong*)lv, rv);
		*(Vlong*)lv = u;
		break;
	}
	*ret = u;
}

void
_p2v(Vlong *ret, void *p)
{
	long t;

	t = (ulong)p;
	ret->lo = t;
	ret->hi = 0;
}

void
_sl2v(Vlong *ret, long sl)
{
	long t;

	t = sl;
	ret->lo = t;
	ret->hi = t >> 31;
}

void
_ul2v(Vlong *ret, ulong ul)
{
	long t;

	t = ul;
	ret->lo = t;
	ret->hi = 0;
}

#pragma textflag NOSPLIT
void
_si2v(Vlong *ret, int si)
{
	ret->lo = (long)si;
	ret->hi = (long)si >> 31;
}

void
_ui2v(Vlong *ret, uint ui)
{
	long t;

	t = ui;
	ret->lo = t;
	ret->hi = 0;
}

void
_sh2v(Vlong *ret, long sh)
{
	long t;

	t = (sh << 16) >> 16;
	ret->lo = t;
	ret->hi = t >> 31;
}

void
_uh2v(Vlong *ret, ulong ul)
{
	long t;

	t = ul & 0xffff;
	ret->lo = t;
	ret->hi = 0;
}

void
_sc2v(Vlong *ret, long uc)
{
	long t;

	t = (uc << 24) >> 24;
	ret->lo = t;
	ret->hi = t >> 31;
}

void
_uc2v(Vlong *ret, ulong ul)
{
	long t;

	t = ul & 0xff;
	ret->lo = t;
	ret->hi = 0;
}

long
_v2sc(Vlong rv)
{
	long t;

	t = rv.lo & 0xff;
	return (t << 24) >> 24;
}

long
_v2uc(Vlong rv)
{

	return rv.lo & 0xff;
}

long
_v2sh(Vlong rv)
{
	long t;

	t = rv.lo & 0xffff;
	return (t << 16) >> 16;
}

long
_v2uh(Vlong rv)
{

	return rv.lo & 0xffff;
}

long
_v2sl(Vlong rv)
{

	return rv.lo;
}

long
_v2ul(Vlong rv)
{

	return rv.lo;
}

#pragma textflag NOSPLIT
long
_v2si(Vlong rv)
{

	return rv.lo;
}

long
_v2ui(Vlong rv)
{

	return rv.lo;
}

int
_testv(Vlong rv)
{
	return rv.lo || rv.hi;
}

int
_eqv(Vlong lv, Vlong rv)
{
	return lv.lo == rv.lo && lv.hi == rv.hi;
}

int
_nev(Vlong lv, Vlong rv)
{
	return lv.lo != rv.lo || lv.hi != rv.hi;
}

int
_ltv(Vlong lv, Vlong rv)
{
	return (long)lv.hi < (long)rv.hi ||
		(lv.hi == rv.hi && lv.lo < rv.lo);
}

int
_lev(Vlong lv, Vlong rv)
{
	return (long)lv.hi < (long)rv.hi ||
		(lv.hi == rv.hi && lv.lo <= rv.lo);
}

int
_gtv(Vlong lv, Vlong rv)
{
	return (long)lv.hi > (long)rv.hi ||
		(lv.hi == rv.hi && lv.lo > rv.lo);
}

#pragma textflag NOSPLIT
int
_gev(Vlong lv, Vlong rv)
{
	return (long)lv.hi > (long)rv.hi ||
		(lv.hi == rv.hi && lv.lo >= rv.lo);
}

int
_lov(Vlong lv, Vlong rv)
{
	return lv.hi < rv.hi ||
		(lv.hi == rv.hi && lv.lo < rv.lo);
}

int
_lsv(Vlong lv, Vlong rv)
{
	return lv.hi < rv.hi ||
		(lv.hi == rv.hi && lv.lo <= rv.lo);
}

int
_hiv(Vlong lv, Vlong rv)
{
	return lv.hi > rv.hi ||
		(lv.hi == rv.hi && lv.lo > rv.lo);
}

int
_hsv(Vlong lv, Vlong rv)
{
	return lv.hi > rv.hi ||
		(lv.hi == rv.hi && lv.lo >= rv.lo);
}
