/*
 * gencmd.c  -  Code generator (specific routines)
 *
 * 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: gencmd.c,v 1.14 2007/01/06 18:31:27 gkminix Exp $
 */

#define NEED_BINARY 1
#include "mknbi.h"
#include "mgl.h"
#include "gencode.h"
#include "opcodes.h"



/*
 *****************************************************************************
 *
 * Local variables
 */
static addr_t stackofs = 0;		 /* Offset to stack size in proc */
static addr_t addstkofs = 0;		 /* Offset to additional stack size */
static addr_t addstksize = 0;		 /* Additional stack size */
static addr_t addstkmax = 0;		 /* Max. additional stack size */
static slabel_t startlabel = LABEL_NULL; /* Label of start of current proc */
static slabel_t endlabel = LABEL_NULL;	 /* Label of end of current proc */
static struct sym *curproc = NULL;	 /* Current procedure */




/*
 ************************************************************************
 *
 *               Basic code generation routines
 *
 ************************************************************************
 */

/*
 * Put any displacement according to mod and r/m fields into the code segment
 */
static void putdisp __F((modrm, disp, dispreloc),
				byte_t modrm AND
				addr_t disp AND
				int dispreloc)
{
  switch (modrm & OP_MOD_MASK) {
	case OP_MOD_DIRECT:
		/* Direct adressing */
		if ((modrm & OP_RM_MASK) == OP_RM_BP)
			putint((long)disp, dispreloc);
		break;
	case OP_MOD_8BIT:
		/* 8 bit displacement */
#ifdef PARANOID
		if (dispreloc)
			interror(66, "cannot use 8 bit displacement with reloc");
#endif
		putcode((byte_t)(disp & 0xff));
		break;
	case OP_MOD_16BIT:
		/* 16 bit displacement */
		putint((long)disp, dispreloc);
		break;
	case OP_MOD_REG:
		/* Register instruction, no displacement */
		break;
  }
}



/*
 * Put an opcode into the code segment which is either a reg/reg, reg/mem
 * or mem/reg operation.
 */
void putregop __F((op, srcreg, destreg, disp),
				unsigned int op AND
				unsigned int srcreg AND
				unsigned int destreg AND
				addr_t disp)
{
  byte_t c1 = (byte_t)(op & ~(OP_WORD_MASK | OP_DIR_MASK));
  byte_t c2;
  int usereloc;

  /* Determine opcodes to use */
  if ((srcreg & REG_8BIT_FLAG) == 0)
	c1 |= OP_WORD_MASK;
  if ((srcreg & REG_MODRM_FLAG) == REG_MODRM_FLAG) {
	/* operation is mem->reg */
	c1 |= OP_DIR_MASK;
	c2 = rmmid(destreg) | (srcreg & ~REG_16BIT_MASK);
	usereloc = ((srcreg & REG_RELOC_FLAG) == REG_RELOC_FLAG);
  } else if ((destreg & REG_MODRM_FLAG) == REG_MODRM_FLAG) {
	/* operation is reg->mem */
	c2 = rmmid(srcreg) | (destreg & ~REG_16BIT_MASK);
	usereloc = ((destreg & REG_RELOC_FLAG) == REG_RELOC_FLAG);
  } else {
	/* operation is reg->reg */
#ifdef PARANOID
	if (op != OP_MOVZX && op != OP_MOVSX &&
	    (destreg & REG_8BIT_FLAG) != (srcreg & REG_8BIT_FLAG))
		interror(81, "register sizes don't match");
#endif
	/* Optimize 'mov' away if possible */
	if (op == OP_MOV && srcreg == destreg)
		return;
	c1 |= OP_DIR_MASK;
	c2 = rmmid(destreg) | rmlow(srcreg) | OP_MOD_REG;
	usereloc = FALSE;
  }

  /* Move into or out of AX is special */
  if (op == OP_MOV &&
      ((srcreg == REG_AX && destreg == REG_16DISP) ||
       (destreg == REG_AX && srcreg == REG_16DISP))) {
	c1 &= OP_WORD_MASK | OP_DIR_MASK;	/* Preserve word and dir bits */
	c1 |= OP_MOV_AX;
	putcode(c1);
	putint((long)disp, usereloc);
  } else {
	putcode(c1);
	putcode(c2);
	putdisp(c2, disp, usereloc);
  }
}



/*
 * Put an opcode for increment/decrement
 */
void putincdec __F((reg, inc), unsigned int reg AND int inc)
{
  byte_t c1 = (inc ? OP_INC_REG16 : OP_DEC_REG16);

  if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG) {
	putcode(inc ? OP_INC_MEM : OP_DEC_MEM);
	c1 = OP_MOD_REG | (inc ? OP_MEM_INC : OP_MEM_DEC);
  }
  c1 |= rmlow(reg);
  putcode(c1);
}



/*
 * Put an opcode with an immediate value into the code segment
 */
void putimmed __F((op, reg, val, disp),
				unsigned int op AND
				unsigned int reg AND
				long val AND
				addr_t disp)
{
  byte_t c1 = OP_IMMED;
  byte_t c2 = (byte_t)(op & OP_IMMED_MASK);

  /* Do some optimization */
  if ((op == OP_IMMED_ADD || op == OP_IMMED_SUB) && val == 0)
	/* Don't optimize for arithmetic with the carry flag! */
	return;
  if ((op == OP_IMMED_ADD || op == OP_IMMED_SUB) &&
      (val == 1 || val == 2 || val == -1 || val == -2)) {
	/* Substitute ADD/SUB with INC/DEC */
	int inc;

	inc = ((op == OP_IMMED_ADD && val > 0) ||
	       (op == OP_IMMED_SUB && val < 0));
	if (val < 0)
		val = -val;
	while (val > 0) {
		putincdec(reg, inc);
		val--;
	}
	return;
  }

  /* Immediate move into a register is special */
  if (op == OP_MOV) {
	if (val == 0 && (reg & REG_MODRM_FLAG) != REG_MODRM_FLAG) {
		putregop(OP_XOR, reg, reg, 0);
	} else if ((reg & REG_MODRM_FLAG) == REG_MODRM_FLAG) {
		if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG)
			putcode(OP_MOV_MEMIM);
		else
			putcode(OP_MOV_MEMIM | OP_WORD_MASK);
		c2 = (reg & (OP_MOD_MASK | OP_RM_MASK));
		putcode(c2);
		putdisp(c2, disp, (reg & REG_RELOC_FLAG) == REG_RELOC_FLAG);
		if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG)
			putcode((byte_t)(val & 0x00ff));
		else
			putint(val & 0xffff, FALSE);
	} else if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG) {
		putcode(OP_MOV_BREGIM | rmlow(reg));
		putcode((byte_t)(val & 0x00ff));
	} else {
		putcode(OP_MOV_WREGIM | rmlow(reg));
		putint(val & 0xffff, FALSE);
	}
	return;
  }

  /* Compute first byte of opcode */
  if ((reg & REG_8BIT_FLAG) == 0) {
	c1 |= OP_WORD_MASK;
	if ((val & 0xff80) == 0 || (val & 0xff80) == 0xff80)
		c1 |= OP_SIGN_MASK;
  }
  putcode(c1);

  /* Compute second byte of opcode */
  if ((reg & REG_MODRM_FLAG) == REG_MODRM_FLAG)
	c2 |= reg & ~OP_IMMED_MASK;
  else
	c2 |= OP_MOD_REG | rmlow(reg);
  putcode(c2);

  /* Output any displacement and the immediate value */
  putdisp(c2, disp, (reg & REG_RELOC_FLAG) == REG_RELOC_FLAG);
  if ((c1 & OP_WORD_MASK) == 0 || (c1 & OP_SIGN_MASK) == OP_SIGN_MASK)
	putcode((byte_t)(val & 0x00ff));
  else
	putint(val, FALSE);
}



