/*
 * genexpr.c  -  Code generator for expressions
 *
 * Copyright (C) 1997-2003 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.4 2003/01/25 23:29:44 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(regs, dp)
unsigned int regs;
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 && (dp->memtype & MEM_ADR_MASK) == 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 && (dp->memtype & MEM_SIZE_MASK) == 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()
{
  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 scalar variables
 *
 ************************************************************************
 */

/*
 * Save an integer value in a register into the memory location or
 * register given by the destination info record.
 */
void putsaveintreg(srcreg, dp)
unsigned int srcreg;
struct meminfo *dp;
{
  struct typesdef *tp;
  unsigned int src8bit, dest8bit;

  /* Just for safety */
  if (dp == NULL || (dp->memtype & MEM_ADR_MASK) == 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.
   */
  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 && dp->t != 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 (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;
	} else if (src8bit && !dest8bit) {
		/* Convert 8-bit value into 16 bits */
		putregop(OP_MOV, srcreg, REG_AL, 0);
		putcode(OP_CBW);
		srcreg = REG_AX;
	}
  }

  /* Save register */
  switch (dp->memtype & MEM_ADR_MASK) {
	case MEM_REGISTER:
		putregop(OP_MOV, srcreg, dp->addr.r, 0);
		break;
	case MEM_ABSOLUTE:
		putregop(OP_MOV, srcreg, REG_16DISP | REG_RELOC_FLAG | dest8bit,
								dp->addr.i);
		break;
	case MEM_RELATIVE:
		if (dp->addr.r == REG_BP) {
			if ((dp->addr.i & 0xff80) == 0 ||
			    (dp->addr.i & 0xff80) == 0xff80)
				putregop(OP_MOV, srcreg,
					REG_BP_8DISP | dest8bit, dp->addr.i);
			else
				putregop(OP_MOV, srcreg,
					REG_BP_16DISP | dest8bit, dp->addr.i);
		} else if (dp->addr.r == REG_BX) {
			if (dp->addr.i == 0)
				putregop(OP_MOV, srcreg,
					REG_BX_0DISP | dest8bit, 0);
			else if ((dp->addr.i & 0xff80) == 0 ||
			         (dp->addr.i & 0xff80) == 0xff80)
				putregop(OP_MOV, srcreg,
					REG_BX_8DISP | dest8bit, dp->addr.i);
			else
				putregop(OP_MOV, srcreg,
					REG_BX_16DISP | dest8bit, dp->addr.i);
		} else if (regcmp(srcreg, REG_BX)) {
			putpush(REG_BP);
			putlea(REG_BP, dp->addr.r, dp->addr.i);
			putregop(OP_MOV, srcreg, REG_BP_8DISP | dest8bit, 0);
			putpop(REG_BP);
		} else {
			if (bx_inuse)
				putpush(REG_BX);
			putlea(REG_BX, dp->addr.r, dp->addr.i);
			putregop(OP_MOV, srcreg, REG_BX_0DISP | dest8bit, 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(val, size, dp)
long val;
int size;
struct meminfo *dp;
{
  unsigned int modrm = (size > 1 ? 0 : REG_8BIT_FLAG);

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

  /* Expression doesn't return a value */
  if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
	return;

  /*
   * If we have a scalar subclass, we have to check for a correct constant
   * value.
   */
  if (dp->t != NULL && (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);
  }

  /* Now write the code for moving the constant value into the destination */
  switch (dp->memtype & MEM_ADR_MASK) {
	case MEM_REGISTER:
		putimmed(OP_MOV, dp->addr.r, val, 0);
		break;
	case MEM_ABSOLUTE:
		modrm |= REG_16DISP | REG_RELOC_FLAG;
		putimmed(OP_MOV, modrm, val, dp->addr.i);
		break;
	case MEM_RELATIVE:
		if (dp->addr.r != REG_BP && dp->addr.r != REG_BX) {
			pushregs(PUSH_BX, NULL);
			putlea(REG_BX, dp->addr.r, dp->addr.i);
			putimmed(OP_MOV, REG_BX_0DISP | modrm, val, 0);
			popregs();
		} else {
			modrm |= REG_MODRM_FLAG;
			modrm |= (dp->addr.r == REG_BP ? OP_RM_BP : OP_RM_BX);
			modrm |= (((dp->addr.i & 0xff80) == 0 ||
			           (dp->addr.i & 0xff80) == 0xff80) ?
						OP_MOD_8BIT : OP_MOD_16BIT);
			putimmed(OP_MOV, modrm, val, dp->addr.i);
		}
		break;
  }
}



/*
 * Handle numerical leaf nodes
 */
static void putleafint(ep, dp, size)
struct expr *ep;
struct meminfo *dp;
int size;
{
  struct meminfo si;
  unsigned int modrm = REG_NONE;
  unsigned int tempreg;

  /* Check if the expression returns a value */
  if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
	return;

  /* Handle symbols */
  if (isvariable(ep)) {
	/* Handle a variable */
	setmeminfo(ep, &si, REG_NONE, REG_NONE);
	if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER)
		tempreg = dp->addr.r;
	else
		tempreg = (size > 1 ? REG_AX : REG_AL);
	if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE)
		modrm = getmodrm(si.addr.r, si.addr.i);
	else if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE)
		modrm = REG_16DISP;
	if (modrm != REG_NONE) {
		if (size < 2)
			modrm |= REG_8BIT_FLAG;
		if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
			modrm |= REG_RELOC_FLAG;
		putregop(OP_MOV, modrm, tempreg, si.addr.i);
	} else {
		modrm = (size > 1 ? 0 : REG_8BIT_FLAG);
		if (tempreg != REG_BX) {
			pushregs(PUSH_BX, NULL);
			putlea(REG_BX, si.addr.r, si.addr.i);
			putregop(OP_MOV, REG_BX_0DISP | modrm, tempreg, 0);
			popregs();
		} else {
			pushregs(PUSH_SI, NULL);
			putlea(REG_SI, si.addr.r, si.addr.i);
			putregop(OP_MOV, REG_SI_0DISP | modrm, tempreg, 0);
			popregs();
		}
	}
	putsaveintreg(tempreg, dp);
  } else if (isconst(ep)) {
	/* Handle a constant value */
	switch (exprtype(ep)) {
		case EXPR_NUM:
			putsaveintconst(ep->spec.cval.val.i, size, dp);
			break;
		case EXPR_CHAR:
			putsaveintconst(ep->spec.cval.val.c, size, dp);
			break;
		case EXPR_BOOL:
			putsaveintconst(ep->spec.cval.val.b, size, dp);
			break;
		case EXPR_ENUM:
			putsaveintconst(ep->spec.cval.val.e, size, 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
}




/*
 ************************************************************************
 *
 *                 Character expression handling
 *
 ************************************************************************
 */

/*
 * Generate a unary character operation. ep points to left subtree.
 */
static void putunarychar(op, ep, dp)
int op;
struct expr *ep;
struct meminfo *dp;
{
  struct meminfo di;

  /* chr command is special */
  if (op == CMD_CHR) {
	if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
		di = *dp;
		di.addr.r &= REG_8BIT_MASK;
		di.t = NULL;
		putintexpr(ep, &di);
	} else {
		di.memtype = MEM_REGISTER;
		di.addr.r = REG_AX;
		di.t = NULL;
		putintexpr(ep, &di);
		di.addr.r = REG_AL;
		putsaveintreg(di.addr.r, dp);
	}
	return;
  }

  /* Determine destination for subexpression code */
  if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
	di.memtype = MEM_REGISTER;
	di.addr.r = REG_AL;
	di.t = NULL;
  } else {
	di = *dp;
	di.t = NULL;
  }

  /* Put character into register and do operation */
  putcharexpr(ep, &di);
  switch (op) {
	case CMD_PRED:
		/* OP_DEC_MEM is also for 8 bit registers */
		putcode(OP_DEC_MEM);
		putcode(OP_MOD_REG | OP_MEM_DEC | rmlow(di.addr.r));
		putcode(OP_INTO);
		break;
	case CMD_SUCC:
		/* OP_INC_MEM is also for 8 bit registers */
		putcode(OP_INC_MEM);
		putcode(OP_MOD_REG | OP_MEM_INC | rmlow(di.addr.r));
		putcode(OP_INTO);
		break;
#ifdef PARANOID
	default:
		interror(29, "invalid unary operation for character expression");
#endif
  }

  /* Finally save the result into the destination */
  putsaveintreg(di.addr.r, dp);
}



/*
 * Output a character operation opcode into code segment
 * This will do an 'op dest,src'. The only supported operation
 * is a comparison.
 */
static void putcharop(op, src, dest)
int op;
struct meminfo *src;
struct meminfo *dest;
{
  if (op == CMD_CMP)
	putregop(OP_CMP, src->addr.r, dest->addr.r, 0);
#ifdef PARANOID
  else
	interror(95, "invalid character operation");
#endif
}



/*
 * Convert a character expression.
 */
void putcharexpr(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
  struct meminfo di1, di2;
  unsigned int tempreg;
  int *inuseptr;

  /* 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(44, "procedure not allowed in expression");
#endif

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

  /* Handle unary expression */
  if (ep->exprnum != 2) {
#ifdef PARANOID
	if (ep->exprnum != 1)
		interror(28, "invalid unary expression");
#endif
	putunarychar(ep->opcode, ep->left, dp);
	return;
  }

  /* Try to find out which register we can use as temporary storage */
  if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
	di1.memtype = MEM_REGISTER;
	di1.addr.r = REG_AL;
	di1.t = NULL;
	tempreg = REG_NONE;
	if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
		tempreg = dp->addr.r;
  } else {
	di1 = *dp;
	di1.t = NULL;
	tempreg = dp->addr.r;
  }
  inuseptr = NULL;
  di2.t = NULL;
  di2.memtype = MEM_REGISTER;
  if (!bx_inuse && !regcmp(tempreg, REG_BX)) {
	inuseptr = &bx_inuse;
	di2.addr.r = REG_BL;
  } else if (!cx_inuse && !regcmp(tempreg, REG_CX)) {
	inuseptr = &cx_inuse;
	di2.addr.r = REG_CL;
  } else if (!dx_inuse && !regcmp(tempreg, REG_DX)) {
	inuseptr = &dx_inuse;
	di2.addr.r = REG_DL;
  }

  /* Handle binary expression */
  if (inuseptr != NULL) {
	putcharexpr(ep->right, &di2);		/* mov di2,expr */
	(*inuseptr)++;
	putcharexpr(ep->left, &di1);		/* mov di1,expr */
	(*inuseptr)--;
	putcharop(ep->opcode, &di2, &di1);	/* op di1,di2 */
  } else if (!regcmp(tempreg, REG_BX)) {
	/* No temporary register available and tempreg is not BX, use stack */
	putpush(REG_BX);			/* push bx */
	di2.addr.r = REG_BL;
	putcharexpr(ep->right, &di2);		/* mov bl,expr */
	putcharexpr(ep->left, &di1);		/* mov di1,expr */
	putcharop(ep->opcode, &di2, &di1);	/* op di1,bl */
	putpop(REG_BX);				/* pop bx */
  } else {
	/* No temporary register available and destreg is BX, use stack */
	putpush(REG_CX);			/* push cx */
	di2.addr.r = REG_CL;
	putcharexpr(ep->right, &di2);		/* mov cl,expr */
	putcharexpr(ep->left, &di1);		/* mov di1,expr */
	putcharop(ep->opcode, &di2, &di1);	/* op di1,cl */
	putpop(REG_CX);				/* pop cx */
  }
  putsaveintreg(di1.addr.r, dp);
}




/*
 ************************************************************************
 *
 *                  Boolean expression handling
 *
 ************************************************************************
 */

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

#ifdef PARANOID
  if (ep->exprnum != 2 || 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 the 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;
  switch (exprtype(ep->left)) {
	case EXPR_STRING:
		putstrexpr(ep, &di, FALSE);
		break;
	case EXPR_NUM:
	case EXPR_ENUM:
		putintexpr(ep, &di);
		break;
	case EXPR_BOOL:
		putboolexpr(ep, &di);
		break;
	case EXPR_CHAR:
		putcharexpr(ep, &di);
		break;
#ifdef PARANOID
	default:
		interror(61, "invalid type in comparison");
		break;
#endif
  }
  ep->opcode = oldopcode;

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



/*
 * Generate a unary boolean operation. ep points to left subtree.
 */
static void putunarybool(op, ep, dp)
int op;
struct expr *ep;
struct meminfo *dp;
{
  struct meminfo di;

  /* Determine destination for subexpression code */
  if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
	di.memtype = MEM_REGISTER;
	di.addr.r = REG_AL;
	di.t = NULL;
  } else {
	di = *dp;
	di.t = NULL;
  }

  putcharexpr(ep, &di);
  switch (op) {
	case CMD_NOT:
		putcode(OP_NONIM);
		putcode(OP_NONIM_NOT | OP_MOD_REG | rmlow(di.addr.r));
		putimmed(OP_IMMED_AND, di.addr.r, 0x01, 0);
		break;
	case CMD_PRED:
		/* This should check for overrun */
		putcode(OP_DEC_MEM);
		putcode(OP_MOD_REG | OP_MEM_DEC | rmlow(di.addr.r));
		putimmed(OP_IMMED_AND, di.addr.r, 0x01, 0);
		break;
	case CMD_SUCC:
		/* This should check for overrun */
		putcode(OP_INC_MEM);
		putcode(OP_MOD_REG | OP_MEM_INC | rmlow(di.addr.r));
		putimmed(OP_IMMED_AND, di.addr.r, 0x01, 0);
		break;
#ifdef PARANOID
	default:
		interror(23, "invalid unary operation for character expression");
#endif
  }

  /* Finally save the result into the destination */
  putsaveintreg(di.addr.r, dp);
}




/*
 * Output a boolean operation opcode into code segment
 * This will do an 'op dest,src'
 */
static void putboolop(op, src, dest)
int op;
struct meminfo *src;
struct meminfo *dest;
{
  unsigned int srcreg, destreg;

