/*
 * genexpr.c  -  Code generator for expressions
 *
 * Copyright (C) 1997-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: genexpr.c,v 1.15 2007/01/06 18:31:27 gkminix Exp $
 */

#include "mknbi.h"
#include "mgl.h"
#include "gencode.h"
#include "opcodes.h"



/*
 * Define which registers are presently used
 */
int si_inuse = 0;			/* SI register is in use */
int di_inuse = 0;			/* DI register is in use */
int bx_inuse = 0;			/* BX register is in use */
int cx_inuse = 0;			/* CX register is in use */
int dx_inuse = 0;			/* DX register is in use */




/*
 ************************************************************************
 *
 *                     Handle pushing registers
 *
 ************************************************************************
 */

/*
 * Register identifiers for the push/pop routines
 */
#define PUSH_BX		0x0001
#define PUSH_CX		0x0002
#define PUSH_DX		0x0004
#define PUSH_SI		0x0008
#define PUSH_DI		0x0010



/*
 * In order to be able to reset the inuse flags, we have to save the
 * old values in a linked list.
 */
struct pushinfo {
	int              old_bx_inuse;
	int              old_cx_inuse;
	int              old_dx_inuse;
	int              old_si_inuse;
	int              old_di_inuse;
	int             *inuseaddr;
	int             *inusesize;
	unsigned int     regspushed;
	struct pushinfo *next;
};

static struct pushinfo *pushstack = NULL;	/* The linked list */



/*
 * Push registers onto the stack
 */
static void pushregs __F((regs, dp), unsigned int regs AND struct meminfo *dp)
{
  struct pushinfo *pp;
  int *inuseaddr, *inusesize;

  /*
   * When calling a routine which recursively calls other routines AND
   * the result is going into a register-relative location, we have to
   * mark that register as used.
   */
  inuseaddr = NULL;
  if (dp != NULL && memaddr(dp) == MEM_RELATIVE) {
#ifdef PARANOID
	if ((dp->addr.r & REG_8BIT_FLAG) == REG_8BIT_FLAG)
		interror(89, "cannot use 8-bit-register for relative base");
#endif
	if (dp->addr.r == REG_BX)
		inuseaddr = &bx_inuse;
	else if (dp->addr.r == REG_CX)
		inuseaddr = &cx_inuse;
	else if (dp->addr.r == REG_DX)
		inuseaddr = &dx_inuse;
	else if (dp->addr.r == REG_SI)
		inuseaddr = &si_inuse;
	else if (dp->addr.r == REG_DI)
		inuseaddr = &di_inuse;
	if (inuseaddr != NULL)
		(*inuseaddr)++;
  }

  inusesize = NULL;
  if (dp != NULL && memsize(dp) == MEM_SIZEREG) {
#ifdef PARANOID
	if ((dp->size.r & REG_8BIT_FLAG) == REG_8BIT_FLAG)
		interror(90, "cannot use 8-bit-register for size value");
#endif
	if (dp->size.r == REG_BX)
		inusesize = &bx_inuse;
	else if (dp->size.r == REG_CX)
		inusesize = &cx_inuse;
	else if (dp->size.r == REG_DX)
		inusesize = &dx_inuse;
	else if (dp->size.r == REG_SI)
		inusesize = &si_inuse;
	else if (dp->size.r == REG_DI)
		inusesize = &di_inuse;
	if (inusesize != NULL)
		(*inusesize)++;
  }

  /* Get some memory for new push info */
  pp = (struct pushinfo *)nbmalloc(sizeof(struct pushinfo));
  if ((regs & PUSH_BX) == PUSH_BX && bx_inuse) {
	putpush(REG_BX);
	pp->regspushed |= PUSH_BX;
	pp->old_bx_inuse = bx_inuse;
	bx_inuse = 0;
  }
  if ((regs & PUSH_CX) == PUSH_CX && cx_inuse) {
	putpush(REG_CX);
	pp->regspushed |= PUSH_CX;
	pp->old_cx_inuse = cx_inuse;
	cx_inuse = 0;
  }
  if ((regs & PUSH_DX) == PUSH_DX && dx_inuse) {
	putpush(REG_DX);
	pp->regspushed |= PUSH_DX;
	pp->old_dx_inuse = dx_inuse;
	dx_inuse = 0;
  }
  if ((regs & PUSH_SI) == PUSH_SI && si_inuse) {
	putpush(REG_SI);
	pp->regspushed |= PUSH_SI;
	pp->old_si_inuse = si_inuse;
	si_inuse = 0;
  }
  if ((regs & PUSH_DI) == PUSH_DI && di_inuse) {
	putpush(REG_DI);
	pp->regspushed |= PUSH_DI;
	pp->old_di_inuse = di_inuse;
	di_inuse = 0;
  }

  pp->inuseaddr = inuseaddr;
  pp->inusesize = inusesize;
  pp->next = pushstack;
  pushstack = pp;
}



/*
 * Pop registers from the stack. The order has to be reverse than in the
 * push routine!
 */
static void popregs __F_NOARGS
{
  struct pushinfo *pp;

#ifdef PARANOID
  if (pushstack == NULL)
	interror(79, "invalid push stack");
#endif

  pp = pushstack;
  if ((pp->regspushed & PUSH_DI) == PUSH_DI) {
	putpop(REG_DI);
	di_inuse = pp->old_di_inuse;
  }
  if ((pp->regspushed & PUSH_SI) == PUSH_SI) {
	putpop(REG_SI);
	si_inuse = pp->old_si_inuse;
  }
  if ((pp->regspushed & PUSH_DX) == PUSH_DX) {
	putpop(REG_DX);
	dx_inuse = pp->old_dx_inuse;
  }
  if ((pp->regspushed & PUSH_CX) == PUSH_CX) {
	putpop(REG_CX);
	cx_inuse = pp->old_cx_inuse;
  }
  if ((pp->regspushed & PUSH_BX) == PUSH_BX) {
	putpop(REG_BX);
	bx_inuse = pp->old_bx_inuse;
  }

  if (pp->inuseaddr != NULL)
	(*(pp->inuseaddr))--;
  if (pp->inusesize != NULL)
	(*(pp->inusesize))--;

  pushstack = pp->next;
  free(pp);
}




/*
 ************************************************************************
 *
 *                 Routines for handling integer values
 *
 ************************************************************************
 */

/*
 * Save an integer value in a register into the memory location or
 * register given by the destination info record. This routine will
 * handle different register and destination sizes.
 */
static void putsaveintreg __F((srcreg, dp),
				unsigned int srcreg AND
				struct meminfo *dp)
{
  struct typesdef *tp;
  unsigned int src8bit, dest8bit, tmpreg;
  int destsigned = issigned(dp->t);