/*
 * Determine the modrm value necessary to access data relative to a register
 */
byte_t getmodrm __F((basereg, disp), unsigned int basereg AND addr_t disp)
{
  byte_t modrm;

  if (is8bitofs(disp))
	modrm = OP_MOD_8BIT | REG_MODRM_FLAG;
  else
	modrm = OP_MOD_16BIT | REG_MODRM_FLAG;
  switch (basereg) {
	case REG_BX:
		modrm |= OP_RM_BX;
		break;
	case REG_BP:
		modrm |= OP_RM_BP;
		break;
	case REG_SI:
		modrm |= OP_RM_IND | OP_RM_SI;
		break;
	case REG_DI:
		modrm |= OP_RM_IND | OP_RM_DI;
		break;
	default:
		modrm = REG_NONE;
		break;
  }
  return(modrm);
}



/*
 * Put an LEA or equivalent instruction into the output code segment
 */
void putlea __F((destreg, basereg, disp),
				unsigned int destreg AND
				unsigned int basereg AND
				addr_t disp)
{
  byte_t modrm;

  /* If we have a relocatable address, we must use an ADD instruction */
  if ((basereg & REG_RELOC_FLAG) == REG_RELOC_FLAG) {
	putregop(OP_MOV, (basereg & ~REG_RELOC_FLAG), destreg, 0);
	putimmed(OP_IMMED_ADD, (destreg | REG_RELOC_FLAG), (long)disp, 0);
	return;
  }

  /* Otherwise we can use LEA */
  if (disp == 0) {
	putregop(OP_MOV, basereg, destreg, 0);	/* mov destreg,basereg */
  } else {
	modrm = getmodrm(basereg, disp);
	if (modrm != REG_NONE)
		putregop(OP_LEA, destreg, modrm, disp);
	else {
		putregop(OP_MOV, basereg, destreg, 0);
		putimmed(OP_IMMED_ADD, destreg, (long)disp, 0);
	}
  }
}



/*
 * Put a push register instruction into the output code segment
 */
void putstackreg __F((reg, push, disp),
				unsigned int reg AND
				int push AND
				addr_t disp)
{
  byte_t modrm;

  if ((reg & REG_MODRM_FLAG) == 0) {
	if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG)
		reg &= REG_8BIT_MASK;
	if (push) {
		putcode(OP_PUSH_REG | rmlow(reg));
		addstksize += 2;
		if (addstksize > addstkmax)
			addstkmax = addstksize;
	} else {
		putcode(OP_POP_REG | rmlow(reg));
		addstksize -= 2;
	}
  } else {
	modrm = (push ? OP_MEM_PUSH : 0) | (reg & ~(OP_MOD_MASK | OP_RM_MASK));
	if (push) {
		putcode(OP_PUSH_MEM);
		addstksize += 2;
		if (addstksize > addstkmax)
			addstkmax = addstksize;
	} else {
		putcode(OP_POP_MEM);
		addstksize -= 2;
	}
	putdisp(modrm, disp, FALSE);
  }
}




/*
 ************************************************************************
 *
 *                  Basic processor support routines
 *
 ************************************************************************
 */

/*
 * Put an immediate push instruction into the code segment
 */
void putimmedpush __F((val, immreloc), long val AND int immreloc)
{
  putcode(OP_PUSH_IMM);
  putint(val, immreloc);
  addstksize += 2;
  if (addstksize > addstkmax)
	addstkmax = addstksize;
}



/*
 * Put a jump opcode into the code segment
 */
void putjmp __F((dest, labelno, jmpcode),
				addr_t dest AND
				slabel_t labelno AND
				unsigned int jmpcode)
{
  addr_t distance;

  /* We can much better optimize if we know the destination address */
  if (labelno != LABEL_NULL) {
	dest = getlabel(labelno);
	if (!islabspecial(dest))
		labelno = LABEL_NULL;
  }

  /* Put the jump instruction into the code segment */
  if (labelno != LABEL_NULL) {
	/* When we have to use a label, the jump offset is always 16 bit */
	if (jmpcode == JMP_UNCOND) {
		putcode(OP_JMP_NEAR);
		putaddr(labelno, 2, FIXUP_REL16);	/* jmp label */
	} else {
		putcode(OP_386);
		putcode(OP_JMP_COND16 | (jmpcode & JMP_MASK));
		putaddr(labelno, 2, FIXUP_REL16);	/* jcond label */
	}
  } else {
	/* Generate jump instruction without label */
	distance = (addr_t)(dest - codeptr - 2);
	if (jmpcode == JMP_UNCOND) {
		if (distance > -128 && distance < 128) {
			putcode(OP_JMP_SHORT);
			putcode((byte_t)(distance & 0xff));
		} else {
			putcode(OP_JMP_NEAR);
			putint((long)((dest - codeptr - 2) & 0xffff), FALSE);
		}
	} else if (distance > -128 && distance < 128) {
		putcode(OP_JMP_COND8 | (jmpcode & JMP_MASK));
		putcode((byte_t)(distance & 0xff));
	} else {
		putcode(OP_386);
		putcode(OP_JMP_COND16 | (jmpcode & JMP_MASK));
		putint((long)(distance & 0xffff), FALSE);
	}
  }
}



/*
 * Put a BOUND instruction into the output code segment. This will set
 * a bounds check against the limits setup by the type given.
 */