  srcreg = src->addr.r;
  destreg = dest->addr.r;
  switch (op) {
	case CMD_OR:
		/* or dest,src */
		putregop(OP_OR, srcreg, destreg, 0);
		break;
	case CMD_XOR:
		/* xor dest,src */
		putregop(OP_XOR, srcreg, destreg, 0);
		break;
	case CMD_AND:
		/* and dest,src */
		putregop(OP_AND, srcreg, destreg, 0);
		break;
	case CMD_CMP:
		/* cmp dest,src */
		putregop(OP_CMP, srcreg, destreg, 0);
		break;
#ifdef PARANOID
	default:
		interror(94, "invalid boolean operation");
		break;
#endif
  }
}



/*
 * Convert a boolean expression.
 */
void putboolexpr(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
  struct meminfo di1, di2;
  unsigned int tempreg;
  int *inuseptr;

  /* 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(41, "procedure not allowed in expression");
#endif

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

  /* Handle unary expression */
  if (ep->exprnum != 2) {
#ifdef PARANOID
	if (ep->exprnum != 1 || exprtype(ep->left) != EXPR_BOOL)
		interror(93, "invalid unary expression");
#endif
	putunarybool(ep->opcode, ep->left, dp);
	return;
  }

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

  /*
   * In contrast to numerical expressions we are not handling any special
   * cases here, because boolean operations also affect flags even when
   * not returning any value.
   */

  /* Try to find out which register we can use as temporary storage */
  if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
	di1.memtype = MEM_REGISTER;
	di1.addr.r = REG_AL;
	di1.t = NULL;
	tempreg = REG_NONE;
	if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
		tempreg = dp->addr.r;
  } else {
	di1 = *dp;
	di1.t = NULL;
	tempreg = dp->addr.r;
  }
  inuseptr = NULL;
  di2.t = NULL;
  di2.memtype = MEM_REGISTER;
  if (!bx_inuse && !regcmp(tempreg, REG_BX)) {
	inuseptr = &bx_inuse;
	di2.addr.r = REG_BL;
  } else if (!cx_inuse && !regcmp(tempreg, REG_CX)) {
	inuseptr = &cx_inuse;
	di2.addr.r = REG_CL;
  } else if (!dx_inuse && !regcmp(tempreg, REG_DX)) {
	inuseptr = &dx_inuse;
	di2.addr.r = REG_DL;
  }