  /* Just for safety */
  if (dp == NULL || memaddr(dp) == MEM_NOADDR)
	return;

  /*
   * If we have a scalar subclass, and the subclass limits are other than the
   * standard limits (which are cared for by the expression handling routines)
   * we have to add some code to check for overflows. For pointer we are unable
   * to check for limits at compile time.
   */
  tp = NULL;
  if (dp->t != NULL)
	switch (dp->t->type) {
		case EXPR_BOOL:
			tp = &bool_type;
			break;
		case EXPR_NUM:
			tp = &int_type;
			break;
		case EXPR_CHAR:
			tp = &char_type;
			break;
		case EXPR_ENUM:
			/*
			 * This is special: the max value of 'none_type' is
			 * zero and therefore always different from the max
			 * found in the target enum type.
			 */
			tp = &none_type;
			break;
		default:
			break;
	}
  if (tp != NULL && isscalar(dp->t) &&
      (dp->t->def.s.min != tp->def.s.min || dp->t->def.s.max != tp->def.s.max))
	putbound(srcreg, dp->t);

  /* Check for 8 bit transfers */
  src8bit = (srcreg & REG_8BIT_FLAG);
  if (memaddr(dp) == MEM_REGISTER)
	dest8bit = dp->addr.r & REG_8BIT_FLAG;
  else if (dp->t == NULL)
	dest8bit = src8bit;
  else
	dest8bit = (dp->t->size > 1 ? 0 : REG_8BIT_FLAG);
  if (!src8bit && dest8bit) {
	/* Use only lower 8 bits of source register */
	srcreg = (srcreg & REG_8BIT_MASK) | REG_LOW_MASK | REG_8BIT_FLAG;
	src8bit = REG_8BIT_FLAG;
  }

  /* Save register */
  switch (memaddr(dp)) {
	case MEM_REGISTER:
		if (src8bit && !dest8bit) {
			putcode(OP_386);
			putregop((destsigned ? OP_MOVSX : OP_MOVZX),
						srcreg, dp->addr.r, 0);
		} else if (srcreg != dp->addr.r)
			putregop(OP_MOV, srcreg, dp->addr.r, 0);
		break;
	case MEM_ABSOLUTE:
		tmpreg = REG_16DISP | dest8bit;
		if ((dp->memtype & MEM_USERELOC) == MEM_USERELOC)
			tmpreg |= REG_RELOC_FLAG;
		if (src8bit && !dest8bit) {
			putcode(OP_386);
			putregop((destsigned ? OP_MOVSX : OP_MOVZX),
						srcreg, tmpreg, dp->addr.i);
		} else
			putregop(OP_MOV, srcreg, tmpreg, dp->addr.i);
		break;
	case MEM_RELATIVE:
		if (dp->addr.r == REG_BP ||
		    dp->addr.r == REG_BX ||
		    dp->addr.r == REG_DI ||
		    dp->addr.r == REG_SI) {
			unsigned int disp0, disp8, disp16;

			switch (dp->addr.r) {
				case REG_BP:
					disp0 = 0;
					disp8 = REG_BP_8DISP | dest8bit;
					disp16 = REG_BP_16DISP | dest8bit;
					break;
				case REG_DI:
					disp0 = REG_DI_0DISP | dest8bit;
					disp8 = REG_DI_8DISP | dest8bit;
					disp16 = REG_DI_16DISP | dest8bit;
					break;
				case REG_SI:
					disp0 = REG_SI_0DISP | dest8bit;
					disp8 = REG_SI_8DISP | dest8bit;
					disp16 = REG_SI_16DISP | dest8bit;
					break;
				case REG_BX:
					disp0 = REG_BX_0DISP | dest8bit;
					disp8 = REG_BX_8DISP | dest8bit;
					disp16 = REG_BX_16DISP | dest8bit;
					break;
				default:
					disp0 = 0;
					disp8 = 0;
					disp16 = 0;
					break;
			}
			if (disp0 != 0 && dp->addr.i == 0) {
				if (src8bit && !dest8bit) {
					putcode(OP_386);
					putregop((destsigned ? OP_MOVSX : OP_MOVZX),
						srcreg, disp0, 0);
				} else
					putregop(OP_MOV, srcreg, disp0, 0);
			} else if (disp8 != 0 && is8bitofs(dp->addr.i)) {
				if (src8bit && !dest8bit) {
					putcode(OP_386);
					putregop((destsigned ? OP_MOVSX : OP_MOVZX),
						srcreg, disp8, dp->addr.i);
				} else
					putregop(OP_MOV,
						srcreg, disp8, dp->addr.i);
			} else if (disp16 != 0) {
				if (src8bit && !dest8bit) {
					putcode(OP_386);
					putregop((destsigned ? OP_MOVSX : OP_MOVZX),
						srcreg, disp16, dp->addr.i);
				} else
					putregop(OP_MOV,
						srcreg, disp16, dp->addr.i);
			}
		} else if (regcmp(srcreg, REG_BX)) {
			tmpreg = REG_BP_8DISP | dest8bit;
			putpush(REG_BP);
			putlea(REG_BP, dp->addr.r, dp->addr.i);
			if (src8bit && !dest8bit) {
				putcode(OP_386);
				putregop((destsigned ? OP_MOVSX : OP_MOVZX),
							srcreg, tmpreg, 0);
			} else
				putregop(OP_MOV, srcreg, tmpreg, 0);
			putpop(REG_BP);
		} else {
			tmpreg = REG_BX_0DISP | dest8bit;
			if (bx_inuse)
				putpush(REG_BX);
			putlea(REG_BX, dp->addr.r, dp->addr.i);
			if (src8bit && !dest8bit) {
				putcode(OP_386);
				putregop((destsigned ? OP_MOVSX : OP_MOVZX),
							srcreg, tmpreg, 0);
			} else
				putregop(OP_MOV, srcreg, tmpreg, 0);
			if (bx_inuse)
				putpop(REG_BX);
		}
		break;
	case MEM_NOADDR:
		/* Expression doesn't return a value */
		break;
  }
}



/*
 * Save a constant integer value into the memory location or register
 * given by the destination info record.
 */
static void putsaveintconst __F((val, use8bit, dp),
				long val AND
				int use8bit AND
				struct meminfo *dp)
{
  unsigned int dest8bit;
  unsigned int tmpreg;

  /* Just for safety */
  if (dp == NULL)
	return;

  /* Expression doesn't return a value */
  if (memaddr(dp) == MEM_NOADDR)
	return;

  /*
   * If we have a scalar subclass, we have to check for a correct constant
   * value.
   */
  if (dp->t != NULL && isscalar(dp->t) &&
      (val < dp->t->def.s.min || val > dp->t->def.s.max)) {
	warning("scalar out of bounds");
	val = (val < dp->t->def.s.min ? dp->t->def.s.min : dp->t->def.s.max);
  }