void putbound __F((srcreg, tp), unsigned int srcreg AND struct typesdef *tp)
{
  int ax_pushed = FALSE;

  /* No type given, cannot check */
  if (!isscalar(tp))
	return;

  /* Check if we have the bounds in the constant segment already */
  if (tp->def.s.boundaddr < 0) {
	tp->def.s.boundaddr = constptr;
	putconst((byte_t)(tp->def.s.min & 0x00ff));
	putconst((byte_t)((tp->def.s.min >> 8) & 0x00ff));
	putconst((byte_t)(tp->def.s.max & 0x00ff));
	putconst((byte_t)((tp->def.s.max >> 8) & 0x00ff));
  }

  /* If we have a 186+ processor, we can now just use the BOUND instruction */
  if ((srcreg & REG_8BIT_FLAG) == REG_8BIT_FLAG) {
	if (srcreg == REG_AH) {
		putpush(REG_AX);
		ax_pushed = TRUE;
	}
	putregop(OP_MOV, srcreg, REG_AL, 0);
	putcode(OP_CBW);
	srcreg = REG_AX;
  }
  putcode(OP_BOUND);
  putcode(OP_MOD_DIRECT | (srcreg & REG_16BIT_MASK) | OP_RM_BP);
  putint(tp->def.s.boundaddr & 0xffff, FALSE);	/* const seg no reloc */
  if (ax_pushed)
	putpop(REG_AX);
}



/*
 * Put an enter instruction into the output code segment
 */
static void putenter __F((level), int level)
{
  struct sym *sp = rtsymbols[RTCMD_CHKSTK];

  /* Put ENTER instruction and save position of stack size */
  level = (level > 1 ? level - 1 : 0);
  putcode(OP_ENTER);
  stackofs = codeptr;
  putint(0, FALSE);				/* put a dummy stack size */
  putcode((byte_t)(level & 0xff));

  /* Generate code to call stack checking function */
  putcode(OP_PUSH_IMM);				/* push additional stack size */
  addstkofs = codeptr;
  putint(0, FALSE);
  putcode(OP_CALL);				/* call stack checking func */
  putaddr(sp->loc.label, 2, FIXUP_REL16);

  /* Push index registers */
  putpush(REG_DI);
  putpush(REG_SI);
  addstksize = 4;
  addstkmax = 4;
}



/*
 * Put a leave instruction into the output code segment
 */
static void putleave __F((stack), addr_t stack)
{
  /* Save the final stack size */
  if (stackofs > 0) {
	addr_t tmp = codeptr;

	codeptr = stackofs;
	putint((long)(stack & 0xffff), FALSE);
	codeptr = tmp;
  }
  stackofs = 0;

  /* Save the final additional stack size */
  if (addstkofs > 0 && addstkmax > 0) {
	addr_t tmp = codeptr;

	codeptr = addstkofs;
	putint((long)(addstkmax & 0xffff), FALSE);
	codeptr = tmp;
  }
  addstkofs = 0;
  addstksize = 0;
  addstkmax = 0;

  /* Put the leave instructions */
  putpop(REG_SI);
  putpop(REG_DI);
  putcode(OP_LEAVE);
}



/*
 * Put a set-bit instruction into the output code segment
 */
void putsetbit __F((reg, cond), unsigned int reg AND unsigned int cond)
{
  putcode(OP_386);
  putcode(OP_SET_COND | cond);
  putcode(OP_MOD_REG | rmlow(reg));
}




/*
 ************************************************************************
 *
 *                   Conditional and nested commands
 *
 ************************************************************************
 */

/*
 * Structure used to keep track of nested commands
 */
typedef enum {
	NEST_IF,			/* Type for if command */
	NEST_WHILE,			/* Type for while command */
	NEST_REPEAT,			/* Type for repeat command */
	NEST_SELECT			/* Type for select command */
} nesttype;

struct nest {
	nesttype     type;		/* Type of nesting statement */
	slabel_t     startlabel;	/* Label to continue another loop */
	slabel_t     endlabel;		/* Label to jump out of the loop */
	slabel_t     nextlabel;		/* Label for misc. purposes */
	struct nest *next;
};

static struct nest *nestlist = NULL;	/* List of nested commands */



/*
 * Put the condition handling code into the output buffer
 */
static void putcond __F((ep), struct expr *ep)
{
  struct meminfo di;
  unsigned char jmpcode;

#ifdef PARANOID
  if (exprtype(ep) != EXPR_BOOL)
	interror(59, "invalid expression in condition");
#endif

  /*
   * We simply leave comparison to the boolean expression routine, but
   * don't let it return a value. This doesn't work if we have a boolean
   * leaf node. In that case we have to load the value into AL.
   */
  di.t = NULL;
  if (!isleaf(ep) && !isfunc(ep)) {
	di.memtype = MEM_NOADDR | MEM_NOSIZE;
	putscalarexpr(ep, &di);
  } else {
	di.memtype = MEM_REGISTER | MEM_NOSIZE;
	di.addr.r = REG_AL;
	putscalarexpr(ep, &di);				/* mov al,val */
	putregop(OP_OR, REG_AL, REG_AL, 0);		/* or al,al */
  }

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

  /* Actually write the jump instruction */
#ifdef PARANOID
  if (nestlist->nextlabel == LABEL_NULL)
	interror(83, "invalid condition jump label");
#endif
  putjmp(0, nestlist->nextlabel, jmpcode);
}



/*
 * Create a new nest structure
 */
static void newnest __F((type, ep), nesttype type AND struct expr *ep)
{
  struct nest *np;

  /* Create new nesting structure */
  np = (struct nest *)nbmalloc(sizeof(struct nest));
  np->next = nestlist;
  np->type = type;
  np->startlabel = setlabel(LABEL_NULL, LADDR_CODEPTR);
  np->endlabel = setlabel(LABEL_NULL, LADDR_UNDEF);
  np->nextlabel = LABEL_NULL;
  nestlist = np;

  /* Create any instructions required at the beginning of the loop */
  switch (type) {
	case NEST_IF:
	case NEST_WHILE:
		nestlist->nextlabel = setlabel(LABEL_NULL, LADDR_UNDEF);
		putcond(ep);
		break;
	case NEST_REPEAT:
	case NEST_SELECT:
		break;
  }
}



/*
 * Put out the code for the 'else' branch of an 'if' statement
 */
static void putelse __F_NOARGS
{
#ifdef PARANOID
  if (nestlist == NULL || nestlist->type != NEST_IF)
	interror(62, "missing if statement for else");
#endif

  putjmp(0, nestlist->endlabel, JMP_UNCOND);
  setlabel(nestlist->nextlabel, LADDR_CODEPTR);
}