  /* Handle binary expression */
  if (inuseptr != NULL) {
	putboolexpr(ep->right, &di2);		/* mov di2,expr */
	(*inuseptr)++;
	putboolexpr(ep->left, &di1);		/* mov di1,expr */
	(*inuseptr)--;
	putboolop(ep->opcode, &di2, &di1);	/* op di1,di2 */
  } else if (!regcmp(tempreg, REG_BX)) {
	/* No temporary register available and tempreg is not BX, use stack */
	putpush(REG_BX);			/* push bx */
	di2.addr.r = REG_BL;
	putboolexpr(ep->right, &di2);		/* mov bl,expr */
	putboolexpr(ep->left, &di1);		/* mov di1,expr */
	putboolop(ep->opcode, &di2, &di1);	/* op di1,bl */
	putpop(REG_BX);				/* pop bx */
  } else {
	/* No temporary register available and destreg is BX, use stack */
	putpush(REG_CX);			/* push cx */
	di2.addr.r = REG_CL;
	putboolexpr(ep->right, &di2);		/* mov cl,expr */
	putboolexpr(ep->left, &di1);		/* mov di1,expr */
	putboolop(ep->opcode, &di2, &di1);	/* op di1,cl */
	putpop(REG_CX);				/* pop cx */
  }
  putsaveintreg(di1.addr.r, dp);
}




/*
 ************************************************************************
 *
 *                  Numerical expression handling
 *
 ************************************************************************
 */

/*
 * Generate a unary integer operation. ep points to left subtree.
 */
static void putunaryint(op, ep, dp)
int op;
struct expr *ep;
struct meminfo *dp;
{
  struct meminfo di;

  /* Determine destination for subexpression code */
  if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
	di.memtype = MEM_REGISTER;
	di.addr.r = REG_AX;
	di.t = NULL;
  } else {
	di = *dp;
	di.t = NULL;
  }

  /* For CMD_ORD we have to deal with different types in the subtree */
  if (op == CMD_ORD) {
	switch (exprtype(ep)) {
		case EXPR_NUM:
		case EXPR_ENUM:
			/* Use full 16 bit register */
			putintexpr(ep, &di);
			break;
		case EXPR_CHAR:
			di.addr.r = REG_AL;
			putcharexpr(ep, &di);
			putcode(OP_CBW);
			di.addr.r = REG_AX;
			break;
		case EXPR_BOOL:
			di.addr.r = REG_AL;
			putboolexpr(ep, &di);
			putcode(OP_CBW);
			di.addr.r = REG_AX;
			break;
#ifdef PARANOID
		default:
			interror(99, "invalid subexpression type for ord command");
			break;
#endif
	}
  } else {
#ifdef PARANOID
	if (exprtype(ep) != EXPR_NUM &&
	    (exprtype(ep) != EXPR_ENUM ||
	     (op != CMD_PRED && op != CMD_SUCC)))
		interror(51, "invalid subexpression type for unary command");
#endif
	putintexpr(ep, &di);
	switch (op) {
		case '-':
			putcode(OP_NONIM | OP_WORD_MASK);
			putcode(OP_NONIM_NEG | OP_MOD_REG | rmlow(di.addr.r));
			break;
		case CMD_NOT:
			putcode(OP_NONIM | 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:
			putcode(OP_DEC_REG16 | rmlow(di.addr.r));
			putcode(OP_INTO);
			break;
		case CMD_SUCC:
			putcode(OP_INC_REG16 | rmlow(di.addr.r));
			putcode(OP_INTO);
			break;
		case CMD_ABS:
			putregop(OP_OR, di.addr.r, di.addr.r, 0);
			putjmp(codeptr + 4, JMP_JNS);
			putcode(OP_NONIM | OP_WORD_MASK);
			putcode(OP_NONIM_NEG | OP_MOD_REG | rmlow(di.addr.r));
			break;
		case CMD_SQR:
			if (dx_inuse && !regcmp(di.addr.r, REG_DX))
				putpush(REG_DX);
			putregop(OP_MOV, di.addr.r, REG_AX, 0);
			putcode(OP_NONIM | OP_WORD_MASK);
			putcode(OP_NONIM_IMUL | OP_MOD_REG | rmlow(di.addr.r));
			putregop(OP_MOV, REG_AX, di.addr.r, 0);
			if (dx_inuse && !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
	}
  }

  /* Finally save the result into the destination */
  putsaveintreg(di.addr.r, dp);
}



/*
 * Output an integer operation opcode into code segment
 * This will do an 'op dest,src'
 */
static void putintop(op, src, dest)
int op;
struct meminfo *src;
struct meminfo *dest;
{
  unsigned int srcreg, destreg;

  srcreg = src->addr.r;
  destreg = dest->addr.r;
  switch (op) {
	case '+':
		/* add dest,src */
		putregop(OP_ADD, srcreg, destreg, 0);
		putcode(OP_INTO);
		break;
	case '-':
		/* sub dest,src */
		putregop(OP_SUB, srcreg, destreg, 0);
		putcode(OP_INTO);
		break;
	case '*':
		/* imul src */
		if (dx_inuse && !regcmp(destreg, REG_DX))
			putpush(REG_DX);			/* push dx */
		putregop(OP_MOV, destreg, REG_AX, 0);		/* mov ax,dest */
		putcode(OP_NONIM | OP_WORD_MASK);
		putcode(OP_NONIM_IMUL | OP_MOD_REG | rmlow(srcreg));
		putregop(OP_MOV, REG_AX, destreg, 0);		/* mov dest,ax */
		if (dx_inuse && !regcmp(destreg, REG_DX))
			putpop(REG_DX);				/* pop dx */
		putcode(OP_INTO);
		break;
	case '/':
		/* idiv src */
		if (regcmp(srcreg, REG_DX)) {
			if (cx_inuse && !regcmp(destreg, REG_CX))
				putpush(REG_CX);		/* push cx */
			putregop(OP_MOV, srcreg, REG_CX, 0);	/* mov cx,src */
		}
		if (dx_inuse && !regcmp(destreg, REG_DX))
			putpush(REG_DX);			/* push dx */
		putregop(OP_MOV, destreg, REG_AX, 0);		/* mov ax,dest */
		putcode(OP_CWD);				/* cwd */
		putcode(OP_NONIM | OP_WORD_MASK);
		if (regcmp(srcreg, REG_DX))
			putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(REG_CX));
		else
			putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(srcreg));
		putregop(OP_MOV, REG_AX, destreg, 0);		/* mov dest,ax */
		if (dx_inuse && !regcmp(destreg, REG_DX))
			putpop(REG_DX);				/* pop dx */
		if (regcmp(srcreg, REG_DX) &&
		    cx_inuse && !regcmp(destreg, REG_CX))
			putpop(REG_CX);				/* pop cx */
		break;
	case '%':
		/* imod src */
		if (regcmp(srcreg, REG_DX)) {
			if (cx_inuse && !regcmp(destreg, REG_CX))
				putpush(REG_CX);		/* push cx */
			putregop(OP_MOV, srcreg, REG_CX, 0);	/* mov cx,src */
		}
		if (dx_inuse && !regcmp(destreg, REG_DX))
			putpush(REG_DX);			/* push dx */
		putregop(OP_MOV, destreg, REG_AX, 0);		/* mov ax,dest */
		putcode(OP_CWD);				/* cwd */
		putcode(OP_NONIM | OP_WORD_MASK);
		if (regcmp(srcreg, REG_DX))
			putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(REG_CX));
		else
			putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(srcreg));
		putregop(OP_MOV, REG_DX, destreg, 0);		/* mov dest,dx */
		if (dx_inuse && !regcmp(destreg, REG_DX))
			putpop(REG_DX);				/* pop dx */
		if (regcmp(srcreg, REG_DX) &&
		    cx_inuse && !regcmp(destreg, REG_CX))
			putpop(REG_CX);				/* pop cx */
		break;
	case CMD_OR:
		/* or dest,src */
		putregop(OP_OR, srcreg, destreg, 0);
		break;
	case CMD_XOR:
		/* xor dest,src */
		putregop(OP_XOR, srcreg, destreg, 0);
		break;
	case CMD_AND:
		/* and dest,src */
		putregop(OP_AND, srcreg, destreg, 0);
		break;
	case CMD_CMP:
		/* cmp dest,src */
		putregop(OP_CMP, srcreg, destreg, 0);
		break;
