/* * genexpr.c - Code generator for expressions * * Copyright (C) 1997-2003 Gero Kuhlmann * * 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; } }