/*
 * Put an item branch of a select command into the output code segment
 */
static void putitem __F((ep), struct expr *ep)
{
  int itemchar, itemno;
  slabel_t jmplabel;

#ifdef PARANOID
  if (nestlist == NULL || nestlist->type != NEST_SELECT)
	interror(63, "no select statement for item");
  if (ep == NULL || ep->opcode != CMD_NONE || ep->exprnum < 1)
	interror(64, "invalid item expression");
#endif

  /* Let the last item terminate and point to this new one */
  if (nestlist->nextlabel != LABEL_NULL) {
	putjmp(0, nestlist->endlabel, JMP_UNCOND);
	setlabel(nestlist->nextlabel, LADDR_CODEPTR);
  }

  /* Create a new label for the next item, the old one is no longer needed */
  nestlist->nextlabel = setlabel(LABEL_NULL, LADDR_UNDEF);

  /* Create a new label to bypass compare instructions */
  jmplabel = (ep->exprnum > 1) ? setlabel(LABEL_NULL, LADDR_UNDEF) : LABEL_NULL;

  /* Now put out new compare instructions for all items */
  itemchar = 0xff;
  for (itemno = 0; itemno < ep->exprnum; itemno++) {
#ifdef PARANOID
	if (ep->exprlist[itemno] == NULL ||
	    exprtype(ep->exprlist[itemno]) != EXPR_NUM ||
	    !isconst(ep->exprlist[itemno]) ||
	    ep->exprlist[itemno]->spec.cval.val.i > 9)
		interror(68, "invalid item number");
#endif

	/* Set itemchar for next compare instruction */
	if (ep->exprlist[itemno]->spec.cval.val.i < 0)
		itemchar = 0xff;
	else
		itemchar = (ep->exprlist[itemno]->spec.cval.val.i & 0x0f) + '0';

	/* Bypass any following compare instructions */
	if (itemno < (ep->exprnum - 1)) {
							/* cmp al,itemchar */
		putimmed(OP_IMMED_CMP, REG_AL, itemchar, 0);
							/* jz jmplabel */
		putjmp(0, jmplabel, JMP_JZ);
	} else {
							/* cmp al,itemchar */
		putimmed(OP_IMMED_CMP, REG_AL, itemchar, 0);
							/* jnz nextlabel */
		putjmp(0, nestlist->nextlabel, JMP_JNZ);
	}
  }

  /* Define address of "bypass label" */
  if (jmplabel != LABEL_NULL)
	setlabel(jmplabel, LADDR_CODEPTR);
}



/*
 * Break command to jump to end of innermost loop command
 */
static void putbreak __F_NOARGS
{
  struct nest *np;

  /* Search innermost loop command */
  for (np = nestlist; np != NULL; np = np->next)
	if (np->type == NEST_SELECT ||
	    np->type == NEST_WHILE ||
	    np->type == NEST_REPEAT)
		break;
  if (np == NULL) {
	error("invalid break command", FALSE);
	return;
  }

  /* Jump to end of loop */
  putjmp(0, np->endlabel, JMP_UNCOND);
}



/*
 * End nested command
 */
static void putendnest __F((ep), struct expr *ep)
{
  struct nest *temp;

#ifdef PARANOID
  if (nestlist == NULL)
	interror(65, "invalid endnest, nestlist empty");
#endif

  /* Terminate current command */
  switch (nestlist->type) {
	case NEST_IF:
		if (getlabel(nestlist->nextlabel) == LADDR_UNDEF)
			setlabel(nestlist->nextlabel, LADDR_CODEPTR);
		break;
	case NEST_WHILE:
		putjmp(0, nestlist->startlabel, JMP_UNCOND);
		if (getlabel(nestlist->nextlabel) == LADDR_UNDEF)
			setlabel(nestlist->nextlabel, LADDR_CODEPTR);
		break;
	case NEST_REPEAT:
		nestlist->nextlabel = nestlist->startlabel;
		putcond(ep);
		break;
	case NEST_SELECT:
		if (nestlist->nextlabel != LABEL_NULL) {
			setlabel(nestlist->nextlabel, LADDR_CODEPTR);
			putjmp(0, nestlist->startlabel, JMP_UNCOND);
		}
		break;
  }
  setlabel(nestlist->endlabel, LADDR_CODEPTR);

  /* Remove current nested command from list */
  temp = nestlist;
  nestlist = nestlist->next;
  free(temp);
}




/*
 ************************************************************************
 *
 *                Determine the address of a variable
 *
 ************************************************************************
 */

/*
 * Put code to move a variable address into a register
 */
void putldaddr __F((reg, dp), unsigned int reg AND struct meminfo *dp)
{
  int usereloc = ((dp->memtype & MEM_USERELOC) == MEM_USERELOC);

  if (memaddr(dp) == MEM_ABSOLUTE) {
	putcode(OP_MOV_WREGIM | rmlow(reg));
	putint(dp->addr.i, usereloc);
  } else if (memaddr(dp) == MEM_RELATIVE)
	putlea(reg, dp->addr.r, dp->addr.i);
#ifdef PARANOID
  else
	interror(82, "invalid memory address type");
#endif
}



/*
 * Return a pointer to the inuse flag corresponding to a register
 */
__attribute__((const)) static int *getinuse __F((reg), unsigned int reg)
{
  switch (reg) {
	case REG_BX:
		return(&bx_inuse);
	case REG_CX:
		return(&cx_inuse);
	case REG_DX:
		return(&dx_inuse);
	case REG_SI:
		return(&si_inuse);
	case REG_DI:
		return(&di_inuse);
	default:
		return(NULL);
  }
}



/*
 * Set the meminfo record for a base variable symbol
 */