#ifdef PARANOID
	default:
		interror(88, "invalid numerical operation");
		break;
#endif
  }
}



/*
 * Convert a numeric expression. This also handles enumeration expressions.
 */
void putintexpr(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
  struct meminfo di1, di2;
  struct expr *tmpexpr;
  unsigned int tempreg;
  int *inuseptr;

  /* 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)) {
	putleafint(ep, dp, 2);
	return;
  }

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

  /*
   * Handle some special expression cases, where the result can be
   * forecast:
   *   1.)  0 / num  -->  0
   *   3.)  num * 0  -->  0
   *   2.)  num + 0  -->  num
   *   4.)  num + 1  -->  inc(num)
   *   5.)  num - 1  -->  dec(num)
   *   6.)  num * 1  -->  num
   *   7.)  num / 1  -->  num
   *  10.)  num & 0  -->  0
   *  11.)  num | 0  -->  num
   *  12.)  num / 0  -->  error
   *  13.)  num % 0  -->  error
   *  14.)  num % 2  -->  and(num,1)
   *  15.)  num % 1  -->  0
   */
  if (exprtype(ep) == EXPR_NUM && (isconst(ep->left) || isconst(ep->right))) {
#ifdef PARANOID
	if (isconst(ep->left) && isconst(ep->right))
		interror(67, "expression tree not collapsed properly");
#endif
	/* Case (1): 0 / num --> 0 */
	if (ep->opcode == '/' && isconst(ep->left) &&
	    ep->left->spec.cval.val.i == 0) {
		putsaveintconst(0, 2, dp);
		return;
	}

	/* Case (2): num * 0 --> 0 */
	if (ep->opcode == '*' &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.i == 0) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.i == 0))) {
		putsaveintconst(0, 2, dp);
		return;
	}

	/* Case (3): num + 0 --> num */
	if (ep->opcode == '+' &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.i == 0) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.i == 0))) {
		if (!isconst(ep->left))
			putintexpr(ep->left, dp);	/* mov dest,expr */
		else
			putintexpr(ep->right, dp);	/* mov dest,expr */
		return;
	}

	/* Case (4): num + 1 --> inc(num) */
	if (ep->opcode == '+' &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.i == 1) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.i == 1))) {
		tmpexpr = (isconst(ep->left) ? ep->right : ep->left);
		if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
			di1.memtype = MEM_REGISTER;
			di1.addr.r = REG_AX;
			di1.t = NULL;
			putintexpr(tmpexpr, &di1);
			putcode(OP_INC_REG16 | rmlow(REG_AX));
			putsaveintreg(REG_AX, dp);
		} else {
			putintexpr(tmpexpr, dp);
			putcode(OP_INC_REG16 | rmlow(dp->addr.r));
		}
		return;
	}

	/* Case (5): num - 1 --> dec(num) */
	if (ep->opcode == '-' &&
	    (isconst(ep->right) && ep->right->spec.cval.val.i == 1)) {
		if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
			di1.memtype = MEM_REGISTER;
			di1.addr.r = REG_AX;
			di1.t = NULL;
			putintexpr(ep->left, &di1);
			putcode(OP_DEC_REG16 | rmlow(REG_AX));
			putsaveintreg(REG_AX, dp);
		} else {
			putintexpr(ep->left, dp);
			putcode(OP_DEC_REG16 | rmlow(dp->addr.r));
		}
		return;
	}

	/* Case (6): num * 1 --> num */
	if (ep->opcode == '*' &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.i == 1) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.i == 1))) {
		if (!isconst(ep->left))
			putintexpr(ep->left, dp);	/* mov dest,expr */
		else
			putintexpr(ep->right, dp);	/* mov dest,expr */
		return;
	}

	/* Case (7): num / 1 --> num */
	if (ep->opcode == '/' &&
	    (isconst(ep->right) && ep->right->spec.cval.val.i == 1)) {
		putintexpr(ep->left, dp);		/* mov dest,expr */
		return;
	}

	/* Case (10): num & 0 --> 0 */
	if (ep->opcode == CMD_AND &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.i == 0) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.i == 0))) {
		putsaveintconst(0, 2, dp);
		return;
	}

	/* Case (11): num | 0 --> num */
	if (ep->opcode == CMD_OR &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.i == 0) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.i == 0))) {
		if (!isconst(ep->left))
			putintexpr(ep->left, dp);	/* mov dest,expr */
		else
			putintexpr(ep->right, dp);	/* mov dest,expr */
		return;
	}

	/* Case (12) and case (13): num /|% 0 --> error */
	if ((ep->opcode == '/' || ep->opcode == '%') &&
	    (isconst(ep->right) && ep->right->spec.cval.val.i == 0)) {
		error("division by zero");
		return;
	}

	/* Case (14): num % 2 --> and(num,1) */
	if (ep->opcode == '/' &&
	    (isconst(ep->right) && ep->right->spec.cval.val.i == 2)) {
		if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
			di1.memtype = MEM_REGISTER;
			di1.addr.r = REG_AX;
			di1.t = NULL;
			putintexpr(ep->left, &di1);
			putimmed(OP_IMMED_AND, REG_AX, 0x0001, 0);
			putsaveintreg(REG_AX, dp);
		} else {
			putintexpr(ep->left, dp);
			putcode(OP_SHIFT_1 | OP_WORD_MASK);
			putimmed(OP_IMMED_AND, dp->addr.r, 0x0001, 0);
		}
		return;
	}

	/* Case (15): num % 1 --> 0 */
	if (ep->opcode == '%' &&
	    (isconst(ep->right) && ep->right->spec.cval.val.i == 1)) {
		putsaveintconst(0, 2, dp);
		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.
   */
  if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
	di1.memtype = MEM_REGISTER;
	di1.addr.r = REG_AX;
	di1.t = NULL;
	tempreg = REG_NONE;
	if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
		tempreg = dp->addr.r;
  } else {
	di1 = *dp;
	di1.t = NULL;
	tempreg = dp->addr.r;
  }
  inuseptr = NULL;
  di2.t = NULL;
  di2.memtype = MEM_REGISTER;
  if (!bx_inuse && tempreg != REG_BX) {
	inuseptr = &bx_inuse;
	di2.addr.r = REG_BX;
  } else if (!cx_inuse && tempreg != REG_CX) {
	inuseptr = &cx_inuse;
	di2.addr.r = REG_CX;
  } else if (!si_inuse && tempreg != REG_SI) {
	inuseptr = &si_inuse;
	di2.addr.r = REG_SI;
  } else if (!di_inuse && tempreg != REG_DI) {
	inuseptr = &di_inuse;
	di2.addr.r = REG_DI;
  } else if (!dx_inuse && tempreg != REG_DX) {
	inuseptr = &dx_inuse;
	di2.addr.r = REG_DX;
  }

  /* Handle binary expression */
  if (inuseptr != NULL) {
	putintexpr(ep->right, &di2);		/* mov di2,expr */
	(*inuseptr)++;
	putintexpr(ep->left, &di1);		/* mov di1,expr */
	(*inuseptr)--;
	putintop(ep->opcode, &di2, &di1);	/* op di1,di2 */
  } else if (tempreg != REG_BX) {
	/* No temporary register available and tempreg is not BX, use stack */
	putpush(REG_BX);			/* push bx */
	di2.addr.r = REG_BX;
	putintexpr(ep->right, &di2);		/* mov bx,expr */
	putintexpr(ep->left, &di1);		/* mov di1,expr */
	putintop(ep->opcode, &di2, &di1);	/* op di1,bx */
	putpop(REG_BX);				/* pop bx */
  } else {
	/* No temporary register available and destreg is BX, use stack */
	putpush(REG_CX);			/* push cx */
	di2.addr.r = REG_CX;
	putintexpr(ep->right, &di2);		/* mov cx,expr */
	putintexpr(ep->left, &di1);		/* mov di1,expr */
	putintop(ep->opcode, &di2, &di1);	/* op di1,cx */
	putpop(REG_CX);				/* pop cx */
  }
  putsaveintreg(di1.addr.r, dp);
}