  /* Determine size of destination */
  if (memaddr(dp) == MEM_REGISTER)
	dest8bit = dp->addr.r & REG_8BIT_FLAG;
  else if (dp->t == NULL)
	dest8bit = (use8bit ? REG_8BIT_FLAG : 0);
  else
	dest8bit = (dp->t->size > 1 ? 0 : REG_8BIT_FLAG);

  /* Now write the code for moving the constant value into the destination */
  switch (memaddr(dp)) {
	case MEM_REGISTER:
		putimmed(OP_MOV, dp->addr.r, val, 0);
		break;
	case MEM_ABSOLUTE:
		tmpreg = REG_16DISP | dest8bit;
		if ((dp->memtype & MEM_USERELOC) == MEM_USERELOC)
			tmpreg |= REG_RELOC_FLAG;
		putimmed(OP_MOV, tmpreg, val, dp->addr.i);
		break;
	case MEM_RELATIVE:
		if (dp->addr.r == REG_BP ||
		    dp->addr.r == REG_BX ||
		    dp->addr.r == REG_DI ||
		    dp->addr.r == REG_SI) {
			unsigned int disp0, disp8, disp16;

			switch (dp->addr.r) {
				case REG_BP:
					disp0 = 0;
					disp8 = REG_BP_8DISP | dest8bit;
					disp16 = REG_BP_16DISP | dest8bit;
					break;
				case REG_DI:
					disp0 = REG_DI_0DISP | dest8bit;
					disp8 = REG_DI_8DISP | dest8bit;
					disp16 = REG_DI_16DISP | dest8bit;
					break;
				case REG_SI:
					disp0 = REG_SI_0DISP | dest8bit;
					disp8 = REG_SI_8DISP | dest8bit;
					disp16 = REG_SI_16DISP | dest8bit;
					break;
				case REG_BX:
					disp0 = REG_BX_0DISP | dest8bit;
					disp8 = REG_BX_8DISP | dest8bit;
					disp16 = REG_BX_16DISP | dest8bit;
					break;
				default:
					disp0 = 0;
					disp8 = 0;
					disp16 = 0;
					break;
			}
			if (disp0 != 0 && dp->addr.i == 0)
				putimmed(OP_MOV, disp0, val, 0);
			else if (disp8 != 0 && is8bitofs(dp->addr.i))
				putimmed(OP_MOV, disp8, val, dp->addr.i);
			else if (disp16 != 0)
				putimmed(OP_MOV, disp16, val, dp->addr.i);
		} else {
			tmpreg = REG_BX_0DISP | dest8bit;
			if (bx_inuse)
				putpush(REG_BX);
			putlea(REG_BX, dp->addr.r, dp->addr.i);
			putimmed(OP_MOV, tmpreg, val, 0);
			if (bx_inuse)
				putpop(REG_BX);
		}
		break;
  }
}



/*
 * Output an integer operation opcode into code segment
 * This will do an 'op dest,src'
 */
static void putintop __F((op, src, dest, constval),
				int op AND
				struct meminfo *src AND
				struct meminfo *dest AND
				long constval)
{
  unsigned int srcreg, destreg, tmpreg;
  int dest8bit;
  int ispushed = FALSE;

#ifdef PARANOID
  if (memaddr(src) != MEM_REGISTER || memaddr(dest) != MEM_REGISTER)
	interror(85, "invalid operands for integer operation");
#endif

  srcreg = src->addr.r;
  destreg = dest->addr.r;
  dest8bit = ((destreg & REG_8BIT_FLAG) == REG_8BIT_FLAG);
  switch (op) {
	case '+':
		/* add dest,src */
		if (srcreg == REG_NONE)
			putimmed(OP_IMMED_ADD, destreg, constval, 0);
		else
			putregop(OP_ADD, srcreg, destreg, 0);
		putcode(OP_INTO);
		break;
	case '-':
		/* sub dest,src */
		if (srcreg == REG_NONE)
			putimmed(OP_IMMED_SUB, destreg, constval, 0);
		else
			putregop(OP_SUB, srcreg, destreg, 0);
		putcode(OP_INTO);
		break;
	case '*':
		/* imul src */
		if (srcreg == REG_NONE && !dest8bit) {
			if (constval < -127 || constval > 127) {
				putcode(OP_IMUL_IMM16);
				putcode(OP_MOD_REG | rmmid(destreg) |
							rmlow(destreg));
				putint(constval, FALSE);
			} else {
				putcode(OP_IMUL_IMM8);
				putcode(OP_MOD_REG | rmmid(destreg) |
							rmlow(destreg));
				putcode((byte_t)(constval & 0xff));
			}
			putcode(OP_INTO);
			break;
		}
		if (srcreg == REG_NONE) {
			srcreg = (dest8bit ? REG_CL : REG_CX);
			if (cx_inuse && !regcmp(destreg, REG_CX)) {
				putpush(REG_CX);		/* push cx */
				ispushed = TRUE;
			}
			putimmed(OP_MOV, srcreg, constval, 0);
		}
		if (dx_inuse && !regcmp(destreg, REG_DX))
			putpush(REG_DX);			/* push dx */
		tmpreg = (dest8bit ? REG_AL : REG_AX);
		putregop(OP_MOV, destreg, tmpreg, 0);		/* mov ax,dest */
		putcode(OP_NONIM | (dest8bit ? 0 : OP_WORD_MASK));
		putcode(OP_NONIM_IMUL | OP_MOD_REG | rmlow(srcreg));
		putregop(OP_MOV, tmpreg, destreg, 0);		/* mov dest,ax */
		if (dx_inuse && !regcmp(destreg, REG_DX))
			putpop(REG_DX);				/* pop dx */
		if (ispushed)
			putpop(REG_CX);				/* pop cx */
		putcode(OP_INTO);
		break;
	case '/':
		/* idiv src */
		if (srcreg == REG_NONE || regcmp(srcreg, REG_DX)) {
			if (cx_inuse && !regcmp(destreg, REG_CX)) {
				putpush(REG_CX);		/* push cx */
				ispushed = TRUE;
			}
			tmpreg = (dest8bit ? REG_CL : REG_CX);
			if (srcreg == REG_NONE)
				putimmed(OP_MOV, tmpreg, constval, 0);
			else					/* mov cx,src */
				putregop(OP_MOV, srcreg, tmpreg, 0);
			srcreg = tmpreg;
		}
		if (dx_inuse && !dest8bit && !regcmp(destreg, REG_DX))
			putpush(REG_DX);			/* push dx */
		tmpreg = (dest8bit ? REG_AL : REG_AX);
		putregop(OP_MOV, destreg, tmpreg, 0);		/* mov ax,dest */
		putcode(dest8bit ? OP_CBW : OP_CWD);		/* cwd */
		putcode(OP_NONIM | (dest8bit ? 0 : OP_WORD_MASK));
		putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(srcreg));
		putregop(OP_MOV, tmpreg, destreg, 0);		/* mov dest,ax */
		if (dx_inuse && !dest8bit && !regcmp(destreg, REG_DX))
			putpop(REG_DX);				/* pop dx */
		if (ispushed)
			putpop(REG_CX);				/* pop cx */
		break;
	case '%':
		/* imod src */
		if (srcreg == REG_NONE || regcmp(srcreg, REG_DX)) {
			if (cx_inuse && !regcmp(destreg, REG_CX)) {
				putpush(REG_CX);		/* push cx */
				ispushed = TRUE;
			}
			tmpreg = (dest8bit ? REG_CL : REG_CX);
			if (srcreg == REG_NONE)
				putimmed(OP_MOV, tmpreg, constval, 0);
			else					/* mov cx,src */
				putregop(OP_MOV, srcreg, tmpreg, 0);
			srcreg = tmpreg;
		}
		if (dx_inuse && !dest8bit && !regcmp(destreg, REG_DX))
			putpush(REG_DX);			/* push dx */
		tmpreg = (dest8bit ? REG_AL : REG_AX);
		putregop(OP_MOV, destreg, tmpreg, 0);		/* mov ax,dest */
		putcode(dest8bit ? OP_CBW : OP_CWD);		/* cwd */
		putcode(OP_NONIM | (dest8bit ? 0 : OP_WORD_MASK));
		putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(srcreg));
		tmpreg = (dest8bit ? REG_AH : REG_DX);
		putregop(OP_MOV, tmpreg, destreg, 0);		/* mov dest,dx */
		if (dx_inuse && !dest8bit && !regcmp(destreg, REG_DX))
			putpop(REG_DX);				/* pop dx */
		if (ispushed)
			putpop(REG_CX);				/* pop cx */
		break;
	case CMD_OR:
		/* or dest,src */
		if (srcreg == REG_NONE)
			putimmed(OP_IMMED_OR, destreg, constval, 0);
		else
			putregop(OP_OR, srcreg, destreg, 0);
		break;
	case CMD_XOR:
		/* xor dest,src */
		if (srcreg == REG_NONE)
			putimmed(OP_IMMED_XOR, destreg, constval, 0);
		else
			putregop(OP_XOR, srcreg, destreg, 0);
		break;
	case CMD_AND:
		/* and dest,src */
		if (srcreg == REG_NONE)
			putimmed(OP_IMMED_AND, destreg, constval, 0);
		else
			putregop(OP_AND, srcreg, destreg, 0);
		break;
	case CMD_CMP:
		/* cmp dest,src */
		if (srcreg == REG_NONE)
			putimmed(OP_IMMED_CMP, destreg, constval, 0);
		else
			putregop(OP_CMP, srcreg, destreg, 0);
		break;