static void setbaseinfo __F((vp, dp, basereg),
				struct varinfo *vp AND
				struct meminfo *dp AND
				unsigned int basereg)
{
  struct sym *sp = vp->spec.basesym;
  unsigned int basedisp = REG_BP_16DISP;

#ifdef PARANOID
  if (sp == NULL)
	interror(109, "invalid variable");
#endif

  if (isfuncsym(sp)) {
#ifdef PARANOID
	if (sp->def.f.ret == NULL || sp != curproc)
		interror(71, "invalid function assignment");
#endif
	/*
	 * Assigning a return value to a function means putting the value
	 * into some space provided by the caller. If the return value is
	 * non-scalar, there is a pointer to the return value space on the
	 * stack. With scalar return types, the space for the return value
	 * is directly located on the stack and accessible through BP.
	 */
	if (isnonscalar(sp->def.f.ret)) {
		putregop(OP_MOV, REG_BP_16DISP, basereg, sp->def.f.retaddr);
		dp->memtype = MEM_RELATIVE;
		dp->addr.i = 0;
		dp->addr.r = basereg;
	} else if (isscalar(sp->def.f.ret)) {
		dp->memtype = MEM_RELATIVE;
		dp->addr.i = sp->def.f.retaddr;
		dp->addr.r = REG_BP;
	}
#ifdef PARANOID
	else
		interror(50, "invalid function return type");
#endif
  } else if (isvarsym(sp)) {
#ifdef PARANOID
	if (sp->level > curlevel)
		interror(73, "invalid symbol level");
#endif
	/*
	 * Handle a variable as the destination. We have to consider the
	 * following cases:
	 *
	 *  1.)  Variable is scalar and can be directly accessed because:
	 *          - it's defined within the current context
	 *          - it's passed to a function by value
	 *  2.)  Variable is scalar and has been passed by reference. In this
	 *       case we only have a pointer on the stack.
	 *  3.)  Variable is non-scalar and can be directly accessed because it
	 *       has been defined within the current context.
	 *  4.)  Variable is non-scalar and has been passed by value. In this
	 *       case we also only have a pointer to the variable value on the
	 *       stack. However, the variable value has to be copied into a
	 *       temporary memory first (usually located on the stack as well),
	 *       so that the original variable remains untouched even when the
	 *       called function modifies the variable.
	 *  5.)  Variable is non-scalar and has been passed by reference. We
	 *       also have a pointer on the stack like in case 4.) but without
	 *       a temporary copy.
	 *  6.)  Variable is static
	 *
	 * Therefore, cases 1.) and 3.) can be handled together, like cases
	 * 2.), 4.) and 5.) are also handled the same.
	 */
	if (sp->def.v.class == CLASS_STATIC ||
	    sp->def.v.class == CLASS_STATICRO) {
		/* This catches static variables, e.g. case 6.) */
		dp->memtype = MEM_ABSOLUTE | MEM_USERELOC;
		dp->addr.i = sp->loc.addr;
	} else if ((isscalar(sp->def.v.t) && sp->def.v.class != CLASS_REF) ||
	           sp->loc.addr < 0) {
		/*
		 * If we have a variable, which is defined within the current
		 * context, or a variable which has NOT been passed by re-
		 * ference (non-scalars are always passed with pointers, not
		 * by reference, which is not the same but very similar), we
		 * can directly access the variable without having to calculate
		 * the address at runtime.
		 * This catches cases 1.) and 3.)
		 */
		if (sp->level == curlevel) {
			dp->memtype = MEM_RELATIVE;
			dp->addr.i = sp->loc.addr;
			dp->addr.r = REG_BP;
		} else {
			putregop(OP_MOV, REG_BP_16DISP, basereg,
						-((sp->level - 1) * 2));
			dp->memtype = MEM_RELATIVE;
			dp->addr.i = sp->loc.addr;
			dp->addr.r = basereg;
		}
	} else {
		/*
		 * It's somewhat more complicated with non-scalars: they
		 * can either be declared within the current context, or
		 * passed as an argument. In the latter case, we only have
		 * a pointer to the variable space on the stack. The first
		 * case is handled above (with sp->loc.addr < 0).
		 * Also with all variables passed by reference we have a
		 * pointer on the stack instead of the actual value, so we
		 * have to load the pointer in order to be able to load
		 * or save a value.
		 * This catches cases 2.), 4.) and 5.)
		 */
		if (sp->level == curlevel) {
			putregop(OP_MOV, REG_BP_16DISP, basereg, sp->loc.addr);
			dp->memtype = MEM_RELATIVE;
			dp->addr.i = 0;
			dp->addr.r = basereg;
		} else {
			switch (basereg) {
				case REG_BX:
					basedisp = REG_BX_16DISP;
					break;
				case REG_BP:
					basedisp = REG_BP_16DISP;
					break;
				case REG_SI:
					basedisp = REG_SI_16DISP;
					break;
				case REG_DI:
					basedisp = REG_DI_16DISP;
					break;
#ifdef PARANOID
				default:
					interror(110, "invalid base register");
#endif
			}
			putregop(OP_MOV, REG_BP_16DISP, basereg,
						-((sp->level - 1) * 2));
			putregop(OP_MOV, basedisp, basereg, sp->loc.addr);
			dp->memtype = MEM_RELATIVE;
			dp->addr.i = 0;
			dp->addr.r = basereg;
		}
	}
  }
#ifdef PARANOID
  else
	interror(125, "invalid variable symbol");
#endif
}



/*
 * Set meminfo record for an array. The record should already contain
 * the data for the base element (i.e. the array itself). The varinfo
 * record should contain the index element.
 */
