/*               
 * 68K/386 32-bit C compiler.
 *
 * copyright (c) 1997, David Lindauer
 * 
 * This compiler is intended for educational use.  It may not be used
 * for profit without the express written consent of the author.
 *
 * It may be freely redistributed, as long as this notice remains intact
 * and either the original sources or derived sources 
 * are distributed along with any executables derived from the originals.
 *
 * The author is not responsible for any damages that may arise from use
 * of this software, either idirect or consequential.
 *
 * V1.85 July 2000
 * David Lindauer, camille@bluegrass.net
 *
 * Credits to Mathew Brandt for original K&R C compiler
 *
 */
/*
 * handle intrinsics
 */
#include        <stdio.h>
#include				<string.h>
#include        "lists.h"
#include        "expr.h"
#include        "c.h"
#include        "errors.h"
#include				"lists.h"
#include				"gen386.h"

#define KWINTRINSHASH 31

extern int prm_cmangle, prm_intrinsic;
extern int regs[3];
extern long nextlabel;

static HASHREC **hashtable=0;
static AMODE edxreg[] = { { am_dreg, EDX, 0, 0, 0, 4 } };
static AMODE alreg[] = { { am_dreg, EAX, 0, 0, 0, 1 } };
static AMODE clreg[] = { { am_dreg, ECX, 0, 0, 0, 1 } };
static AMODE axreg[] = { { am_dreg, EAX, 0, 0, 0, 2 } };
static AMODE dxreg[] = { { am_dreg, EDX, 0, 0, 0, 2 } };
static int pushedax,pushedcx;