#ifdef PARANOID
	default:
		interror(88, "invalid scalar operation");
		break;
#endif
  }
}




/*
 ************************************************************************
 *
 *                  Scalar expression handling
 *
 ************************************************************************
 */

/*
 * Handle scalar and pointer leaf nodes
 */
static void putleafscalar __F((ep, dp),
				struct expr *ep AND
				struct meminfo *dp)
{
  struct meminfo si;
  unsigned int modrm = REG_NONE;
  unsigned int tempreg;
  int srcsigned = issigned(ep->type);
  int src8bit = (exprsize(ep) == 1);
  int temp8bit;

  /* Check if the expression returns a value */
  if (dp == NULL || memaddr(dp) == MEM_NOADDR)
	return;

  /* Handle symbols */
  if (isvariable(ep)) {
	/* Handle a variable */
	setmeminfo(ep, &si, REG_NONE, REG_NONE);
	if (memaddr(dp) == MEM_REGISTER)
		tempreg = dp->addr.r;
	else
		tempreg = (src8bit ? REG_AL : REG_AX);
	temp8bit = ((tempreg & REG_8BIT_FLAG) != 0);
	if (memaddr(&si) == MEM_RELATIVE)
		modrm = getmodrm(si.addr.r, si.addr.i);
	else if (memaddr(&si) == MEM_ABSOLUTE) {
		modrm = REG_16DISP;
		if ((si.memtype & MEM_USERELOC) == MEM_USERELOC)
			modrm |= REG_RELOC_FLAG;
	}
	if (modrm != REG_NONE) {
		if (src8bit && !temp8bit) {
			putcode(OP_386);
			putregop((srcsigned ? OP_MOVSX : OP_MOVZX),
				modrm | REG_8BIT_FLAG, tempreg, si.addr.i);
		} else if (!src8bit && temp8bit) {
			putregop(OP_MOV,
				modrm | REG_8BIT_FLAG, tempreg, si.addr.i);
		} else
			putregop(OP_MOV, modrm, tempreg, si.addr.i);
	} else {
		modrm = (!src8bit && temp8bit ? REG_8BIT_FLAG : 0);
		if (tempreg != REG_BX) {
			modrm |= REG_BX_0DISP;
			if (bx_inuse)
				putpush(REG_BX);
			putlea(REG_BX, si.addr.r, si.addr.i);
			if (src8bit && !temp8bit) {
				putcode(OP_386);
				putregop((srcsigned ? OP_MOVSX : OP_MOVZX),
						modrm, tempreg, 0);
			} else
				putregop(OP_MOV, modrm, tempreg, 0);
			if (bx_inuse)
				putpop(REG_BX);
		} else {
			modrm |= REG_SI_0DISP;
			if (si_inuse)
				putpush(REG_SI);
			putlea(REG_SI, si.addr.r, si.addr.i);
			if (src8bit && !temp8bit) {
				putcode(OP_386);
				putregop((srcsigned ? OP_MOVSX : OP_MOVZX),
						modrm, tempreg, 0);
			} else
				putregop(OP_MOV, modrm, tempreg, 0);
			if (si_inuse)
				putpop(REG_SI);
		}
	}
	putsaveintreg(tempreg, dp);
  } else if (isconst(ep)) {
	/* Handle a constant value */
	switch (exprtype(ep)) {
		case EXPR_NUM:
			putsaveintconst((long)(ep->spec.cval.val.i),
								src8bit, dp);
			break;
		case EXPR_CHAR:
			putsaveintconst((long)(ep->spec.cval.val.c),
								src8bit, dp);
			break;
		case EXPR_BOOL:
			putsaveintconst((long)(ep->spec.cval.val.b),
								src8bit, dp);
			break;
		case EXPR_ENUM:
			putsaveintconst((long)(ep->spec.cval.val.e),
								src8bit, dp);
			break;
		case EXPR_POINTER:
			putsaveintconst((long)(ep->spec.cval.val.p),
								src8bit, dp);
			break;
#ifdef PARANOID
		default:
			interror(92, "invalid type in numerical leaf node");
			break;
#endif
	}
  }
#ifdef PARANOID
  else
	interror(54, "expected leaf node in numerical expression");
#endif
}