/*
 ************************************************************************
 *
 *                    String expression handling
 *
 ************************************************************************
 */

/*
 * Handle leaf string nodes. Determining where the string 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).
 * If the 'rec' flag has been set, we were called from a recursively called
 * 'putstrexpr' routine, and have to return the end of the destination string
 * in AX. In that case we will never be called with MEM_REGISTER.
 */
static void putleafstr(ep, dp, rec)
struct expr *ep;
struct meminfo *dp;
int rec;
{
  struct meminfo si;
  unsigned int modrm;

  /* Expression doesn't return a value */
  if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
	return;

  /* Just return the adress of the string in the given register */
  if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
#ifdef PARANOID
	if (rec)
		interror(55, "invalid call to 'putleafstr'");
#endif
	if (isvariable(ep)) {
		setmeminfo(ep, &si, dp->addr.r, REG_NONE);
	} else if (isconst(ep)) {
#ifdef PARANOID
		if (ep->spec.cval.val.s == NULL)
			interror(68, "no string defined in string leaf node");
#endif
		si.memtype = MEM_ABSOLUTE;
		si.size.i = strlen(ep->spec.cval.val.s);
		si.addr.i = putstring(ep->spec.cval.val.s);
	}
	if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
		/* String is constant or static */
		putcode(OP_MOV_WREGIM | rmlow(dp->addr.r));
		if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
			setreloc();
		putint(si.addr.i);
	} else if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
#ifdef PARANOID
		if (dp->addr.r == REG_BP)
			interror(117, "invalid destination register for string");
#endif
		modrm = 0;
		if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
			modrm = REG_RELOC_FLAG;
		putlea(dp->addr.r, si.addr.r | modrm, si.addr.i);
	}
	return;
  }

  /*
   * We have to copy the string into the destination. First determine the
   * address of the source and put it into SI.
   */
  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.s == NULL)
		interror(74, "no string defined in string leaf node");
#endif
	si.memtype = MEM_ABSOLUTE | MEM_IMMEDIATE;
	si.size.i = strlen(ep->spec.cval.val.s);
	si.addr.i = putstring(ep->spec.cval.val.s);
  }
  if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
	/* String is constant or static */
	putcode(OP_MOV_WREGIM | rmlow(REG_SI));
	if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
		setreloc();
	putint(si.addr.i);
  } else if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
	modrm = 0;
	if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
		modrm = REG_RELOC_FLAG;
	putlea(REG_SI, si.addr.r | modrm, si.addr.i);
  }

  /* Determine the address of the destination and put it into DI */
  if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
	putcode(OP_MOV_WREGIM | rmlow(REG_DI));		/* mov di,#dest */
	if ((dp->memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
		setreloc();
	putint(dp->addr.i);
  } else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
	putlea(REG_DI, dp->addr.r, dp->addr.i);		/* lea di,dest */
#ifdef PARANOID
  else
	interror(82, "invalid destination type");
#endif

  /* Determine the size of the destination buffer and put it into CX */
  if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE) {
	putimmed(OP_MOV, REG_CX, dp->size.i, 0);	/* mov cx,size */
  } else if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
	putregop(OP_MOV, dp->size.r, REG_CX, 0);	/* mov cx,sizereg */
#ifdef PARANOID
  else
	interror(76, "invalid size type");
#endif

  /* Now actually generate the copy code */
  putcode(OP_JCXZ);					/* jcxz $+9 */
  putcode(8);
  putcode(OP_LODSB);					/* lodsb */
  putregop(OP_OR, REG_AL, REG_AL, 0);			/* or al,al */
  putjmp(codeptr + 5, JMP_JZ);				/* jz $+5 */
  putcode(OP_STOSB);					/* stosb */
  putcode(OP_LOOP);					/* loop $-6 */
  putcode((unsigned int)(-8 & 0xff));
  putimmed(OP_MOV, REG_DI_0DISP | REG_8BIT_FLAG, 0, 0);	/* mov [di],0 */
  if (rec)
	putregop(OP_MOV, REG_DI, REG_AX, 0);		/* mov ax,di */
  popregs();
}



/*
 * Put string comparison into code segment
 */