static INTRINSICS intrinsic_table[] = {
	{ 0, "abs",1 },
	{ 0, "div",2 },
	{ 0, "ldiv",3 },
	{ 0, "_rotl",4 },
	{ 0, "_rotr", 5 },
	{ 0, "memcmp", 6 },
	{ 0, "memcpy", 7 },
	{ 0, "memmove", 8 },
	{ 0, "memset", 9 },
	{ 0, "strcat", 10 },
	{ 0, "strcmp", 11 },
	{ 0, "strcpy", 12 },
	{ 0, "strlen", 13 },
	{ 0, "strncat", 14 },
	{ 0, "strncmp", 15 },
	{ 0, "strncpy", 16 },
	{ 0, "memchr", 17 },
	{ 0, "strchr", 18 },
	{ 0, "strrchr", 19 },
	{ 0, "inportb",20 },
	{ 0, "inport",21 },
	{ 0, "inp", 22 },
	{ 0, "inpw", 23 },
	{ 0, "outportb", 24 },
	{ 0, "outport",25 },
	{ 0, "outp",26 },
	{ 0, "outpw", 27 },
	{ 0, "_crotl",28 },
	{ 0, "_crotr", 29 },
	{ 0, 0, 0 },
};
void IntrinsIni(void)
{
	INTRINSICS *q = intrinsic_table;
	if (!hashtable) {
		hashtable = (INTRINSICS **)malloc(KWINTRINSHASH * sizeof(INTRINSICS *));
		memset(hashtable,0,KWINTRINSHASH * sizeof(INTRINSICS *));
		if (!hashtable)
			fatal("Out of memory");
		while (q->word) {
			AddHash(q,hashtable,KWINTRINSHASH);
			q++;
		}
	}
}
void SearchIntrins(SYM *sp)
{
	HASHREC **q;
	char *p=sp->name;
	if (prm_cmangle)
		p++;
	q = LookupHash(p,hashtable,KWINTRINSHASH);
	if (!q) {
		gensymerror(ERR_NOINTRINSFOUND,sp->name);
		sp->tp->cflags &= ~DF_INTRINS;
		return;
	}
	sp->value.i = ((INTRINSICS *)(*q))->val;
}
static int oneparm(ENODE *parms)
{
	if (!parms)
		return 0;
	return 1;
}
static int twoparms(ENODE *parms)
{
	if (!parms || !parms->v.p[1])
		return 0;
	return 1;
}
static int threeparms(ENODE *parms)
{
	if (!parms || !parms->v.p[1] || !parms->v.p[1]->v.p[1])
		return 0;
	return 1;
}
static AMODE *roll(ENODE *parms, int size, int op)
{
	AMODE *ap1,*ap2,*ap3;
	int pushed = FALSE;
				if (!twoparms(parms))
					return 0;
				ap1 = gen_expr(parms->v.p[1]->v.p[0],0,0,4);
				do_extend(ap1,size,F_DREG | F_VOL);
				ap2 = gen_expr(parms->v.p[0],0,0,0);
				if (ap2->mode != am_immed) {
					do_extend(ap2,4,0);
					if (ap1->mode == am_dreg && ap1->preg == ECX) {
						ap3 = temp_data();
						gen_code(op_mov,ap3,ap1);
						freeop(ap1);
						ap1 = ap3;
					}
					if (ap2->mode != am_dreg || ap2->preg != ECX) {
						if (regs[2]) {
							gen_push(ECX,am_dreg,0);
							pushed = TRUE;
						}
						gen_code(op_mov,makedreg(ECX),ap2);
						ap2 = clreg;
					}
				}
				ap1->length = size;
				gen_code(op,ap1,ap2);
				if (pushed)
					gen_pop(ECX,am_dreg,0);
				freeop(ap2);
				freeop(ap1);
				return ap1;
}
static void LoadSIDI(ENODE *parms, int backwards, int savecx)
{
	AMODE *ap1, *ap2,*ap3;
	pushedax = pushedcx = FALSE;
	ap2 = gen_expr(parms->v.p[0],FALSE,FALSE,0);                     
	noids(ap2);
	ap1 = gen_expr(parms->v.p[1]->v.p[0],FALSE,FALSE,0); 
	if (ap1->mode == am_indisp && ap1->preg == ESI ||
			(ap1->mode == am_indispscale && (ap1->preg == ESI || ap2->preg == ESI)) ||
			(ap1->mode == am_dreg && ap1->preg == ESI)) {
		ap3 = temp_data();
		gen_code(op_mov,ap3,ap1);
		ap1 = ap3;
		freeop(ap1);
	}
	gen_push(ESI,am_dreg,0);                                  
	gen_push(EDI,am_dreg,0);
	freeop(ap2);
	freeop(ap1);
	if (regs[1] && savecx) {
		gen_push(ECX,am_dreg,0);
		pushedcx = TRUE;
	}
	if (regs[0]) {
		pushedax = TRUE;
		gen_push(EAX,am_dreg,0);
	}
	gen_codes(op_mov,4,makedreg(ESI),ap2);		                  
	gen_codes(op_mov,4,makedreg(EDI),ap1);                     
	if (backwards)
		gen_code(op_xchg,makedreg(ESI),makedreg(EDI));
	gen_code(op_cld,0,0);
}
static void LoadDI(ENODE *parms, int savecx)
{
	AMODE *ap1;
	pushedax = pushedcx = FALSE;
	ap1 = gen_expr(parms->v.p[0],FALSE,FALSE,4);                     
	noids(ap1);
	freeop(ap1);
	gen_push(EDI,am_dreg,0);
	if (regs[1] && savecx) {
		gen_push(ECX,am_dreg,0);
		pushedcx = TRUE;
	}
	if (regs[0]) {
		gen_push(EAX,am_dreg,0);
		pushedax = TRUE;
	}
	gen_codes(op_mov,4,makedreg(EDI),ap1);                     
	gen_code(op_cld,0,0);
}
static AMODE *popDI(AMODE *ap, int tocx)
{
	AMODE *ap1=ap;
	if (pushedax) {
		gen_pop(EAX,am_dreg,0);
	}
	if (pushedcx)
		gen_pop(ECX,am_dreg,0);
	gen_pop(EDI,am_dreg,0);
	if (ap1 == 0)
		ap1 = clreg;
	return ap1;
}
static AMODE * popSIDI(AMODE *ap, int tocx)
{
	AMODE *ap1 = popDI(ap,tocx);
	gen_pop(ESI,am_dreg,0);
	return ap1;
}
static void skipdi(void)
{
	gen_code(op_sub,makedreg(EAX),makedreg(EAX));
	gen_code(op_mov,makedreg(ECX),make_immed(-1));
	gen_code(op_repnz,0,0);
	gen_code(op_scasb,0,0);
	gen_code(op_dec,makedreg(EDI),0);
}
static AMODE *xstrlen(int todec)
{
	AMODE *ap1;
	gen_code(op_sub,makedreg(EAX),makedreg(EAX));
	gen_code(op_mov,makedreg(ECX),make_immed(-1));
	gen_code(op_repnz,0,0);
	gen_code(op_scasb,0,0);
	gen_code(op_not,makedreg(ECX),0);
	if (todec)
		gen_code(op_dec,makedreg(ECX),0);
	ap1 = temp_data();
	if (ap1->preg != ECX)
		gen_code(op_mov,ap1,makedreg(ECX));
	return ap1;
}
static void xmove(void)
{
	gen_code(op_repnz,0,0);
	gen_code(op_movsb,0,0);
}
static void xset(void)
{
	gen_code(op_repnz,0,0);
	gen_code(op_stosb,0,0);
}
static AMODE * xstrcmp(void)
{
	int neg=nextlabel++,exit=nextlabel++;
	AMODE *ap1 = temp_data();
	gen_code(op_repz,0,0);
	gen_code(op_cmpsb,0,0);
	gen_code(op_mov,ap1,make_immed(0));
	gen_code(op_je,make_label(exit),0);
	gen_code(op_js,make_label(neg),0);
	gen_code(op_mov,ap1,make_immed(1));
	gen_code(op_jmp,make_label(exit),0);
	gen_label(neg);
	gen_code(op_mov,ap1,make_immed(-1));
	gen_label(exit);
	return ap1;
}
	