static void setarrayinfo __F((vp, prev, dp, basereg),
				struct varinfo *vp AND
				struct varinfo *prev AND
				struct meminfo *dp AND
				unsigned int basereg)
{
  struct typesdef *elemtype;
  struct typesdef *indextype;
  struct meminfo di;
  int *addrinuse, *sizeinuse;

#ifdef PARANOID
  if (prev == NULL || (!isarray(prev->vartype) && !isstring(prev->vartype)))
	interror(101, "invalid array index");
  if (vp->spec.index == NULL || !isscalar(vp->spec.index->type))
	interror(105, "invalid index type");
  if (memaddr(dp) != MEM_ABSOLUTE && memaddr(dp) != MEM_RELATIVE)
	interror(106, "invalid array addressing");
#endif

  elemtype = vp->vartype;
  indextype = prev->vartype->def.a.indextype;

  /* If the index is constant, we can compute it at compile time */
  if (isconst(vp->spec.index)) {
	long indexval;

	indexval = getord(vp->spec.index);
	if (indexval < indextype->def.s.min ||
	    indexval > indextype->def.s.max) {
		error("array subscript out of range", FALSE);
		return;
	}
	indexval   -= indextype->def.s.min;
	dp->addr.i += indexval * elemtype->size;
	return;
  }

  /*
   * Otherwise we have to compute it at runtime. For this we have to mark
   * the base and size registers as used.
   */
  addrinuse = NULL;
  if (memaddr(dp) == MEM_RELATIVE)
	addrinuse = getinuse(dp->addr.r);
  if (addrinuse != NULL)
	(*addrinuse)++;

  sizeinuse = NULL;
  if (memsize(dp) == MEM_SIZEREG)
	sizeinuse = getinuse(dp->size.r);
  if (sizeinuse != NULL)
	(*sizeinuse)++;

  /*
   * We can now setup a new meminfo for the subexpression and put
   * out the code to compute the index.
   */
  di.memtype = MEM_REGISTER | MEM_NOSIZE;
  di.t = NULL;
  switch (exprtype(vp->spec.index)) {
	case EXPR_ENUM:
	case EXPR_NUM:
		di.addr.r = REG_AX;
		putscalarexpr(vp->spec.index, &di);
		break;
	case EXPR_CHAR:
	case EXPR_BOOL:
		di.addr.r = REG_AL;
		putscalarexpr(vp->spec.index, &di);
		putregop(OP_XOR, REG_AH, REG_AH, 0);
		break;
	default:
		/* Can't happen, already checked above */
		break;
  }

  /* Check that the index is within bounds */
  putbound(REG_AX, indextype);

  /* If the min of the index is not zero, we have to subtract the min */
  if (indextype->def.s.min != 0) {
	if (indextype->def.s.min != 1)
		putimmed(OP_IMMED_SUB, REG_AX, indextype->def.s.min, 0);
	else
		putcode(OP_DEC_REG16 | rmlow(REG_AX));
  }

  /* The index is now in AX, so that we can compute the offset */
  if (elemtype->size == 2) {
	putcode(OP_SHIFT_1 | OP_WORD_MASK);
	putcode(OP_MOD_REG | OP_SHIFT_SHL | rmlow(REG_AX));
  } else if (elemtype->size == 4) {
	putcode(OP_SHIFT_NUM | OP_WORD_MASK);
	putcode(OP_MOD_REG | OP_SHIFT_SHL | rmlow(REG_AX));
	putcode(2);
  } else if (elemtype->size == 8) {
	putcode(OP_SHIFT_NUM | OP_WORD_MASK);
	putcode(OP_MOD_REG | OP_SHIFT_SHL | rmlow(REG_AX));
	putcode(3);
  } else if (elemtype->size > 1) {
	if (dx_inuse)
		putpush(REG_DX);
	putimmed(OP_MOV, REG_DX, elemtype->size, 0);
	putcode(OP_NONIM | OP_WORD_MASK);
	putcode(OP_NONIM_MUL | OP_MOD_REG | rmlow(REG_DX));
	if (dx_inuse)
		putpop(REG_DX);
  }

  /* We have the offset in AX now, so we can finally set the meminfo */
  if (memaddr(dp) == MEM_ABSOLUTE) {
	unsigned int reg;

	reg = basereg;
	if ((dp->memtype & MEM_USERELOC) == MEM_USERELOC)
		reg |= REG_RELOC_FLAG;
	putimmed(OP_MOV, reg, dp->addr.i, 0);
	putregop(OP_ADD, REG_AX, basereg, 0);
	dp->memtype = (dp->memtype & ~MEM_ADR_MASK) | MEM_RELATIVE;
	dp->addr.i = 0;
	dp->addr.r = basereg;
  } else if (memaddr(dp) == MEM_RELATIVE) {
	if (dp->addr.r == REG_BP) {
		putlea(basereg, dp->addr.r, dp->addr.i);
		putregop(OP_ADD, REG_AX, basereg, 0);
		dp->addr.r = basereg;
	} else {
		putlea(dp->addr.r, dp->addr.r, dp->addr.i);
		putregop(OP_ADD, REG_AX, dp->addr.r, 0);
	}
	dp->addr.i = 0;
  }

  /* Restore the inuse indicators */
  if (addrinuse)
	(*addrinuse)--;
  if (sizeinuse)
	(*sizeinuse)--;
}



/*
 * Set meminfo record for a record variable. The meminfo should already contain
 * the data for the base element (i.e. the record itself). The varinfo should
 * contain the record variant specification.
 */
static void setrecordinfo __F((vp, prev, dp, basereg),
				struct varinfo *vp AND
				struct varinfo *prev AND
				struct meminfo *dp AND
				unsigned int basereg)
{
  struct sym *sp = vp->spec.recsym;

#ifdef PARANOID
  if (prev== NULL || !isrecord(prev->vartype) || sp == NULL || !isrecsym(sp))
	interror(107, "invalid record element");
  if (memaddr(dp) != MEM_ABSOLUTE && memaddr(dp) != MEM_RELATIVE)
	interror(108, "invalid record addressing");
#endif

  /* Just add the offset into the record */
  dp->addr.i += sp->loc.addr;
}



/*
 * Set meminfo record for a pointer dereferentiation. The meminfo should
 * already contain the data for the base element (i.e. the pointer).
 */
static void setpointerinfo __F((vp, prev, dp, basereg),
				struct varinfo *vp AND
				struct varinfo *prev AND
				struct meminfo *dp AND
				unsigned int basereg)
{
  byte_t modrm = REG_NONE;

#ifdef PARANOID
  if (prev == NULL || !ispointer(prev->vartype))
	interror(126, "invalid pointer dereferentiation");
  if (memaddr(dp) != MEM_ABSOLUTE && memaddr(dp) != MEM_RELATIVE)
	interror(104, "invalid pointer addressing");
#endif

  /* Determine the register where to store the pointer into */
  if (memaddr(dp) == MEM_RELATIVE) {
	if (dp->addr.r == REG_SI || dp->addr.r == REG_DI)
		basereg = dp->addr.r;
	modrm = getmodrm(dp->addr.r, dp->addr.i);
  } else if (memaddr(dp) == MEM_ABSOLUTE) {
	modrm = REG_16DISP;
	if ((dp->memtype & MEM_USERELOC) == MEM_USERELOC)
		modrm |= REG_RELOC_FLAG;
  }

  /* Now load the pointer into the base register */
  if (modrm == REG_NONE) {
	if (basereg != REG_BX) {
		if (bx_inuse)
			putpush(REG_BX);
		putlea(REG_BX, dp->addr.r, dp->addr.i);
		putregop(OP_MOV, REG_BX_0DISP, basereg, 0);
		if (bx_inuse)
			putpop(REG_BX);
	} else {
		if (si_inuse)
			putpush(REG_SI);
		putlea(REG_SI, dp->addr.r, dp->addr.i);
		putregop(OP_MOV, REG_SI_0DISP, basereg, 0);
		if (si_inuse)
			putpop(REG_SI);
	}
  } else
	putregop(OP_MOV, modrm, basereg, dp->addr.i);

  /* Finally set the meminfo record correctly */
  dp->memtype = (dp->memtype & ~MEM_ADR_MASK) | MEM_RELATIVE;
  dp->addr.i = 0;
  dp->addr.r = basereg;
}



/*
 * Set the meminfo size information
 */