static void putstrcmp(ep)
struct expr *ep;
{
  struct meminfo di;
  unsigned int tmpreg;
  addr_t stack;
  addr_t tempstrings[2];
  int i;

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

  /* We need the SI and DI registers */
  pushregs(PUSH_SI | PUSH_DI, NULL);

  /* Decrement the stack pointer to get enough space for argument strings */
  stack = 0;
  for (i = 0; i < 2; i++) {
#ifdef PARANOID
	if (isproc(ep->exprlist[i]))
		interror(53, "procedure not allowed in string comparison");
#endif
	tempstrings[i] = -1;
	if (!isconst(ep->exprlist[i]) && !isvariable(ep->exprlist[i])) {
		/* String expression is not a constant or variable */
		tempstrings[i] = stack;
		stack += MAX_STR_LEN + 1;
	}
  }
  if (stack > 0) {
	pushregs(PUSH_DX, NULL);			/* push dx */
	putregop(OP_MOV, REG_SP, REG_DX, 0);		/* mov dx,sp */
	putimmed(OP_IMMED_SUB, REG_SP, stack, 0);	/* sub sp,ofs */
	dx_inuse++;
  }

  /* Now scan through all expressions and push the values onto the stack */
  for (i = 0; i < 2; i++) {
	tmpreg = (i == 0 ? REG_SI : REG_DI);
	if (tempstrings[i] < 0) {
		/* String is a variable or is constant */
		di.memtype = MEM_REGISTER;
		di.addr.r = tmpreg;
		di.t = NULL;
		putleafstr(ep->exprlist[i], &di, FALSE);
	} else {
		/* String is a subexpression, so copy it into temp space */
		putregop(OP_MOV, REG_SP, tmpreg, 0);
		putlea(tmpreg, tmpreg, tempstrings[i]);
		di.memtype = MEM_RELATIVE | MEM_IMMEDIATE;
		di.addr.i = 0;
		di.addr.r = tmpreg;
		di.size.i = MAX_STR_LEN;
		di.t = NULL;
		putstrexpr(ep->exprlist[i], &di, FALSE);
	}
	if (i == 0)
		si_inuse++;
	else
		di_inuse++;
  }

  /* Actually call the string comparison function, and cleanup stack */
  putcode(OP_LODSB);					/* lodsb */
  putcode(OP_SCASB);					/* scasb */
  putjmp(codeptr + 6, JMP_JNZ);				/* jne $+6 */
  putregop(OP_OR, REG_AL, REG_AL, 0);			/* or al,al */
  putjmp(codeptr - 6, JMP_JNZ);				/* jnz $-6 */
  if (stack > 0) {
	dx_inuse--;
	putregop(OP_MOV, REG_DX, REG_SP, 0);		/* mov sp,dx */
	popregs();					/* pop dx */
  }

  si_inuse--;
  di_inuse--;
  popregs();						/* pop regs */
}



/*
 * Convert a string expression. When the 'rec' parameter is TRUE, we
 * are called recursively, and have to return a pointer to the end of
 * the result string in AX. This does not apply for a string comparison,
 * since the result is always boolean.
 */
void putstrexpr(ep, dp, rec)
struct expr *ep;
struct meminfo *dp;
int rec;
{
  struct meminfo di, tmpdi;
  addr_t tmpcode, jmpadr = 0;

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

  /* Handle string comparison */
  if (ep->opcode == CMD_CMP) {
	putstrcmp(ep);
	return;
  }

  /*
   * Expression is a function. A function always returns the destination
   * pointer in AX. If _we_ have to return the result's end adress, we
   * have to search for the end marker.
   */
  if (isfunc(ep)) {
	putproc(ep, dp);
	if (rec) {
		pushregs(PUSH_DI | PUSH_CX, dp);
		putregop(OP_MOV, REG_AX, REG_DI, 0);	/* mov di,ax */
		putregop(OP_XOR, REG_AL, REG_AL, 0);	/* xor al,al */
		if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE) {
			putimmed(OP_MOV, REG_CX, dp->size.i, 0);
		} else if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG) {
			putregop(OP_MOV, dp->size.r, REG_CX, 0);
		}
		putcode(OP_REPNE);
		putcode(OP_SCASB);			/* repne scasb */
		putcode(OP_DEC_REG16 | rmlow(REG_DI));	/* dec di */
		putregop(OP_MOV, REG_DI, REG_AX, 0);	/* mov ax,di */
		popregs();
	}
	return;
  }
#ifdef PARANOID
  if (isproc(ep))
	interror(45, "procedure not allowed in expression");
#endif

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

  /* There are no unary string operations */
#ifdef PARANOID
  if (ep->exprnum != 2)
	interror(78, "no unary operation for strings");
  else if (ep->opcode != '+' && ep->opcode != '*')
	interror(80, "invalid binary operation for strings");
#endif

  /* Expression doesn't return a value */
  if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
	return;

  /*
   * If the target string is not long enough, there is no need to continue.
   * We only have to check for a constant length here. Variable length will
   * be handled by the string copy routines.
   */
#ifdef PARANOID
  if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE && dp->size.i <= 0)
	interror(87, "invalid string length");