/*
 * Generate a unary scalar operation
 */
static void putunaryscalar __F((ep, dp),
				struct expr *ep AND
				struct meminfo *dp)
{
  struct meminfo di;
  unsigned int tmpreg;
  int use8bit;

  /* Determine destination for subexpression code */
  if (memaddr(dp) != MEM_REGISTER) {
	di.memtype = MEM_REGISTER;
	di.addr.r = (exprsize(ep) < 2 ? REG_AL : REG_AX);
	di.t = NULL;
  } else {
	di = *dp;
	di.t = NULL;
  }
  use8bit = ((di.addr.r & REG_8BIT_FLAG) == REG_8BIT_FLAG);

  /* For CMD_ORD we have to deal with different types in the subtree */
  if (ep->opcode == CMD_ORD) {
#ifdef PARANOID
	if (exprtype(ep) != EXPR_NUM)
		interror(86, "invalid result type for ORD command");
#endif
	if (use8bit) {
		di.addr.r = REG_AL;
		putscalarexpr(ep->left, &di);
		putregop(OP_XOR, REG_AH, REG_AH, 0);
		di.addr.r = REG_AX;
	} else
		putscalarexpr(ep->left, &di);
	putsaveintreg(di.addr.r, dp);
	return;
  }

  /* The CHR command is special as well */
  if (ep->opcode == CMD_CHR) {
#ifdef PARANOID
	if (exprtype(ep) != EXPR_CHAR)
		interror(87, "invalid result type for CHR command");
#endif
	if ((di.addr.r & REG_SPEC_FLAG) == REG_SPEC_FLAG)
		di.addr.r = REG_AL;
	else
		di.addr.r = (di.addr.r & REG_8BIT_MASK) |
					REG_LOW_MASK | REG_8BIT_FLAG;
	putscalarexpr(ep->left, &di);
	putsaveintreg(di.addr.r, dp);
	return;
  }

  /* Handle scalar numerical expression */
  putscalarexpr(ep->left, &di);
  switch (ep->opcode) {
	case '-':
		putcode(OP_NONIM | (use8bit? 0 : OP_WORD_MASK));
		putcode(OP_NONIM_NEG | OP_MOD_REG | rmlow(di.addr.r));
		break;
	case CMD_NOT:
		if (exprtype(ep) == EXPR_BOOL)
			putimmed(OP_IMMED_XOR, di.addr.r, 0x01, 0);
		else {
			putcode(OP_NONIM | (use8bit? 0 : OP_WORD_MASK));
			putcode(OP_NONIM_NOT | OP_MOD_REG | rmlow(di.addr.r));
		}
		break;
	case CMD_ODD:
		putimmed(OP_IMMED_AND, di.addr.r, 0x0001, 0);
		break;
	case CMD_PRED:
		putincdec(di.addr.r, FALSE);
		putcode(OP_INTO);
		break;
	case CMD_SUCC:
		putincdec(di.addr.r, TRUE);
		putcode(OP_INTO);
		break;
	case CMD_ABS:
		putregop(OP_OR, di.addr.r, di.addr.r, 0);
		putjmp(codeptr + 4, LABEL_NULL, JMP_JNS);
		putcode(OP_NONIM | (use8bit? 0 : OP_WORD_MASK));
		putcode(OP_NONIM_NEG | OP_MOD_REG | rmlow(di.addr.r));
		break;
	case CMD_SQR:
		if (dx_inuse && !use8bit && !regcmp(di.addr.r, REG_DX))
			putpush(REG_DX);
		tmpreg = (use8bit ? REG_AL : REG_AX);
		putregop(OP_MOV, di.addr.r, tmpreg, 0);
		putcode(OP_NONIM | (use8bit? 0 : OP_WORD_MASK));
		putcode(OP_NONIM_IMUL | OP_MOD_REG | rmlow(di.addr.r));
		putregop(OP_MOV, tmpreg, di.addr.r, 0);
		if (dx_inuse && !use8bit && !regcmp(di.addr.r, REG_DX))
			putpop(REG_DX);
		putcode(OP_INTO);
		break;
#ifdef PARANOID
	default:
		interror(52, "invalid unary operation for numerical expression");
#endif
  }
  putsaveintreg(di.addr.r, dp);
}



/*
 * Comparison
 */
static void putcmp __F((ep, dp), struct expr *ep AND struct meminfo *dp)
{
  struct meminfo di;
  unsigned int jmpop;
  int oldopcode;

#ifdef PARANOID
  if (ep->exprnum != 2 || exprtype(ep) != EXPR_BOOL ||
      exprtype(ep->left) != exprtype(ep->right))
	interror(96, "invalid comparison");
#endif

  /* Determine jump code */
  switch (ep->opcode) {
	default:
	case CMD_EQ:
		jmpop = JMP_JZ;
		break;
	case CMD_GT:
		jmpop = JMP_JG;
		break;
	case CMD_GE:
		jmpop = JMP_JGE;
		break;
	case CMD_LT:
		jmpop = JMP_JL;
		break;
	case CMD_LE:
		jmpop = JMP_JLE;
		break;
	case CMD_NE:
		jmpop = JMP_JNZ;
		break;
  }

  /*
   * Comparisons use a special opcode CMD_CMP, which will not be returned
   * by the parser. Additionally we set no return value address, so
   * that no code will be produced which might interfere with the flags.
   */
  oldopcode = ep->opcode;
  ep->opcode = CMD_CMP;
  di.memtype = MEM_NOADDR | MEM_NOSIZE;
  di.t = NULL;
  putscalarexpr(ep, &di);
  ep->opcode = oldopcode;

  /* Now check the resulting flags */
  if (memaddr(dp) == MEM_REGISTER)
	putsetbit(dp->addr.r, jmpop);
  else if (memaddr(dp) != MEM_NOADDR) {
	putsetbit(REG_AL, jmpop);
	putsaveintreg(REG_AL, dp);
  }
}



/*
 * Convert a numeric expression. This also handles enumeration expressions
 * and pointers.
 */