static void setsizeinfo __F((vp, dp, sizereg),
				struct varinfo *vp AND
				struct meminfo *dp AND
				unsigned int sizereg)
{
  struct sym *sp;
  struct typesdef *tp;

  /*
   * Determine the size of a non-scalar base variable.  If it's not a
   * string, a static variable, not an argument or an argument passed
   * by value, we know the size from the variable type. Only for strings
   * passed by reference the destination size has been pushed onto the
   * stack together with the address.
   */
  tp = vp->vartype;
  if (sizereg != REG_NONE && vp->cmd == basevar && tp->type == EXPR_STRING) {
	sp = vp->spec.basesym;
	if (isfuncsym(sp)) {
		putregop(OP_MOV, REG_BP_16DISP, sizereg, sp->def.f.retaddr + 2);
		dp->memtype |= MEM_SIZEREG;
		dp->size.i = 0;
		dp->size.r = sizereg;
		return;
	} else if (isvarsym(sp) &&
	           sp->def.v.class == CLASS_REF && sp->loc.addr >= 0) {
		if (sp->level == curlevel) {
			putregop(OP_MOV, REG_BP_16DISP, sizereg,
							sp->loc.addr + 2);
		} else {
			if (si_inuse ||
			    (memaddr(dp) == MEM_RELATIVE &&
			     dp->addr.r == REG_SI))
				putpush(REG_SI);
			putregop(OP_MOV, REG_BP_16DISP, REG_SI,
						-((sp->level - 1) * 2));
			putregop(OP_MOV, REG_SI_16DISP, sizereg, sp->loc.addr);
			if (si_inuse ||
			    (memaddr(dp) == MEM_RELATIVE &&
			     dp->addr.r == REG_SI))
				putpop(REG_SI);
		}
		dp->memtype |= MEM_SIZEREG;
		dp->size.r = sizereg;
		return;
	}
  }

  /* For all other cases, we determine the size of the variable from the type */
  dp->memtype |= MEM_IMMEDIATE;
  dp->size.i = tp->size;
}



/*
 * Set a meminfo record according to the variable pointed to in the
 * expression.
 */
void setmeminfo __F((ep, dp, basereg, sizereg),
				struct expr *ep AND
				struct meminfo *dp AND
				unsigned int basereg AND
				unsigned int sizereg)
{
  struct varinfo *vp, *prev;

#ifdef PARANOID
  if (!isvariable(ep))
	interror(57, "expression is not a variable");
#endif

  /*
   * Find out which register we can use in case we need to calculate an
   * address.
   */
  if (basereg == REG_NONE) {
	if (!di_inuse)
		basereg = REG_DI;
	else if (!si_inuse)
		basereg = REG_SI;
	else if (!bx_inuse)
		basereg = REG_BX;
	else if (!cx_inuse)
		basereg = REG_CX;
	else if (!dx_inuse)
		basereg = REG_DX;
	else
		basereg = REG_AX;
  }

  /*
   * Handle the first element in the varinfo record (which is the
   * last element in a record reference).
   */
  vp = ep->spec.var;
#ifdef PARANOID
  if (vp == NULL || vp->cmd != basevar)
	interror(103, "missing variable specification");
#endif
  setbaseinfo(vp, dp, basereg);

  /* Now scan through the variable info list */
  while (vp->next != NULL) {
	prev = vp;
	vp = vp->next;
	switch (vp->cmd) {
		case arrayindex:
			setarrayinfo(vp, prev, dp, basereg);
			break;
		case recordelement:
			setrecordinfo(vp, prev, dp, basereg);
			break;
		case ptrderef:
			setpointerinfo(vp, prev, dp, basereg);
			break;
		default:
			break;
	}
  }

  /* Set the variable size */
  setsizeinfo(vp, dp, sizereg);

  /* Save the variable type */
  dp->t = ep->type;
}





/*
 ************************************************************************
 *
 *                      Interface to parser
 *
 ************************************************************************
 */

/*
 * With an assignment to a complex variable we have a problem: the
 * expression might involve the variable itself, like:  a := b + a.
 * In this case we cannot allow the expression calculation routines
 * to put the result directly into the destination, but have to
 * use a temporary area as the initial destination. After doing the
 * expression the data has to be copied out of the temp area into
 * the final variable.
 * Using a temp area is not necessary if the destination of the
 * assignment is a temporary variable.
 */
static void handlecomplex __F((ep, dp, dest),
				struct expr *ep AND
				struct meminfo *dp AND
				struct expr *dest)
{
  struct meminfo di;
  unsigned int tempreg = REG_DI;
  int needtmp = (!isleaf(ep) && !istempvar(dest));
  int *addrinuse = NULL;
  int *sizeinuse = NULL;
  addr_t size = (addr_t)0;


  /*
   * We don't need to copy if the expression is a leaf node.
   */
  di = *dp;
  if (needtmp) {
	/*
	 * If the expression is a leaf node, there is no need to use a
	 * temporary buffer. Otherwise we have to provide temp space on
	 * the stack. We don't have to care much about used registers,
	 * because this routine is only called from 'docmd'.
	 */
	addrinuse = NULL;
	if (memaddr(dp) == MEM_RELATIVE)
		addrinuse = getinuse(dp->addr.r);
	if (addrinuse != NULL)
		/* Prevent base pointer to get used by expression */
		(*addrinuse)++;

	sizeinuse = NULL;
	if (memsize(dp) == MEM_SIZEREG)
		sizeinuse = getinuse(dp->size.r);
	if (sizeinuse != NULL)
		/* Prevent size register to get used by expression */
		(*sizeinuse)++;

	if (memaddr(dp) == MEM_RELATIVE)
		tempreg = (dp->addr.r == REG_DI ? REG_SI :
		           dp->addr.r == REG_BX ? REG_DI : REG_BX);
	else
		tempreg = REG_DI;

	if (memsize(dp) == MEM_SIZEREG) {
		putregop(OP_SUB, dp->size.r, REG_SP, 0);
		addstksize += MAX_REC_SIZE;
	} else {
		size = (dp->size.i + 1) & 0xfffe;
		putimmed(OP_IMMED_SUB, REG_SP, size, 0);
		addstksize += size;
	}
	if (addstksize > addstkmax)
		addstkmax = addstksize;
	putregop(OP_MOV, REG_SP, tempreg, 0);
	di.memtype = (di.memtype & ~MEM_ADR_MASK) | MEM_RELATIVE;
	di.addr.i = 0;
	di.addr.r = tempreg;
  }

  /* Process the expression */
  putcomplexexpr(ep, &di);