AMODE *inport(ENODE *parms, int size)
{
	AMODE *ap1;
	int pushed = FALSE, pushedax = FALSE;
	if (!oneparm(parms))
		return 0;
	ap1 = gen_expr(parms->v.p[0],0,0,2);
	if (ap1->mode != am_dreg || ap1->preg != EDX) {
		freeop(ap1);
		if (regs[2]) {
			pushed = TRUE;
			gen_push(EDX,am_dreg,0);
		}
		gen_codes(op_mov,2,makedreg(EDX),ap1);
	}
	if (regs[0]) {
		pushedax = TRUE;
		gen_push(EAX,am_dreg,0);
	}
	switch (size) {
		case 1:
			gen_code(op_in,alreg,dxreg);
			gen_code(op_movzx,makedreg(EAX),alreg);
			break;
		case 2:
			gen_code(op_in,axreg,dxreg);
			gen_code(op_movzx,makedreg(EAX),axreg);
			break;
	}
	if (pushedax) {
		ap1 = temp_data();
		gen_code(op_mov,ap1,makedreg(EAX));
		gen_pop(EAX,am_dreg,0);
	}
	else {
		regs[0]++;
		ap1 = makedreg(EAX);
	}
	if (pushed)
		gen_pop(EDX, am_dreg,0);
	return ap1;
}
AMODE *outport(ENODE *parms, int size)
{
	AMODE *ap1,*ap2;
	int pushed = FALSE, pushedax = FALSE;
	if (!twoparms(parms))
		return 0;
	ap2 = gen_expr(parms->v.p[0],FALSE,FALSE,size);
	if (regs[0] && (ap2->mode != am_dreg || ap2->preg != EAX)) {
		pushedax = TRUE;
		gen_push(EAX,am_dreg,0);
		regs[0]--;
	} else
		do_extend(ap2,size,F_DREG | F_VOL);
	ap1 = gen_expr(parms->v.p[1]->v.p[0],FALSE,FALSE,2);
	if (ap1->mode != am_dreg || ap1->preg != EDX) {
		freeop(ap1);
		if (regs[2]) {
			pushed = TRUE;
			gen_push(EDX,am_dreg,0);
		}
		gen_codes(op_mov,2,makedreg(EDX),ap1);
	}
	switch (size) {
		case 1:
			gen_code(op_out,dxreg,alreg);
			break;
		case 2:
			gen_code(op_out,dxreg,axreg);
			break;
	}
	if (pushed)
		gen_pop(EDX, am_dreg,0);
	if (pushedax) {
		gen_pop(EAX,am_dreg,0);
		regs[0]++;
	}
	return ap1;
}
AMODE *HandleIntrins(ENODE *node, int novalue)
{
	ENODE *parms = node->v.p[1]->v.p[1]->v.p[0];
	AMODE *ap1,*ap2,*ap3;	
	SYM *sp;
	int xchg = FALSE;
	if (prm_intrinsic 
   	    && ( node->v.p[1]->v.p[0]->nodetype == en_nacon || node->v.p[1]->v.p[0]->nodetype == en_napccon )
				&&( (sp = node->v.p[1]->v.p[0]->v.sp)->tp->cflags & DF_INTRINS)) {
		switch(sp->value.i) {
			case 1 : /* abs */
				if (!oneparm(parms))
					return 0;
				ap1 = gen_expr(parms->v.p[0],0,0,4);
				do_extend(ap1,ap1->length,F_DREG | F_VOL);
				if (ap1->preg != EAX) {
					gen_code(op_xchg,ap1,makedreg(EAX));
					xchg = TRUE;
				}
				if (regs[2])
					gen_push(EDX,am_dreg,0);
				gen_code(op_cdq,0,0);
				gen_code(op_xor,makedreg(EAX),edxreg);
				gen_code(op_sub,makedreg(EAX),edxreg);
				if (regs[2])
					gen_pop(EDX,am_dreg,0);
				if (xchg)
					gen_code(op_xchg,ap1,makedreg(EAX));
				return ap1;
			case 2 : /* div */
			case 3 : /* ldiv */
				break;
			case 4 : /* _rotl */
				return roll(parms,4,op_rol);
			case 5 : /* _rotr */
				return roll(parms,4,op_ror);
			case 6 : /* memcmp */
				if (!threeparms(parms))
					return 0;
				regs[0]++;
				ap1 = gen_expr(parms->v.p[0],FALSE,FALSE,4);
				do_extend(ap1,4,F_DREG | F_VOL);
				regs[0]--;
				if (ap1->preg != ECX)
					gen_code(op_mov,makedreg(ECX),ap1);
				LoadSIDI(parms->v.p[1],TRUE,FALSE);
				freeop(ap1);
				ap1 = xstrcmp();
				
				return popSIDI(ap1,0);
			case 7 : /* memcpy */
				if (!threeparms(parms))
					return 0;
				regs[0]++;
				ap1 = gen_expr(parms->v.p[0],FALSE,FALSE,4);
				do_extend(ap1,4,F_DREG | F_VOL);
				regs[0]--;
				if (ap1->preg != ECX)
					gen_code(op_mov,makedreg(ECX),ap1);
				LoadSIDI(parms->v.p[1],FALSE,FALSE);
				freeop(ap1);
				xmove();
				
				return popSIDI(0,0);
			case 8 : /* memmove */
				break;
			case 9 : /* memset */
				if (!threeparms(parms))
					return 0;
				ap2 = gen_expr(parms->v.p[1]->v.p[0],FALSE,FALSE,4);
				do_extend(ap2,4,F_DREG | F_VOL);
				if (ap2->preg != EAX)
					gen_code(op_mov,makedreg(EAX),ap2);
				regs[0]++;
				ap1 = gen_expr(parms->v.p[0],FALSE,FALSE,4);
				do_extend(ap1,4,F_DREG | F_VOL);
				if (ap1->preg != ECX)
					gen_code(op_mov,makedreg(ECX),ap1);
				regs[0]--;
				LoadDI(parms->v.p[1]->v.p[1],FALSE);
				freeop(ap1);
				freeop(ap2);
				xset();
				return popDI(0,0);
			case 10 : /* strcat */
				if (!twoparms(parms))
					return 0;
				LoadSIDI(parms,FALSE,TRUE);
				skipdi();
				gen_code(op_xchg,makedreg(ESI),makedreg(EDI));
				gen_push(EDI,am_dreg,0);
				xstrlen(FALSE);
				gen_pop(EDI,am_dreg,0);
				gen_code(op_xchg,makedreg(ESI),makedreg(EDI));
				xmove();
				return popSIDI(0,0);
			case 11 : /* strcmp */
				if (!twoparms(parms))
					return 0;
				LoadSIDI(parms,TRUE,TRUE);
				gen_push(EDI,am_dreg,0);
				xstrlen(FALSE);
				gen_pop(EDI,am_dreg,0);
				ap1 = xstrcmp();
				return popSIDI(ap1,FALSE);
			case 12 : /* strcpy */
				if (!twoparms(parms))
					return 0;
				LoadSIDI(parms,TRUE,TRUE);
				gen_push(EDI,am_dreg,0);
				xstrlen(FALSE);
				gen_pop(EDI,am_dreg,0);
				gen_code(op_xchg,makedreg(ESI),makedreg(EDI));
				xmove();
				return popSIDI(0,0);
			case 13 : /* strlen */
				if (!oneparm(parms))
					return 0;
				LoadDI(parms,FALSE);
				ap1 = xstrlen(TRUE);
				return popDI(ap1,TRUE);
			case 14 : /* strncat */
			case 15 : /* strncmp */
			case 16 : /* strncpy */
			case 17 : /* memchr */
			case 18 : /* strchr */
			case 19 : /* strrchr */
				break;
			case 20 : /* inportb */
				return inport(parms,1);
			case 21 : /* inport */
				return inport(parms,2);
			case 22 : /* inp */
				return inport(parms,1);
			case 23 : /* inpw */
				return inport(parms,2);
			case 24 : /* outportb */
				return outport(parms,1);
			case 25 : /* outport */
				return outport(parms,2);
			case 26 : /* outp */
				return outport(parms,1);
			case 27 : /* outpw */
				return outport(parms,2);
			case 28 : /* _crotl */
				return roll(parms,1,op_rol);
			case 29 : /* _crotr */
				return roll(parms,1,op_ror);
		}
	}
	if ( node->v.p[1]->v.p[0]->nodetype == en_nacon || node->v.p[1]->v.p[0]->nodetype == en_napccon )
		node->v.p[1]->v.p[0]->v.sp->tp->cflags &= ~ DF_INTRINS;
	return 0;
}