void putscalarexpr __F((ep, dp), struct expr *ep AND struct meminfo *dp)
{
  struct meminfo di1, di2;
  struct expr *tmpexpr;
  unsigned int tempreg, dopush;
  long constval;
  int *inuseptr, use8bit;

  /* Just as a safety measure */
  if (ep == NULL)
	return;

  /* Expression is a function */
  if (isfunc(ep)) {
	putproc(ep, dp);
	return;
  }
#ifdef PARANOID
  if (isproc(ep))
	interror(42, "procedure not allowed in expression");
#endif

  /* Expression is a leaf node */
  if (isleaf(ep)) {
	putleafscalar(ep, dp);
	return;
  }

  /* Handle unary expression */
  if (ep->exprnum != 2) {
#ifdef PARANOID
	if (ep->exprnum != 1 || exprtype(ep) == EXPR_POINTER)
		interror(56, "invalid unary expression");
#endif
	putunaryscalar(ep, dp);
	return;
  }

  /* Comparisons are special because they involve different expression types */
  if (iscmdcond(ep)) {
	putcmp(ep, dp);
	return;
  }

  /*
   * Check if any subexpression is constant, so that we can simplify the
   * resulting code.
   */
  if (isconst(ep->left) || isconst(ep->right)) {
#ifdef PARANOID
	if (isconst(ep->left) && isconst(ep->right))
		interror(67, "expression tree not collapsed properly");
#endif

	/*
	 * Handle some special expression cases for integer values:
	 *   1.)  num / 2  -->  shr(num,1)
	 *   2.)  num * 2  -->  shl(num,1)
	 */
	if (exprtype(ep) == EXPR_NUM) {

		/* Case (1): num / 2 --> shr(num,1) */
		if (ep->opcode == '/' &&
		    (isconst(ep->right) && ep->right->spec.cval.val.i == 2)) {
			if (memaddr(dp) != MEM_REGISTER) {
				di1.memtype = MEM_REGISTER;
				di1.addr.r = REG_AX;
				di1.t = NULL;
				putscalarexpr(ep->left, &di1);
				putcode(OP_SHIFT_1 | OP_WORD_MASK);
				putcode(OP_MOD_REG | OP_SHIFT_SHR |
							rmlow(REG_AX));
				putsaveintreg(REG_AX, dp);
			} else {
				putscalarexpr(ep->left, dp);
				putcode(OP_SHIFT_1 | OP_WORD_MASK);
				putcode(OP_MOD_REG | OP_SHIFT_SHR |
							rmlow(dp->addr.r));
			}
			return;
		}

		/* Case (2): num * 2 --> shl(num,1) */
		if (ep->opcode == '*' &&
		    (isconst(ep->right) && ep->right->spec.cval.val.i == 2)) {
			if (memaddr(dp) != MEM_REGISTER) {
				di1.memtype = MEM_REGISTER;
				di1.addr.r = REG_AX;
				di1.t = NULL;
				putscalarexpr(ep->left, &di1);
				putcode(OP_SHIFT_1 | OP_WORD_MASK);
				putcode(OP_MOD_REG | OP_SHIFT_SHL |
							rmlow(REG_AX));
				putsaveintreg(REG_AX, dp);
			} else {
				putscalarexpr(ep->left, dp);
				putcode(OP_SHIFT_1 | OP_WORD_MASK);
				putcode(OP_MOD_REG | OP_SHIFT_SHL |
							rmlow(dp->addr.r));
			}
			return;
		}
	}

	/*
	 * If one of the expressions is constant, we can use special opcodes
	 * which deal with immediate values. However, this will only work if
	 * the right tree is constant.
	 */
	if (isconst(ep->right) || (iscmdcommut(ep) && isconst(ep->left))) {
		if (isconst(ep->left)) {
			tmpexpr = ep->right;
			ep->right = ep->left;
			ep->left = tmpexpr;
		}
		switch (exprtype(ep->right)) {
			case EXPR_NUM:
				constval = (long)(ep->right->spec.cval.val.i);
				break;
			case EXPR_ENUM:
				constval = (long)(ep->right->spec.cval.val.e);
				break;
			case EXPR_CHAR:
				constval = (long)(ep->right->spec.cval.val.c);
				break;
			case EXPR_BOOL:
				constval = (long)(ep->right->spec.cval.val.b);
				break;
			case EXPR_POINTER:
				constval = (long)(ep->right->spec.cval.val.p);
				break;
			default:
				constval = 0;
				break;
		}
		di2.t = NULL;
		di2.memtype = MEM_REGISTER;
		di2.addr.r = REG_NONE;
		if (memaddr(dp) != MEM_REGISTER) {
			di1.memtype = MEM_REGISTER;
			di1.addr.r = (exprsize(ep->left) == 1 ?
							REG_AL : REG_AX);
			di1.t = NULL;
			putscalarexpr(ep->left, &di1);
			putintop(ep->opcode, &di2, &di1, constval);
			putsaveintreg(di1.addr.r, dp);
		} else {
			putscalarexpr(ep->left, dp);
			putintop(ep->opcode, &di2, dp, constval);
		}
		return;
	}
  }

  /*
   * Try to find out which register we can use as temporary storage. First
   * use the general purpose registers, then the string pointer registers,
   * and finally DX. DX has to come last because it is used with multipli-
   * cation and division and with every such operation has to be saved on
   * the stack if it's used.
   */
  use8bit = (exprsize(ep->left) == 1);
  if (memaddr(dp) != MEM_REGISTER) {
	di1.memtype = MEM_REGISTER;
	di1.addr.r = (use8bit ? REG_AL : REG_AX);
	di1.t = NULL;
	tempreg = (memaddr(dp) == MEM_RELATIVE ? dp->addr.r : REG_NONE);
  } else {
	di1 = *dp;
	di1.t = NULL;
	tempreg = dp->addr.r;
  }
  dopush = 0;
  di2.t = NULL;
  di2.memtype = MEM_REGISTER;
  if (!bx_inuse && !regcmp(tempreg, REG_BX)) {
	inuseptr = &bx_inuse;
	di2.addr.r = (use8bit ? REG_BL : REG_BX);
  } else if (!cx_inuse && !regcmp(tempreg, REG_CX)) {
	inuseptr = &cx_inuse;
	di2.addr.r = (use8bit ? REG_CL : REG_CX);
  } else if (!si_inuse && !use8bit && tempreg != REG_SI) {
	inuseptr = &si_inuse;
	di2.addr.r = REG_SI;
  } else if (!di_inuse && !use8bit && tempreg != REG_DI) {
	inuseptr = &di_inuse;
	di2.addr.r = REG_DI;
  } else if (!dx_inuse && !regcmp(tempreg, REG_DX)) {
	inuseptr = &dx_inuse;
	di2.addr.r = (use8bit ? REG_DL : REG_DX);
  } else if (!regcmp(tempreg, REG_BX)) {
	/* No temporary register available and tempreg is not BX */
	dopush = PUSH_BX;
	inuseptr = &bx_inuse;
	di2.addr.r = (use8bit ? REG_BL : REG_BX);
  } else {
	/* No temporary register available and destreg is BX */
	dopush = PUSH_CX;
	inuseptr = &cx_inuse;
	di2.addr.r = (use8bit ? REG_CL : REG_CX);
  }

  /*
   * Handle binary expression. It seems that it would be better to
   * first calculate the left subtree (which is always longer than
   * the right subtree). However, the left subtree has to be computed
   * into AX or the destination register directly because of require-
   * ments of the putintop() function - and we would always have to
   * save AX onto the stack. When computing the right subtree first,
   * chances are that we don't need to save anything onto the stack.
   * Therefore, even though not obvious it is much better to first
   * compute the right subtree, and then the left one.
   */
  pushregs(dopush, NULL);
  putscalarexpr(ep->right, &di2);		/* mov di2,expr */
  (*inuseptr)++;
  putscalarexpr(ep->left, &di1);		/* mov di1,expr */
  (*inuseptr)--;
  putintop(ep->opcode, &di2, &di1, 0);		/* op di1,di2 */
  popregs();
  putsaveintreg(di1.addr.r, dp);
}