  /*
   * If we used a temporary data area, copy the result into the variable.
   * Here we can safely use any register we want, because this routine
   * returns back to the parser.
   */
  if (needtmp) {
	if (addrinuse != NULL)
		(*addrinuse)--;
	if (sizeinuse != NULL)
		(*sizeinuse)--;

	if (tempreg == REG_DI) {
		putregop(OP_MOV, REG_DI, REG_AX, 0);
		tempreg = REG_AX;
	}

	/* Set destination address into DI */
	putldaddr(REG_DI, dp);

	/*
	 * Set source register to our temporary area, the expression is
	 * not allowed to touch the base register in 'di', e.g. our tempreg.
	 */
	if (tempreg == REG_SI) {
		putregop(OP_MOV, REG_SI, REG_AX, 0);	/* Save it for later */
		tempreg = REG_AX;
	} else
		putregop(OP_MOV, tempreg, REG_SI, 0);

	/* Set copy size */
	if (memsize(dp) == MEM_SIZEREG)
		putregop(OP_MOV, dp->size.r, REG_CX, 0);
	else
		putimmed(OP_MOV, REG_CX, dp->size.i, 0);

	/* Now do the copy */
	putcode(OP_REP);
	putcode(OP_MOVSB);

	/* Restore the stack */
	if (size > 0) {
		if (dp->size.i != size)
			putimmed(OP_IMMED_ADD, REG_SI, size - dp->size.i, 0);
		addstksize -= size;
	} else
		addstksize -= MAX_REC_SIZE;
	putregop(OP_MOV, REG_SI, REG_SP, 0);
  }
}



/*
 * Handle high level commands and break them down into smaller pieces.
 * This is basically one big switch statement.
 */
void docmd __F((cmd, sp, ep1, ep2),
				codes cmd AND
				struct sym *sp AND
				struct expr *ep1 AND
				struct expr *ep2)
{
  struct meminfo di;

  /*
   * Don't do any low-level processing if we have compile errors. There
   * will be no output anyway.
   */
  if (errors > 0)
	return;

  /*
   * Set the debugging info, and then process the low-level command.
   */
  setdebug();
  switch (cmd) {
	case CODE_PROC_START:
		/* Start a new procedure or function */
		curproc = sp;
		if (sp->loc.label == LABEL_NULL) {
			/* Assign a new label if not already assigned */
			sp->loc.label = setlabel(LABEL_NULL, LADDR_CODEPTR);
		}
		putenter(sp->level + 1);
		if (ep1 != NULL) {
			/* Call some routine before setting the start label */
			memzero(&di, sizeof(di));
			di.memtype = MEM_NOADDR | MEM_NOSIZE;
			di.t = NULL;
			putproc(ep1, &di);
		}
		startlabel = setlabel(LABEL_NULL, LADDR_CODEPTR);
		endlabel = setlabel(LABEL_NULL, LADDR_UNDEF);
		break;
	case CODE_PROC_END:
		/* Quit current procedure and return to previous procedure */
#ifdef PARANOID
		if (nestlist != NULL)
			interror(60, "unterminated nested command");
#endif
		setlabel(endlabel, LADDR_CODEPTR);
		if (isfuncsym(curproc) && curproc->def.f.ret != NULL) {
			if (isscalar(curproc->def.f.ret) &&
			    curproc->def.f.ret->size < 2)
				putregop(OP_MOV, REG_BP_16DISP | REG_8BIT_FLAG,
						REG_AL, curproc->def.f.retaddr);
			else
				putregop(OP_MOV, REG_BP_16DISP,
						REG_AX, curproc->def.f.retaddr);
		}
		putleave(curproc->def.f.varsize);
		if (curproc->def.f.argsize > 0) {
			putcode(OP_RETNUM);
			putint((long)(curproc->def.f.argsize), FALSE);
		} else
			putcode(OP_RET);
		dofixup();
		startlabel = LABEL_NULL;
		endlabel = LABEL_NULL;
		curproc = NULL;
		break;
	case CODE_RESTART:
#ifdef PARANOID
		if (startlabel == LABEL_NULL)
			interror(40, "invalid restart instruction");
#endif
		putjmp(0, startlabel, JMP_UNCOND);
		break;
	case CODE_RETURN:
#ifdef PARANOID
		if (endlabel == LABEL_NULL)
			interror(53, "invalid return instruction");
#endif
		putjmp(0, endlabel, JMP_UNCOND);
		break;
	case CODE_CALL_PROC:
		/* Call a procedure */
		memzero(&di, sizeof(di));
		di.memtype = MEM_NOADDR | MEM_NOSIZE;
		di.t = NULL;
		putproc(ep1, &di);
		break;
	case CODE_LABEL:
		if (sp->loc.label == LABEL_NULL) {
			/* Create an undefined label (forward reference) */
			sp->loc.label = setlabel(LABEL_NULL, LADDR_UNDEF);
		} else if (getlabel(sp->loc.label) == LADDR_UNDEF) {
			/* Assign the current address to a label */
			setlabel(sp->loc.label, LADDR_CODEPTR);
		} else
			error("label already declared", FALSE);
		break;
	case CODE_GOTO:
#ifdef PARANOID
		if (sp->loc.label == LABEL_NULL)
			interror(57, "invalid goto instruction");
#endif
		putjmp(0, sp->loc.label, JMP_UNCOND);
		break;
	case CODE_ASSIGN:
		setmeminfo(ep1, &di, REG_DI, REG_CX);
		switch (exprtype(ep2)) {
			case EXPR_POINTER:
			case EXPR_NUM:
			case EXPR_ENUM:
			case EXPR_CHAR:
			case EXPR_BOOL:
				/* Scalar and pointer assignment */
				putscalarexpr(ep2, &di);
				break;
			case EXPR_STRING:
			case EXPR_ARRAY:
			case EXPR_RECORD:
			case EXPR_IPADDR:
				/* Non-scalar assignment */
				handlecomplex(ep2, &di, ep1);
				break;
			default:
#ifdef PARANOID
				interror(69, "invalid symbol type in assignment");
#endif
				break;
		}
		break;
	case CODE_IF:
		/* Start IF command */
		newnest(NEST_IF, ep1);
		break;
	case CODE_ELSE:
		/* Start ELSE command */
		putelse();
		break;
	case CODE_WHILE:
		/* Start WHILE command */
		newnest(NEST_WHILE, ep1);
		break;
	case CODE_REPEAT:
		/* Start REPEAT command */
		newnest(NEST_REPEAT, NULL);
		break;
	case CODE_SELECT:
		/* Start select command */
		newnest(NEST_SELECT, NULL);
		break;
	case CODE_ITEM:
		/* Start new ITEM selection */
		putitem(ep1);
		break;
	case CODE_BREAK:
		/* Terminate current nested command */
		putbreak();
		break;
	case CODE_ENDNEST:
		/* Terminate current nesting command */
		putendnest(ep1);
		break;
  }
}