#endif

  /*
   * If the result has to go into a register, we use our "register"
   * string buffer. Otherwise we can directly copy all strings into
   * the destination.
   */
  di = *dp;
  di.t = NULL;
  if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
	di.memtype = MEM_ABSOLUTE | MEM_IMMEDIATE;
	di.addr.i = 0;
	di.size.i = MAX_STR_LEN;
  }

  /*
   * Put the address of the destination string space into DI.  All further
   * string expression handling will then be done relative to DI.
   */
  pushregs(PUSH_DI, dp);				/* push di and rel reg */
  if ((di.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
	putcode(OP_MOV_WREGIM | rmlow(REG_DI));		/* mov di,#dest */
	setreloc();
	putint(di.addr.i);
  } else if ((di.memtype & MEM_ADR_MASK) == MEM_RELATIVE)
	putlea(REG_DI, di.addr.r, di.addr.i);		/* lea di,dest */
#ifdef PARANOID
  else
	interror(83, "invalid destination type");
#endif
  di.memtype = (di.memtype & ~MEM_ADR_MASK) | MEM_RELATIVE;
  di.addr.i = 0;
  di.addr.r = REG_DI;

  /*
   * A multiplication is special: it means to copy the character in the
   * left subexpression into the destination string by the number given
   * by the right subexpression.
   */
  if (exprtype(ep->left) == EXPR_CHAR &&
      exprtype(ep->right) == EXPR_NUM &&
      ep->opcode == '*') {
	/* First get the number of characters to fill in */
	tmpdi.memtype = MEM_REGISTER | MEM_NOSIZE;
	tmpdi.addr.r = REG_AX;
	tmpdi.t = NULL;
	putintexpr(ep->right, &tmpdi);
	/* Compare the destination size with the number of characters to copy */
	if ((di.memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE)
		putimmed(OP_CMP, REG_AX, di.size.i, 0);	/* cmp ax,size */
	else if ((di.memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
		putregop(OP_CMP, di.size.r, REG_AX, 0);
#ifdef PARANOID
	else
		interror(116, "invalid size type");
#endif
	/* Use the smaller of the two values */
	jmpadr = codeptr;
	codeptr += 2;
	if ((di.memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE) {
		putimmed(OP_MOV, REG_AX, di.size.i, 0);	/* mov ax,size */
	} else if ((di.memtype & MEM_SIZE_MASK) == MEM_SIZEREG) {
		putregop(OP_MOV, di.size.r, REG_AX, 0);
	}
	tmpcode = codeptr;
	codeptr = jmpadr;
	putjmp(tmpcode, JMP_JC);
	codeptr = tmpcode;
	/* Move the copy size into CX */
	pushregs(PUSH_CX, dp);
	putregop(OP_MOV, REG_AX, REG_CX, 0);
	/* Get the character to copy into AL */
	cx_inuse++;
	tmpdi.memtype = MEM_REGISTER | MEM_NOSIZE;
	tmpdi.addr.r = REG_AL;
	tmpdi.t = NULL;
	putcharexpr(ep->left, &tmpdi);
	cx_inuse--;
	/* Now actually do the fill */
	putcode(OP_REP);
	putcode(OP_STOSB);
	putimmed(OP_MOV, REG_DI_0DISP | REG_8BIT_FLAG, 0, 0);
	/* Return a pointer to the last character in AX and restore stack */
	if (rec)
		putregop(OP_MOV, REG_DI, REG_AX, 0);
	goto endstrexpr;
  }

  /*
   * Handle left expression. We call this routine recursively, so that it
   * will return a pointer to the end of the destination string in AX. This
   * does not apply if the left subexpression is a character.
   */
  if (exprtype(ep->left) == EXPR_STRING) {
	/* String subexpression */
	putstrexpr(ep->left, &di, TRUE);
  } else if (exprtype(ep->left) == EXPR_CHAR) {
	/* Character subexpression */
	if ((di.memtype & MEM_SIZE_MASK) == MEM_SIZEREG) {
		putregop(OP_OR, di.size.r, di.size.r, 0);	/* or size,size */
		putjmp(codeptr + 5, JMP_JNZ);			/* jnz $+3 */
		jmpadr = codeptr;
		codeptr += 3;			/* provide space for jmp near */
	}
	putcharexpr(ep->left, &di);
  }
#ifdef PARANOID
  else
	interror(85, "invalid subexpression for string expression");
#endif

  /*
   * Put the size of the destination string space into CX.  All further
   * string expression handling will then be done using CX.
   */
  pushregs(PUSH_CX, dp);				/* push cx and rel reg */
  if ((di.memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE)
	putimmed(OP_MOV, REG_CX, di.size.i, 0);		/* mov cx,size */
  else if ((di.memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
	putregop(OP_MOV, di.size.r, REG_CX, 0);
#ifdef PARANOID
  else
	interror(86, "invalid size type");
#endif
  di.memtype = (di.memtype & ~MEM_SIZE_MASK) | MEM_SIZEREG;
  di.size.r = REG_CX;

  /*
   * Now we have to find out the end of the current string so that we
   * can append the right side. Also, the size of the remaining string
   * buffer has to be put into CX.
   */
  if (exprtype(ep->left) == EXPR_STRING) {
	/* String subexpression */
	putregop(OP_XCHG, REG_AX, REG_DI, 0);		/* xchg di,ax */
	putregop(OP_SUB, REG_DI, REG_AX, 0);		/* sub ax,di */
	putregop(OP_ADD, REG_AX, REG_CX, 0);		/* add cx,ax */
  } else {
	/* Character subexpression */
	putcode(OP_INC_REG16 | rmlow(REG_DI));		/* inc di */
	putcode(OP_DEC_REG16 | rmlow(REG_CX));		/* dec cx */
  }

  /*
   * Handle right expression. All relevant values are now in DI and CX.
   * If the it's a character expression, we also have to check if there
   * is enough space for the additional character.
   */
  if (exprtype(ep->right) == EXPR_STRING) {
	putstrexpr(ep->right, &di, rec);
  } else if (exprtype(ep->right) == EXPR_CHAR) {
	di.memtype = MEM_REGISTER;
	di.addr.r = REG_AL;
	di.t = NULL;
	di_inuse++;
	cx_inuse++;
	putcharexpr(ep->right, &di);
	di_inuse--;
	cx_inuse--;
	putcode(OP_JCXZ);				/* jcxz skip */
	putcode(5);
	putcode(OP_STOSB);				/* stosb */
	putimmed(OP_MOV, REG_DI_0DISP | REG_8BIT_FLAG,
						0, 0);	/* mov [di],0 */
	if (rec)
		putregop(OP_MOV, REG_DI, REG_AX, 0);	/* mov ax,di */
  }

  /* Resolve the jump, which checked for string size previously */
  if (jmpadr > 0) {
	tmpcode = codeptr;
	codeptr = jmpadr;
	putjmp(tmpcode, JMP_UNCOND);
	codeptr = tmpcode;
  }

  /*
   * If we were using the string "register", we have to set the processor
   * register accordingly.
   */
endstrexpr:
  if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
	putcode(OP_MOV_WREGIM | rmlow(dp->addr.r));
	setreloc();
	putint(0);
  }

  /* At the end restore all registers we might have set */
  popregs();						/* pop cx */
  popregs();						/* pop di */
}




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

/*
 * Handle leaf nodes. This is the only operation allowed with non-scalars.
 */
static void putleafcomplex(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
  struct meminfo si;
  unsigned int modrm;

  /* Expression doesn't return a value */
  if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
	return;

  /* Just return the adress of the string in the given register */
#ifdef PARANOID
  if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER || !isvariable(ep))
	interror(111, "invalid call to 'putleafcomplex'");
#endif

  /*
   * We have to copy the string/record into the destination. First
   * determine the address of the source and put it into SI.
   */
  pushregs(PUSH_SI | PUSH_DI | PUSH_CX, dp);
  setmeminfo(ep, &si, REG_SI, REG_NONE);
  if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
	/* Variable is static */
	putcode(OP_MOV_WREGIM | rmlow(REG_SI));
	if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
		setreloc();
	putint(si.addr.i);
  } else if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
	modrm = 0;
	if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
		modrm = REG_RELOC_FLAG;
	putlea(REG_SI, si.addr.r | modrm, si.addr.i);
  }

  /* Determine the address of the destination and put it into DI */
  if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
	putcode(OP_MOV_WREGIM | rmlow(REG_DI));		/* mov di,#dest */
	if ((dp->memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
		setreloc();
	putint(dp->addr.i);
  } else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
	putlea(REG_DI, dp->addr.r, dp->addr.i);		/* lea di,dest */
#ifdef PARANOID
  else
	interror(112, "invalid destination type");
#endif

  /* Determine the size of the destination buffer and put it into CX */
  if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE)
	putimmed(OP_MOV, REG_CX, dp->size.i, 0);	/* mov cx,size */
#ifdef PARANOID
  else
	interror(113, "invalid size type");
#endif

  /* 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(ep, dp)
struct expr *ep;
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
 *
 ************************************************************************
 */

/*
 * Determine the size of a non-scalar expression
 */
static addr_t getsize(ep)
struct expr *ep;
{
  addr_t argsize = 0;

  if (isvariable(ep))
	/* Expression is a variable only */
	argsize = ep->spec.var.type->size;
  else if (isconst(ep) && exprtype(ep) == EXPR_STRING)
	/* Expression is a constant string */
	argsize = strlen(ep->spec.cval.val.s);
  else if (isfunc(ep) && ep->spec.func->def.f.ret != NULL)
	/* Expression is a function */
	argsize = ep->spec.func->def.f.ret->size;
  else if (exprtype(ep) == EXPR_STRING)
	/* Expression is a string operation --> we can't forcast it's size */
	argsize = MAX_STR_LEN;
#ifdef PARANOID
  else
	/* There are no other possible operations on non-scalars! */
	interror(43, "cannot determine size of non-scalar");
#endif

  return(argsize);
}



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

  setmeminfo(ep, &si, REG_NONE, REG_NONE);
  if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
	/* String is constant or static */
	putcode(OP_MOV_WREGIM | rmlow(REG_AX));
	if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
		setreloc();
	putint(si.addr.i);
	putpush(REG_AX);
  } else if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
	modrm = 0;
	if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
		modrm = REG_RELOC_FLAG;
	if (si.addr.r == REG_BP) {
		putlea(REG_AX, si.addr.r | modrm, si.addr.i);
		putpush(REG_AX);
	} else {
		putlea(si.addr.r, si.addr.r | modrm, si.addr.i);
		putpush(si.addr.r);
	}
  }
}



/*
 * Put a function or procedure call into the code segment.
 */
void putproc(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
  struct sym *sp = NULL;
  struct expr *curexpr;
  struct meminfo di;
  addr_t tempstrings[MAX_EXPRS];
  addr_t argsize, stack, stackofs;
  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);

  /*
   * Decrement the stack pointer to get enough space for non-scalar arguments.
   * This is necessary for all non-scalars which are passed by value.
   */
  stack = 0;
  for (i = 0; i < ep->exprnum; i++) {
#ifdef PARANOID
	if (ep->exprlist[i] == NULL)
		interror(58, "NULL expression in argument list");
#endif
	tempstrings[i] = -1;
	if (sp->def.f.attribs[i] == ATTR_NONE ||
	    (sp->def.f.attribs[i] == ATTR_CONST &&
	     !isvariable(ep->exprlist[i]) && !isconst(ep->exprlist[i]))) {
		/* Argument has to be passed by value and is not constant */
		if (isnonscalar(ep->exprlist[i]->type)) {
			tempstrings[i] = stack;
			stack += (getsize(ep->exprlist[i]) + 2) & 0xfffe;
		}
	}
#ifdef PARANOID
	else if (!isvariable(ep->exprlist[i]) &&
	         sp->def.f.attribs[i] == ATTR_REF)
		interror(31, "passing a non-variable by reference");
#endif
  }
  if (stack > 0)
	putimmed(OP_IMMED_SUB, REG_SP, stack, 0);		/* sub sp,ofs */

  /*
   * If the function returns a non-scalar, push the destination address and
   * size for the destination.
   */
  stackofs = 0;
  if (isfunc(ep) && isnonscalar(ep->type)) {
	if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
		if (exprtype(ep) == EXPR_STRING) {
			putimmed(OP_MOV, REG_AX, MAX_STR_LEN, 0);
			putpush(REG_AX);
		}
		putcode(OP_MOV_WREGIM | rmlow(REG_AX));
		setreloc();
		putint(0);	/* Use non-scalar "register", the temp area */
		putpush(REG_AX);
	} else {
		if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE) {
			putimmed(OP_MOV, REG_AX, dp->size.i, 0);/* mov ax,size */
			putpush(REG_AX);			/* push ax */
		} else if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG) {
			putpush(dp->size.r);
		}
		if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
			putcode(OP_MOV_WREGIM | rmlow(REG_AX));	/* mov ax,addr */
			setreloc();
			putint(dp->addr.i);
		} else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
			putlea(REG_AX, dp->addr.r, dp->addr.i);	/* lea ax,dest */