/*
 ************************************************************************
 *
 *                    Non-scalar expression handling
 *
 ************************************************************************
 */

/*
 * Handle leaf complex nodes. Determining where the value is coming from
 * is a bit easier than with scalar data types because if it has been
 * passed to a function, we always have a pointer to the variable on the
 * stack, regardless if it's passed by value or reference (this distinction
 * has to be made by the caller by providing a temporary variable space if
 * the variable has to be passed by value).
 */
static void putleafcomplex __F((ep, dp),
				struct expr *ep AND
				struct meminfo *dp)
{
  size_t varsize;
  struct meminfo si;

  /* Expression doesn't return a value */
  if (memaddr(dp) == MEM_NOADDR)
	return;

  /* Determine the size of the complex constant */
  if (isconst(ep) && exprtype(ep) == EXPR_STRING)
	varsize = (size_t)(ep->spec.cval.val.s[0]) + 1;
  else
	varsize = exprsize(ep);

  /* Just return the adress of the variable in the given register */
  if (memaddr(dp) == MEM_REGISTER) {
	if (isvariable(ep))
		setmeminfo(ep, &si, dp->addr.r, REG_NONE);
	else if (isconst(ep)) {
#ifdef PARANOID
		if (ep->spec.cval.val.m == NULL)
			interror(111, "no complex constant defined in leaf node");
#endif
		si.memtype = MEM_ABSOLUTE | MEM_IMMEDIATE;
		si.size.i = varsize;
		si.addr.i = putmemblock(ep->spec.cval.val.m, varsize);
	}
#ifdef PARANOID
	else
		interror(113, "unable to put complex expression into register");
#endif
	putldaddr(dp->addr.r, &si);
	return;
  }

  /*
   * We have to copy the string/record into the destination. For this
   * we move the source address into SI and the destination address into
   * DI.
   */
  pushregs(PUSH_SI | PUSH_DI | PUSH_CX, dp);
  if (isvariable(ep))
	setmeminfo(ep, &si, REG_SI, REG_NONE);
  else if (isconst(ep)) {
#ifdef PARANOID
	if (ep->spec.cval.val.m == NULL)
		interror(84, "no complex constant defined in leaf node");
#endif
	si.memtype = MEM_ABSOLUTE | MEM_IMMEDIATE;
	si.size.i = varsize;
	si.addr.i = putmemblock(ep->spec.cval.val.m, varsize);
  }
#ifdef PARANOID
  else
	interror(116, "invalid storage type of complex expression");
#endif
  putldaddr(REG_SI, &si);				/* mov si,#src */
  putldaddr(REG_DI, dp);				/* mov di,#dest */

  /* Move the copy size into CX */
  if (exprtype(ep) == EXPR_STRING) {
	putcode(OP_386);
	putregop(OP_MOVZX, REG_SI_0DISP | REG_8BIT_FLAG,
					REG_AX, 0);	/* movzx ax,[si] */
	putincdec(REG_SI, TRUE);			/* inc si */
	if (memsize(dp) == MEM_IMMEDIATE) {
		putimmed(OP_IMMED_CMP, REG_AX, (dp->size.i - 1), 0);
		putjmp(codeptr + 5, LABEL_NULL, JMP_JBE);
		putimmed(OP_MOV, REG_AX, (dp->size.i - 1), 0);
	} else {
		putincdec(REG_AX, TRUE);
		putregop(OP_CMP, dp->size.r, REG_AX, 0);
		putjmp(codeptr + 4, LABEL_NULL, JMP_JBE);
		putregop(OP_MOV, dp->size.r, REG_AX, 0);
		putincdec(REG_AX, FALSE);
	}
	putcode(OP_STOSB);				/* stosb */
	putregop(OP_MOV, REG_AX, REG_CX, 0);		/* mov cx,ax */
  } else {
#ifdef PARANOID
	if (memsize(&si) != MEM_IMMEDIATE ||
	    memsize(dp) != MEM_IMMEDIATE ||
	    si.size.i != dp->size.i)
		interror(72, "invalid sizes in complex leaf node assignment");
#endif
	putimmed(OP_MOV, REG_CX, dp->size.i, 0);	/* mov cx,size */
  }

  /* Now actually generate the copy code */
  putcode(OP_REP);					/* rep */
  putcode(OP_MOVSB);					/* movsb */
  popregs();
}



/*
 * Handle complex expressions with record or arrays. The only such
 * operations are an assignment and function call.
 */
void putcomplexexpr __F((ep, dp), struct expr *ep AND struct meminfo *dp)
{
  /* Just a safety measure */
  if (ep == NULL)
	return;

  /* Expression is a function */
  if (isfunc(ep)) {
	putproc(ep, dp);
	return;
  }
#ifdef PARANOID
  if (isproc(ep))
	interror(114, "procedure not allowed in expression");
  if (!isleaf(ep))
	interror(115, "invalid operation in complex expression");
#endif

  /* Expression is a leaf node */
  putleafcomplex(ep, dp);
}




/*
 ************************************************************************
 *
 *                     Call a function or procedure
 *
 ************************************************************************
 */

/*
 * Push the address of a variable onto the stack
 */
static void pushvaraddr __F((ep), struct expr *ep)
{
  struct meminfo si;
  unsigned int reg;

  setmeminfo(ep, &si, REG_NONE, REG_NONE);
  if (memaddr(&si) == MEM_ABSOLUTE) {
	/* String is constant or static */
	putimmedpush(si.addr.i,
			((si.memtype & MEM_USERELOC) == MEM_USERELOC));
  } else if (memaddr(&si) == MEM_RELATIVE) {
	reg = (si.addr.r == REG_BP ? REG_AX : si.addr.r);
	putlea(reg, si.addr.r, si.addr.i);
	putpush(reg);
  }
#ifdef PARANOID
  else
	interror(112, "invalid variable memory type");
#endif
}