#ifdef PARANOID
		else
			interror(77, "invalid destination type");
#endif
		putpush(REG_AX);				/* push ax */
	}
	stackofs += 4;
  }

  /* Now scan through all expressions and push the values onto the stack */
  for (i = 0; i < ep->exprnum; i++) {
	argsize = 0;
	curexpr = ep->exprlist[i];
	if (sp->def.f.attribs[i] == ATTR_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
		 * don't have to repeat that check here.
		 * Strings 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 (exprtype(ep->exprlist[i]) == EXPR_STRING) {
			putimmed(OP_MOV, REG_AX,		/* mov ax,size */
					ep->exprlist[i]->type->size, 0);
			putpush(REG_AX);			/* push ax */
			argsize += 2;
		}
		pushvaraddr(curexpr);				/* push addr */
		argsize += 2;
	} else switch (exprtype(ep->exprlist[i])) {
		case EXPR_NUM:
		case EXPR_ENUM:
			di.memtype = MEM_REGISTER;
			di.addr.r = REG_AX;
			di.t = NULL;
			putintexpr(curexpr, &di);
			putpush(REG_AX);
			argsize = 2;
			break;
		case EXPR_STRING:
			if (sp->def.f.attribs[i] == ATTR_CONST &&
			    (isvariable(curexpr) || isconst(curexpr))) {
				/* For constant string we just pass the addr*/
				if (isvariable(curexpr))
					pushvaraddr(curexpr);
				else {
					di.memtype = MEM_REGISTER | MEM_NOSIZE;
					di.addr.r = REG_AX;
					di.t = NULL;
					putstrexpr(curexpr, &di, FALSE);
					putpush(REG_AX);
				}
			} else {
#ifdef PARANOID
				if (tempstrings[i] < 0)
					interror(32, "passing string by value without buffer");
#endif
				putregop(OP_MOV, REG_SP, REG_BX, 0);
				putlea(REG_BX, REG_BX, stackofs + tempstrings[i]);
				putpush(REG_BX);
				di.memtype = MEM_RELATIVE | MEM_IMMEDIATE;
				di.addr.i = 0;
				di.addr.r = REG_BX;
				di.size.i = getsize(curexpr) + 1;
				di.t = NULL;
				putstrexpr(curexpr, &di, FALSE);
			}
			argsize = 2;
			break;
		case EXPR_BOOL:
			di.memtype = MEM_REGISTER;
			di.addr.r = REG_AL;
			di.t = NULL;
			putboolexpr(curexpr, &di);
			putpush(REG_AX);
			argsize = 2;
			break;
		case EXPR_CHAR:
			di.memtype = MEM_REGISTER;
			di.addr.r = REG_AL;
			di.t = NULL;
			putcharexpr(curexpr, &di);
			putpush(REG_AX);
			argsize = 2;
			break;
		case EXPR_NONE:
			argsize = 0;
			break;
	}
	stackofs += argsize;
  }

  /* Determine if we have to call an internal or a user function/menu */
  if (ep->opcode >= CMD_FIRSTINT) {
	putfunc(ep->opcode, stack + stackofs);
  } else {
	putcode(OP_CALL);
	putint((long)(sp->addr - codeptr - 2));
	if ((stack + stackofs) > 0)
		/* add sp,#stack */
		putimmed(OP_IMMED_ADD, REG_SP, (long)(stack + stackofs), 0);
  }

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

  /* Save the result into the destination */
  if ((dp->memtype & MEM_ADR_MASK) != MEM_NOADDR)
	switch (exprtype(ep)) {
		case EXPR_NUM:
		case EXPR_ENUM:
			putsaveintreg(REG_AX, dp);
			break;
		case EXPR_BOOL:
		case EXPR_CHAR:
			putsaveintreg(REG_AL, dp);
			break;
		default:
			/*
			 * Non-scalars are already copied into destination
			 * buffer by the called function
			 */
			break;
	}
}



syntax highlighted by Code2HTML, v. 0.9.1