/*
 * Put a function or procedure call into the code segment.
 */
void putproc __F((ep, dp), struct expr *ep AND struct meminfo *dp)
{
  basictypes curtype;
  struct sym *sp = NULL;
  struct expr *curexpr;
  struct meminfo di;
  int retpushed;
  int i;

  /* Do some preliminary checks */
  if (isfunc(ep) || isproc(ep))
	sp = ep->spec.func;
#ifdef PARANOID
  if (sp == NULL || !isfuncsym(sp))
	interror(70, "invalid function call");
  if (ep->exprnum != sp->def.f.argnum)
	interror(30, "number of subexpressions doesn't match function prototype");
#endif

  /*
   * If we have used BX, CX or DX, they have to be saved because the
   * called procedure doesn't. We also have to save the base register
   * if the destination is relative.
   */
  pushregs(PUSH_BX | PUSH_CX | PUSH_DX, dp);

  /* Check for correct arguments */
#ifdef PARANOID
  for (i = 0; i < ep->exprnum; i++) {
	if (ep->exprlist[i] == NULL)
		interror(58, "NULL expression in argument list");
	if (sp->def.f.args[i].class == CLASS_REF &&
	    !isvariable(ep->exprlist[i]))
		interror(31, "passing a non-variable by reference");
	if (sp->def.f.args[i].class != CLASS_REF &&
	    sp->def.f.args[i].t->type == EXPR_ANY)
		interror(32, "passing an unknown type not by reference");
  }
#endif

  /*
   * If the function returns any type, we have to push the destination address
   * and size. However, scalars can be stored in registers for which we can't
   * determine an address. Therefore, we push the scalar value onto the stack
   * and use the address of the pushed value.
   */
  retpushed = FALSE;
  if (isanytype(sp->def.f.ret) && memaddr(dp) == MEM_REGISTER) {
#ifdef PARANOID
	if (!isscalar(ep->type) || ep->type->size > 2)
		interror(74, "invalid register return type");
#endif
	retpushed = TRUE;
	putpush(dp->addr.r);
  }

  /*
   * If the function returns a non-scalar, push the destination address and
   * size for the destination.
   */
  if (isnonscalar(sp->def.f.ret) || isanytype(sp->def.f.ret)) {
	/* push size */
	if (memsize(dp) == MEM_IMMEDIATE)
		putimmedpush(dp->size.i, FALSE);
	else if (memsize(dp) == MEM_SIZEREG)
		putpush(dp->size.r);
#ifdef PARANOID
	else
		interror(76, "invalid destination size");
#endif
	/* push addr */
	if (memaddr(dp) == MEM_ABSOLUTE) {
		putimmedpush(dp->addr.i,		/* push #dest */
			((dp->memtype & MEM_USERELOC) == MEM_USERELOC));
	} else if (memaddr(dp) == MEM_RELATIVE) {
		putlea(REG_AX, dp->addr.r, dp->addr.i);	/* lea ax,dest */
		putpush(REG_AX);			/* push ax */
	} else if (memaddr(dp) == MEM_REGISTER && retpushed) {
		putregop(OP_MOV, REG_SP, REG_AX, 0);	/* mov ax,sp */
		putimmed(OP_IMMED_ADD, REG_AX, 4, 0);	/* add ax,4 */
		putpush(REG_AX);			/* push ax */
	}
#ifdef PARANOID
	else
		/*
		 * This should never happen: The only non-scalar
		 * targets are either variable assignments or function
		 * arguments. For the latter, the parser should have
		 * created a temporary variable. Therefore, if this
		 * function returns a non-scalar, it will always be
		 * assigned to some variable, e.g. memory space.
		 * MEM_REGISTER should never happen.
		 */
		interror(77, "invalid destination type");
#endif
  }

  /* Now scan through all expressions and push the values onto the stack */
  for (i = 0; i < ep->exprnum; i++) {
	curexpr = ep->exprlist[i];
	curtype = sp->def.f.args[i].t->type;
	if (sp->def.f.args[i].class == CLASS_REF) {
		/*
		 * The argument has to be passed by reference, so determine
		 * and push it's address onto the stack. It has already been
		 * checked above that the argument is indeed a variable, so
		 * we don't have to repeat that check here.
		 * Strings and all unknown parameters passed by reference get
		 * the size _and_ address pushed.
		 */
#ifdef PARANOID
		if (!isvariable(curexpr))
			interror(75, "passing by reference something not a variable");
#endif
		if (curtype == EXPR_ANY || curtype == EXPR_STRING)
			putimmedpush(exprsize(curexpr), FALSE);	/* push size */
		pushvaraddr(curexpr);				/* push addr */
	} else switch (curtype) {
		case EXPR_POINTER:
		case EXPR_NUM:
		case EXPR_ENUM:
		case EXPR_CHAR:
		case EXPR_BOOL:
			di.memtype = MEM_REGISTER;
			di.addr.r = (curtype == EXPR_CHAR ||
			             curtype == EXPR_BOOL ? REG_AL : REG_AX);
			di.t = NULL;
			putscalarexpr(curexpr, &di);
			putpush(REG_AX);
			break;

		case EXPR_STRING:
		case EXPR_ARRAY:
		case EXPR_RECORD:
		case EXPR_IPADDR:
			if (isvariable(curexpr))
				pushvaraddr(curexpr);
			else {
				di.memtype = MEM_REGISTER | MEM_NOSIZE;
				di.addr.r = REG_AX;
				di.t = NULL;
				putcomplexexpr(curexpr, &di);
				putpush(REG_AX);
			}
			break;

		case EXPR_ANY:
		case EXPR_NONE:
			break;
	}
  }

  /* Call the function/procedure/menu */
  putcode(OP_CALL);
  putaddr(sp->loc.label, 2, FIXUP_REL16);

  /* Restore the function result if stored on stack */
  if (retpushed)
	putpop(REG_AX);

  /* Restore the registers which we saved previously */
  popregs();

  /* Save the result into the destination */
  if (memaddr(dp) != MEM_NOADDR)
	switch (exprtype(ep)) {
		case EXPR_POINTER:
			putsaveintreg(REG_AX, dp);
			break;

		case EXPR_NUM:
		case EXPR_ENUM:
			putsaveintreg(exprsize(ep) > 1 ? REG_AX : REG_AL, dp);
			break;

		case EXPR_BOOL:
		case EXPR_CHAR:
			putsaveintreg(REG_AL, dp);
			break;
#ifdef PARANOID
		case EXPR_NONE:
		case EXPR_ANY:
			/*
			 * These two should never appear in expression
			 * types.
			 */
			interror(78, "invalid function return type");
#endif
		default:
			/*
			 * Non-scalars are already copied into destination
			 * buffer by the called function.
			 */
			break;
	}
}

