/* * gencode.c - Code generator * * 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: gencode.c,v 1.4 2003/01/25 23:29:44 gkminix Exp $ */ #define NEED_BINARY 1 #include "mknbi.h" #include "mgl.h" #include "gencode.h" #include "opcodes.h" /* * Global variables */ addr_t dataptr = 0; /* Current data pointer */ addr_t constptr = 0; /* Current constant pointer */ addr_t codeptr = 0; /* Current code pointer */ addr_t startadr = 0; /* Start address */ /* * The following structure is used to hold debugging information */ struct debuginfo { addr_t addr; int lineno; }; /* * Local variables */ static __u8 codebuf[CODESIZE]; /* Buffer for code */ static __u8 constbuf[DATASIZE]; /* Buffer for constant data */ static addr_t relocbuf[RELOCSIZE]; /* Buffer for reloc entries */ static unsigned int relocnum = 0; /* Current reloc entry */ static struct debuginfo debugbuf[DEBUGSIZE]; /* Buffer for debugging info */ static unsigned int debugnum = 0; /* Number of debug entries */ static addr_t stackofs = 0; /* Size of local stack area */ /* ************************************************************************ * * Basic code generation routines * ************************************************************************ */ /* * Put one byte into the code segment */ void putcode(c) unsigned int c; { if (codeptr >= CODESIZE) { prnerr0("code segment too large"); exit(EXIT_MGL_CODESIZE); } codebuf[codeptr++] = (__u8)(c & 0xff); } /* * Put one byte into the data segment */ void putconst(c) unsigned int c; { if (constptr >= CONSTSIZE) { prnerr0("constant data segment too large"); exit(EXIT_MGL_CONSTSIZE); } constbuf[constptr++] = (__u8)(c & 0xff); } /* * Put one string into the data segment */ addr_t putstring(str) char *str; { char *cp; addr_t i, j, end; /* Let's see if we have the string in the constant buffer already */ end = constptr - strlen(str) + 1; for (i = 0; i < end; i++) { for (cp = str, j = i; *cp && j < constptr; cp++, j++) if ((__u8)(*cp & 0xff) != constbuf[j]) break; if (j < constptr && !*cp && (__u8)(*cp & 0xff) == constbuf[j]) return(i); } /* No, it's not in yet, create a new string in the buffer */ i = constptr; for (cp = str; *cp; cp++) putconst(*cp & 0xff); putconst(0); return(i); } /* * Put one integer value into the code segment */ void putint(val) long val; { putcode((unsigned int)(val & 0x00ff)); putcode((unsigned int)((val >> 8) & 0x00ff)); } /* * Set a new relocation entry at present code position */ void setreloc() { if (relocnum >= RELOCSIZE) { prnerr0("relocation buffer overflow"); exit(EXIT_MGL_RELOCOVRFLOW); } relocbuf[relocnum++] = codeptr; } /* * Put a jump opcode into the code segment */ void putjmp(dest, jmpcode) addr_t dest; unsigned int jmpcode; { addr_t distance; distance = dest - codeptr - 2; if (jmpcode == JMP_UNCOND) { if (distance > -128 && distance < 128) { putcode(OP_JMP_SHORT); putcode((unsigned int)(distance & 0xff)); } else { putcode(OP_JMP_NEAR); putint((dest - codeptr - 2) & 0xffff); } } else { #ifdef PARANOID if (distance <= -128 || distance >= 128) interror(40, "conditional jump outside of range"); #endif putcode(OP_JMP_COND | jmpcode); putcode((unsigned int)(distance & 0xff)); } } /* * Put any displacement according to mod and r/m fields into the code segment */ static void putdisp(modrm, disp, usereloc) unsigned int modrm; addr_t disp; int usereloc; { switch (modrm & OP_MOD_MASK) { case OP_MOD_DIRECT: /* Direct adressing */ if ((modrm & OP_RM_MASK) == OP_RM_BP) { if (usereloc) setreloc(); putint((long)disp); } break; case OP_MOD_8BIT: /* 8 bit displacement */ #ifdef PARANOID if (usereloc) interror(66, "cannot use 8 bit displacement with reloc"); #endif putcode((unsigned int)(disp & 0xff)); break; case OP_MOD_16BIT: /* 16 bit displacement */ if (usereloc) setreloc(); putint((long)disp); 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(op, srcreg, destreg, disp) unsigned int op; unsigned int srcreg; unsigned int destreg; addr_t disp; { unsigned char c1 = op & ~((unsigned int)(OP_WORD_MASK | OP_DIR_MASK)); unsigned char c2; int usereloc; /* Determine opcodes to use */ if ((destreg & REG_8BIT_FLAG) == 0 || (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 & ~(unsigned int)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 & ~(unsigned int)REG_16BIT_MASK); usereloc = ((destreg & REG_RELOC_FLAG) == REG_RELOC_FLAG); } else { /* operation is reg->reg */ #ifdef PARANOID if ((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); if (usereloc) setreloc(); putint((long)disp); } else { putcode(c1); putcode(c2); putdisp(c2, disp, usereloc); } } /* * Put an opcode with an immediate value into the code segment */ void putimmed(op, reg, val, disp) unsigned int op; unsigned int reg; long val; addr_t disp; { unsigned char c1 = OP_IMMED; unsigned char c2 = 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; /* 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((unsigned int)(val & 0x00ff)); else putint(val & 0xffff); } else if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG) { putcode(OP_MOV_BREGIM | rmlow(reg)); putcode((unsigned int)(val & 0x00ff)); } else { putcode(OP_MOV_WREGIM | rmlow(reg)); putint(val & 0xffff); } 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 & ~(unsigned int)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((unsigned int)(val & 0x00ff)); else putint(val); } /* * Determine the modrm value necessary to access data relative to a register */ unsigned int getmodrm(basereg, disp) unsigned int basereg; addr_t disp; { unsigned int modrm; if ((disp & 0xff80) == 0 || (disp & 0xff80) == 0xff80) 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(destreg, basereg, disp) unsigned int destreg; unsigned int basereg; addr_t disp; { unsigned int 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 & ~(unsigned int)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(reg, push, disp) unsigned int reg; int push; addr_t disp; { unsigned int 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)); else putcode(OP_POP_REG | rmlow(reg)); } else { modrm = (push ? OP_MEM_PUSH : 0) | (reg & ~((unsigned int)(OP_MOD_MASK | OP_RM_MASK))); if (push) putcode(OP_PUSH_MEM); else putcode(OP_POP_MEM); putdisp(modrm, disp, FALSE); } } /* * Put a call to the runtime module into the output code segment */ void putfunc(func, stack) int func; addr_t stack; { if (func >= 0) { putimmed(OP_MOV, REG_AH, func, 0); putcode(OP_CALL); putint((long)(ENTRYOFS - codeptr - 2)); } if (stack > 0) /* add sp,#stack */ putimmed(OP_IMMED_ADD, REG_SP, (long)stack, 0); } /* ************************************************************************ * * Code to support different processors * ************************************************************************ */ /* * 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(srcreg, tp) unsigned int srcreg; struct typesdef *tp; { int ax_pushed = FALSE; /* No type given, cannot check */ if (tp == NULL || !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((unsigned char)(tp->def.s.min & 0x00ff)); putconst((unsigned char)((tp->def.s.min >> 8) & 0x00ff)); putconst((unsigned char)(tp->def.s.max & 0x00ff)); putconst((unsigned char)((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; } if (is186) { putcode(OP_BOUND); putcode(OP_MOD_DIRECT | (srcreg & REG_16BIT_MASK) | OP_RM_BP); putint(tp->def.s.boundaddr & 0xffff); /* const seg --> no reloc */ } else { putimmed(OP_IMMED_CMP, srcreg, tp->def.s.min, 0); putjmp(codeptr + 6, JMP_JC); putimmed(OP_IMMED_CMP, srcreg, tp->def.s.max, 0); putjmp(codeptr + 4, JMP_JBE); putcode(OP_INT); putcode(5); } if (ax_pushed) putpop(REG_AX); } /* * Put an enter instruction into the output code segment */ static void putenter(stack, level) addr_t stack; int level; { int i; stackofs = stack & 0xffff; if (is186) { level = (level > 1 ? level - 1 : 0); putcode(OP_ENTER); putint((long)(stack & 0xffff)); putcode((unsigned int)(level & 0xff)); } else { putpush(REG_BP); /* push bp */ if (level > 1) { putregop(OP_MOV, REG_SP, REG_AX, 0); /* mov ax,sp */ for (i = 0; i < (level - 2); i++) { /* sub bp,2 */ putimmed(OP_IMMED_SUB, REG_BP, 2, 0); /* push [bp] */ putstackreg(REG_BP_8DISP, TRUE, 0); } putpush(REG_AX); /* push ax */ putregop(OP_MOV, REG_AX, REG_BP, 0); /* mov bp,ax */ } else putregop(OP_MOV, REG_SP, REG_BP, 0); /* mov bp,sp */ if (stackofs > 0) /* sub sp, stackofs */ putimmed(OP_IMMED_SUB, REG_SP, (long)stackofs, 0); } putpush(REG_DI); putpush(REG_SI); } /* * Put a leave instruction into the output code segment */ static void putleave() { putpop(REG_SI); putpop(REG_DI); if (is186) putcode(OP_LEAVE); else { if (stackofs > 0) /* * Don't reset stackofs here - CODE_PROC_END might * be called more than once in a procedure! */ putregop(OP_MOV, REG_BP, REG_SP, 0); /* mov sp,bp */ putcode(OP_POP_REG | rmlow(REG_BP)); /* pop bp */ } } /* * Put a set-bit instruction into the output code segment */ void putsetbit(reg, cond) unsigned int reg; unsigned int cond; { if (!is386) { putimmed(OP_MOV, reg, 1, 0); putjmp(codeptr + 4, cond); putregop(OP_XOR, reg, reg, 0); } else { 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 */ #define MAX_JUMP 32 /* Maximum number of jumps */ 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 */ int selecterr; /* Error in select, skip rest */ int jmpnum; /* Number of addresses to jumps */ addr_t jmptab[MAX_JUMP]; /* Pointers to jump instructions */ addr_t jmpdest[MAX_JUMP]; /* Addresses where jumps are going to */ addr_t loopaddr; /* Return address for loops */ int breaknum; /* Number of break statements */ int breaktabsize; /* Size of break address table */ addr_t *breaktab; /* Pointers to jumps for break */ struct nest *next; }; static struct nest *nestlist = NULL; /* List of nested commands */ /* * Put the condition handling code into the output buffer */ static void putcond(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)) { di.memtype = MEM_NOADDR | MEM_NOSIZE; putboolexpr(ep, &di); } else { di.memtype = MEM_REGISTER | MEM_NOSIZE; di.addr.r = REG_AL; putboolexpr(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_JZ; break; case CMD_GT: jmpcode = JMP_JG; break; case CMD_GE: jmpcode = JMP_JGE; break; case CMD_LT: jmpcode = JMP_JL; break; case CMD_LE: jmpcode = JMP_JLE; break; case CMD_NE: jmpcode = JMP_JNZ; break; default: jmpcode = JMP_JNZ; break; /* for AND, OR, XOR */ } putjmp(codeptr + 5, jmpcode); /* jcond $+3 */ nestlist->jmptab[nestlist->jmpnum++] = codeptr; codeptr += 3; /* provide space for jmp near */ } /* * Create a new nest structure */ static void newnest(type) nesttype type; { struct nest *np; np = (struct nest *)nbmalloc(sizeof(struct nest)); np->next = nestlist; np->type = type; np->loopaddr = codeptr; nestlist = np; } /* * Put out the code for the 'else' branch of an 'if' statement */ static void putelse() { #ifdef PARANOID if (nestlist == NULL || nestlist->type != NEST_IF || nestlist->jmpnum != 1) interror(62, "missing if statement for else"); #endif nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr + 3;/* where if jumps to */ nestlist->jmptab[nestlist->jmpnum++] = codeptr; /* mark new jump */ codeptr += 3; /* provide space for jmp near */ } /* * Put an item branch of a select command into the output code segment */ static void putitem(ep) struct expr *ep; { int itemchar; #ifdef PARANOID if (nestlist == NULL || nestlist->type != NEST_SELECT) interror(63, "no select statement for item"); if (ep == NULL || exprtype(ep) != EXPR_NUM || !isconst(ep) || ep->spec.cval.val.i > 9) interror(64, "invalid item number"); #endif /* Check that we don't have too many items in select statement */ if (nestlist->jmpnum >= MAX_JUMP) { error("too many items in select statement"); nestlist->selecterr++; } if (nestlist->selecterr > 0) return; /* Let the last item terminate and point to this new one */ if (nestlist->jmpnum > 0) { putjmp(nestlist->loopaddr, JMP_UNCOND); nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr; } /* Now put out a new compare instruction */ if (ep->spec.cval.val.i < 0) itemchar = 0xff; else itemchar = (ep->spec.cval.val.i & 0x0f) + '0'; putimmed(OP_IMMED_CMP, REG_AL, itemchar, 0); /* cmp ax,itemchar */ putjmp(codeptr + 5, JMP_JZ); /* jz $+3 */ nestlist->jmptab[nestlist->jmpnum++] = codeptr; codeptr += 3; /* provide space for jmp near */ } /* * Break command to jump to end of innermost loop command */ static void putbreak() { struct nest *np; addr_t *newbuf; int newsize; /* 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"); return; } /* Lets see, if our break pointer buffer is large enough */ if (np->breaktab == NULL || np->breaknum >= np->breaktabsize) { newsize = np->breaktabsize + 32; newbuf = (addr_t *)nbmalloc(sizeof(addr_t) * newsize); if (np->breaktab != NULL) { memcpy(newbuf, np->breaktab, sizeof(addr_t) * np->breaknum); free(np->breaktab); } np->breaktabsize = newsize; np->breaktab = newbuf; } /* Add current code pointer into the break table, and put out new jump */ np->breaktab[np->breaknum++] = codeptr; codeptr += 3; /* provide space for jmp near */ } /* * End nested command */ static void putendnest(ep) struct expr *ep; { struct nest *temp; addr_t tmpadr; int i; #ifdef PARANOID if (nestlist == NULL) interror(65, "invalid endnest, nestlist empty"); #endif /* Terminate current command */ switch (nestlist->type) { case NEST_IF: nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr; break; case NEST_WHILE: putjmp(nestlist->loopaddr, JMP_UNCOND); nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr; break; case NEST_REPEAT: putcond(ep); nestlist->jmpdest[nestlist->jmpnum - 1] = nestlist->loopaddr; break; case NEST_SELECT: nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr; putjmp(nestlist->loopaddr, JMP_UNCOND); break; } /* Resolve jump list */ tmpadr = codeptr; for (i = 0; i < nestlist->jmpnum; i++) { codeptr = nestlist->jmptab[i]; putjmp(nestlist->jmpdest[i], JMP_UNCOND); } /* Resolve break list */ if (nestlist->breaktab != NULL) { for (i = 0; i < nestlist->breaknum; i++) { codeptr = nestlist->breaktab[i]; putjmp(tmpadr, JMP_UNCOND); } free(nestlist->breaktab); } codeptr = tmpadr; /* Remove current nested command from list */ temp = nestlist; nestlist = nestlist->next; free(temp); } /* ************************************************************************ * * Determine the address of a variable * ************************************************************************ */ /* * Return a pointer to the inuse flag corresponding to a register */ static int *getinuse(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 terminal symbol (i.e. a symbol which * appears last in a record reference). */ static void setfirstinfo(sp, dp, basereg, sizereg) struct sym *sp; struct meminfo *dp; unsigned int basereg; unsigned int sizereg; { unsigned int basedisp = REG_BP_16DISP; if (isfuncsym(sp)) { #ifdef PARANOID if (sp->def.f.ret == NULL || sp != curproc) interror(72, "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; if (sp->def.f.ret->type == EXPR_STRING && sizereg != REG_NONE) { putregop(OP_MOV, REG_BP_16DISP, sizereg, sp->def.f.retaddr + 2); dp->memtype |= MEM_SIZEREG; dp->size.i = 0; dp->size.r = sizereg; } else { dp->memtype |= MEM_IMMEDIATE; dp->size.i = sp->def.f.ret->size; } } 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. * * Therefore, cases 1.) and 3.) can be handled together, like cases * 2.), 4.) and 5.) are also handled the same. */ if ((isscalar(sp->def.v.t) && sp->def.v.attr != ATTR_REF) || sp->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. */ if (sp->level == curlevel) { dp->memtype = MEM_RELATIVE; dp->addr.i = sp->addr; dp->addr.r = REG_BP; } else if (sp->level <= 0) { /* This catches static variables and record elements */ dp->memtype = MEM_ABSOLUTE; dp->addr.i = sp->addr; } else { putregop(OP_MOV, REG_BP_16DISP, basereg, -((sp->level - 1) * 2)); dp->memtype = MEM_RELATIVE; dp->addr.i = sp->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->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. */ if (sp->level == curlevel) { putregop(OP_MOV, REG_BP_16DISP, basereg, sp->addr); dp->memtype = MEM_RELATIVE; dp->addr.i = 0; dp->addr.r = basereg; } else if (sp->level <= 0) { /* This catches record elements, should never happen */ dp->memtype = MEM_ABSOLUTE; dp->addr.i = sp->addr; } 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->addr); dp->memtype = MEM_RELATIVE; dp->addr.i = 0; dp->addr.r = basereg; } } /* * Determine the size of a non-scalar. If it's not a strings, 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 refe- * rence the destination size has been pushed onto the stack together * with the address. */ if (isnonscalar(sp->def.v.t)) { if (sp->def.v.t->type == EXPR_STRING && sp->def.v.attr == ATTR_REF && sp->addr >= 0 && sp->level > 0 && sizereg != REG_NONE) { if (sp->level == curlevel) { putregop(OP_MOV, REG_BP_16DISP, sizereg, sp->addr + 2); } else { if (si_inuse || basereg == 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->addr); if (si_inuse || basereg == REG_SI) putpop(REG_SI); } dp->memtype |= MEM_SIZEREG; dp->size.r = sizereg; } else { dp->memtype |= MEM_IMMEDIATE; dp->size.i = sp->def.v.t->size; } } } } /* * Set meminfo record for an array. The record should already contain * the data for the base element (i.e. the array itself). */ static void setarrayinfo(vp, dp, basereg) struct varinfo *vp; struct meminfo *dp; unsigned int basereg; { struct typesdef *elemtype = vp->type->def.a.basetype; struct typesdef *indextype = vp->type->def.a.indextype; struct meminfo di; int *addrinuse, *sizeinuse; long indexval; #ifdef PARANOID if (!isscalar(indextype)) interror(105, "invalid index type"); #endif /* If the index is constant, we can compute it at compile time */ if (isconst(vp->index)) { indexval = getord(vp->index); if (indexval < indextype->def.s.min || indexval > indextype->def.s.max || (indexval - indextype->def.s.min) >= vp->type->def.a.elementnum) { error("array subscript out of range"); 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 ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) addrinuse = getinuse(dp->addr.r); if (addrinuse != NULL) (*addrinuse)++; sizeinuse = NULL; if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG) sizeinuse = getinuse(dp->size.r); if (sizeinuse != NULL) (*sizeinuse)++; /* * We can now setup a new meminfo for the subexpression and let put * out the code to compute the index. */ di.memtype = MEM_REGISTER | MEM_NOSIZE; di.t = NULL; switch (indextype->type) { case EXPR_ENUM: case EXPR_NUM: di.addr.r = REG_AX; putintexpr(vp->index, &di); break; case EXPR_BOOL: di.addr.r = REG_AL; putboolexpr(vp->index, &di); putcode(OP_CBW); break; case EXPR_CHAR: di.addr.r = REG_AL; putcharexpr(vp->index, &di); putcode(OP_CBW); 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) putimmed(OP_SUB, REG_AX, indextype->def.s.min, 0); /* The index is now in AX, so that we can compute the offset */ if (elemtype->size == 2) { putcode(OP_SHIFT_1); putcode(OP_MOD_REG | OP_SHIFT_SHL | rmlow(REG_AX)); } else if (elemtype->size == 4) { putcode(OP_SHIFT_1); putcode(OP_MOD_REG | OP_SHIFT_SHL | rmlow(REG_AX)); putcode(OP_SHIFT_1); putcode(OP_MOD_REG | OP_SHIFT_SHL | rmlow(REG_AX)); } 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 ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) { putimmed(OP_MOV, basereg, 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 ((dp->memtype & MEM_ADR_MASK) == 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). This is very similar * but not the same as 'setfirstinfo'. */ static void setrecordinfo(vp, dp, basereg) struct varinfo *vp; struct meminfo *dp; unsigned int basereg; { struct sym *sp = vp->symbol; #ifdef PARANOID if (sp == NULL || (!isvarsym(sp) && !isfuncsym(sp))) interror(107, "invalid type for record element"); if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE && dp->addr.r == REG_BP) interror(108, "BP register not allowed for record elements"); #endif /* Handle function return value. This always has to be a terminal. */ if (isfuncsym(sp)) { #ifdef PARANOID if (sp->def.f.ret == NULL || sp->def.f.ret->type != EXPR_RECORD || sp != curproc) interror(106, "invalid function assignment"); #endif if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) { putregop(OP_MOV, REG_BP_16DISP, basereg, sp->def.f.retaddr); dp->memtype = (dp->memtype & ~MEM_ADR_MASK) | MEM_RELATIVE; dp->addr.r = basereg; } else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) putregop(OP_ADD, REG_BP_16DISP, dp->addr.r, sp->def.f.retaddr); return; } /* Handle variable record elements */ #ifdef PARANOID if (sp->def.v.t->type != EXPR_RECORD) interror(109, "invalid record"); #endif if (sp->level == curlevel) { if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) { dp->memtype = (dp->memtype & ~MEM_ADR_MASK) | MEM_RELATIVE; dp->addr.r = REG_BP; } else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) { putregop(OP_ADD, REG_BP, dp->addr.r, 0); } } else if (sp->level > 0) { if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) { putregop(OP_MOV, REG_BP_16DISP, basereg, -((sp->level - 1) * 2)); dp->memtype = (dp->memtype & ~MEM_ADR_MASK) | MEM_RELATIVE; dp->addr.r = basereg; } else { putregop(OP_ADD, REG_BP_16DISP, dp->addr.r, -((sp->level - 1) * 2)); } } dp->addr.i += sp->addr; } /* * Set a meminfo record according to the variable pointed to in the * expression. */ void setmeminfo(ep, dp, basereg, sizereg) struct expr *ep; struct meminfo *dp; unsigned int basereg; 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). */ #ifdef PARANOID if (ep->spec.var.symbol == NULL || (!isvarsym(ep->spec.var.symbol) && !isfuncsym(ep->spec.var.symbol))) interror(103, "missing symbol in variable specification"); #endif setfirstinfo(ep->spec.var.symbol, dp, basereg, sizereg); if (ep->spec.var.index != NULL) setarrayinfo(&(ep->spec.var), dp, basereg); /* * Now scan through the variable info list and handle all record references. * At this point, if we have a record element, the meminfo record contains * the offset to the element from the start of the record. */ prev = &(ep->spec.var); for (vp = ep->spec.var.next; vp != NULL; vp = vp->next) { #ifdef PARANOID if (vp->symbol == NULL || !isvarsym(vp->symbol)) interror(104, "missing symbol in variable specification"); #endif prev = vp; setrecordinfo(vp, dp, basereg); if (vp->index != NULL) setarrayinfo(vp, dp, basereg); } /* * Finally we have to find out if the variable is in the static area, so * that we have to set the relocation flag. Also save the variable type * into the meminfo record for later use by the bound checker. */ if (prev->symbol->level <= 0) dp->memtype |= MEM_RELOC_FLAG; dp->t = ep->type; } /* ************************************************************************ * * Interface to parser * ************************************************************************ */ /* * Save debug info for current statement */ static void setdebug() { if (debug && (debugnum == 0 || debugbuf[debugnum - 1].lineno != lineno)) { debugbuf[debugnum].addr = codeptr; debugbuf[debugnum].lineno = lineno; if (++debugnum >= DEBUGSIZE) { prnerr0("debugging info buffer overflow"); exit(EXIT_MGL_DEBUGOVRFLOW); } } } /* * Compile a timeout value and put it into DX. */ static void puttimeout(ep) struct expr *ep; { struct meminfo di; if (ep != NULL) { di.memtype = MEM_REGISTER; di.addr.r = REG_DX; di.t = NULL; putintexpr(ep, &di); } else putregop(OP_XOR, REG_DX, REG_DX, 0); } /* * 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. */ static void handlecomplex(ep, dp) struct expr *ep; struct meminfo *dp; { struct meminfo di; unsigned int tempreg = REG_DI; unsigned int modrm; int *addrinuse = NULL; int *sizeinuse = NULL; int needcopy; addr_t size; /* * We don't need to copy if the expression is a leaf node, or it's * a function of which we know that it doesn't use any complex * variables: * 1.) it has to be an internal function * 2.) it has to have no string arguments * Presently, there is only one such function: getbootp. */ needcopy = (!isleaf(ep) && ep->opcode != CMD_GETBOOTP); di = *dp; if (needcopy) { /* * 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 ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) addrinuse = getinuse(dp->addr.r); if (addrinuse != NULL) /* Prevent base pointer to get used by expression */ (*addrinuse)++; sizeinuse = NULL; if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG) sizeinuse = getinuse(dp->size.r); if (sizeinuse != NULL) /* Prevent size register to get used by expression */ (*sizeinuse)++; if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) tempreg = (dp->addr.r == REG_DI ? REG_SI : dp->addr.r == REG_BX ? REG_DI : REG_BX); else tempreg = REG_DI; if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG) putregop(OP_SUB, dp->size.r, REG_SP, 0); else { if (exprtype(ep) == EXPR_STRING) size = (dp->size.i + 2) & 0xfffe; else size = (dp->size.i + 1) & 0xfffe; putimmed(OP_SUB, REG_SP, size, 0); } putregop(OP_MOV, REG_SP, tempreg, 0); di.memtype = (di.memtype & ~(MEM_ADR_MASK | MEM_RELOC_FLAG)) | MEM_RELATIVE; di.addr.i = 0; di.addr.r = tempreg; } /* Process the expression */ switch (exprtype(ep)) { case EXPR_STRING: putstrexpr(ep, &di, FALSE); break; case EXPR_ARRAY: case EXPR_RECORD: putcomplexexpr(ep, &di); break; default: break; } /* * 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 (needcopy) { 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 */ if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) { modrm = 0; if ((dp->memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG) modrm = REG_RELOC_FLAG; putlea(REG_DI, dp->addr.r | modrm, dp->addr.i); } else if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) { putcode(OP_MOV_WREGIM | rmlow(REG_DI)); if ((dp->memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG) setreloc(); putint(dp->addr.i); } /* * 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 ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG) putregop(OP_MOV, dp->size.r, REG_CX, 0); else { if (exprtype(ep) == EXPR_STRING) size = (dp->size.i + 2) & 0xfffe; else size = (dp->size.i + 1) & 0xfffe; putimmed(OP_MOV, REG_CX, size, 0); } /* Now do the copy */ putcode(OP_REP); putcode(OP_MOVSB); /* Restore the stack */ 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(cmd, sp, ep1, ep2) codes cmd; struct sym *sp; struct expr *ep1; struct expr *ep2; { struct meminfo di; setdebug(); switch (cmd) { case CODE_PROC_START: /* Start a new procedure or function */ putenter(sp->addr, sp->level); break; case CODE_PROC_END: /* Quit current procedure and return to previous procedure */ 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(); putcode(OP_RET); break; case CODE_RESTART: /* Restart is simply a jump to the beginning of current menu */ #ifdef PARANOID if (curproc == NULL || !isfuncsym(curproc)) interror(60, "invalid curproc"); #endif putjmp(curproc->def.f.restartaddr, JMP_UNCOND); break; case CODE_CALL_PROC: /* Call a procedure */ #ifdef PARANOID if (!isproc(ep1)) interror(71, "invalid procedure call"); #endif di.memtype = MEM_NOADDR | MEM_NOSIZE; di.t = NULL; putproc(ep1, &di); break; case CODE_ASSIGN: setmeminfo(ep1, &di, REG_DI, REG_CX); switch (exprtype(ep2)) { case EXPR_NUM: case EXPR_ENUM: /* Integer assignment */ putintexpr(ep2, &di); break; case EXPR_CHAR: /* Character assignment */ putcharexpr(ep2, &di); break; case EXPR_BOOL: /* Boolean assignment */ putboolexpr(ep2, &di); break; case EXPR_STRING: case EXPR_ARRAY: case EXPR_RECORD: /* Non-scalar assignment */ handlecomplex(ep2, &di); break; #ifdef PARANOID default: interror(69, "invalid symbol type in assignment"); break; #endif } break; case CODE_INT_PRINT: /* Print integer number */ di.memtype = MEM_REGISTER; di.addr.r = REG_CX; di.t = NULL; putintexpr(ep1, &di); putfunc(FUNC_iprint, 0); break; case CODE_STR_PRINT: /* Print string */ di.memtype = MEM_REGISTER; di.addr.r = REG_SI; di.t = NULL; putstrexpr(ep1, &di, FALSE); putfunc(FUNC_sprint, 0); break; case CODE_CHAR_PRINT: /* Print character */ di.memtype = MEM_REGISTER; di.addr.r = REG_CL; di.t = NULL; putcharexpr(ep1, &di); putfunc(FUNC_cprint, 0); break; case CODE_INT_GET: /* Read an integer number */ puttimeout(ep2); setmeminfo(ep1, &di, REG_NONE, REG_NONE); putfunc(FUNC_iget, 0); putsaveintreg(REG_AX, &di); break; case CODE_CHAR_GET: /* Read a character */ puttimeout(ep2); setmeminfo(ep1, &di, REG_NONE, REG_NONE); putfunc(FUNC_cget, 0); putsaveintreg(REG_AL, &di); break; case CODE_STR_GET: /* Read a string */ puttimeout(ep2); setmeminfo(ep1, &di, REG_NONE, REG_CX); if ((di.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) { putcode(OP_MOV_WREGIM | rmlow(REG_DI)); if ((di.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG) setreloc(); putint((long)(di.addr.i & 0xffff)); #ifdef PARANOID } else if ((di.memtype & MEM_ADR_MASK) != MEM_RELATIVE) { interror(48, "invalid meminfo in get string"); #endif } else { putlea(REG_DI, di.addr.r, di.addr.i); } if ((di.memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE) putimmed(OP_MOV, REG_CX, (long)di.size.i, 0); else putregop(OP_MOV, di.size.r, REG_CX, 0); putfunc(FUNC_sget, 0); break; case CODE_PUSH_IPADDR: /* Push an IP address onto the stack */ if (sp == NULL) { putregop(OP_XOR, REG_CX, REG_CX, 0); /* xor cx,cx */ putpush(REG_CX); /* push cx */ putpush(REG_CX); /* push cx */ } else { putcode(OP_MOV_WREGIM | rmlow(REG_CX)); /* mov cx,val */ putint((*(__u16 *)&(sp->name[2])) & 0xffff); putpush(REG_CX); /* push cx */ putcode(OP_MOV_WREGIM | rmlow(REG_CX)); /* mov cx,val */ putint((*(__u16 *)&(sp->name[0])) & 0xffff); putpush(REG_CX); /* push cx */ } break; case CODE_LOAD: /* Load new file. Two IP addresses are on the stack already */ di.memtype = MEM_REGISTER; di.addr.r = REG_AX; di.t = NULL; putstrexpr(ep1, &di, FALSE); putpush(REG_AX); putfunc(FUNC_load, 10); break; case CODE_IF: /* Start IF command */ newnest(NEST_IF); putcond(ep1); break; case CODE_ELSE: /* Start ELSE command */ putelse(); break; case CODE_WHILE: /* Start WHILE command */ newnest(NEST_WHILE); putcond(ep1); break; case CODE_REPEAT: /* Start REPEAT command */ newnest(NEST_REPEAT); break; case CODE_SELECT: /* Start select command */ newnest(NEST_SELECT); puttimeout(ep1); putfunc(FUNC_getsel, 0); 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; } } /* ************************************************************************ * * * ************************************************************************ */ /* * Main routine of the code generator. It parses the input file and * assembles the output binary. */ int gencode(infile) char *infile; { char *fname; long codesize; long relocval; long debugptr; int outfile; unsigned int j; /* Clear code and data buffers and write runtime module into code segment */ memset(codebuf, OP_NOP, sizeof(codebuf)); memset(constbuf, 0, sizeof(constbuf)); memcpy(codebuf, runtime_data, runtime_data_size); /* Setup global variables */ codeptr = runtime_data_size; dataptr = (MAX_STR_LEN + 2) & 0xfffe; constptr = 0; /* Parse the input file */ yylexinit(infile); yyparse(); yylexterm(); if (errors > 0) { prnerr1("%d error(s) encountered, aborting", errors); exit(EXIT_MGL_COMPERRS); } if (warnings > 0) prnerr1("%d warning(s)", warnings); /* * Write the debugging info into the constant segment */ debugptr = 0; if (debug) { debugptr = constptr; for (j = 0; j < debugnum; j++) { putconst((unsigned int)(debugbuf[j].addr & 0x00ff)); putconst((unsigned int)((debugbuf[j].addr >> 8) & 0x00ff)); putconst((unsigned int)(debugbuf[j].lineno & 0x00ff)); putconst((unsigned int)((debugbuf[j].lineno >> 8) & 0x00ff)); } putconst(0); /* end marker */ putconst(0); } /* * Scan through the relocation table and add the value at the relocation * to the size of the constant segment */ for (j = 0; j < relocnum; j++) { relocval = codebuf[relocbuf[j]] & 0x00ff; relocval += ((codebuf[relocbuf[j]+1]) << 8) & 0xff00; relocval += constptr; codebuf[relocbuf[j]] = (relocval & 0x00ff); codebuf[relocbuf[j]+1] = ((relocval >> 8) & 0x00ff); } /* Write the start address into the code module */ if (startadr == 0) { prnerr0("no start screen defined"); exit(EXIT_MGL_NOSTART); } assign(*((__u16 *)&(codebuf[STARTOFS])), htot(startadr & 0xffff)); /* Align the data segment to a segment boundary and write code size */ codesize = (codeptr + 0x000f) & 0xfff0; assign(*((__u16 *)&(codebuf[CODESIZEOFS])), htot(codesize & 0xffff)); assign(*((__u16 *)&(codebuf[CONSTSIZEOFS])), htot(constptr & 0xffff)); assign(*((__u16 *)&(codebuf[DATASIZEOFS])), htot(dataptr & 0xffff)); assign(*((__u16 *)&(codebuf[DEBUGOFS])), htot(debugptr & 0xffff)); /* Open temporary output file */ if ((fname = tempnam(NULL, "mgl")) == NULL) { prnerr0("unable to generate name of temporary file"); exit(EXIT_TEMPNAME); } if ((outfile = open(fname, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0644)) < 0) { prnerr1("unable to open temporary file %s", fname); exit(EXIT_OPEN); } if (verbose < 2) unlink(fname); free(fname); /* Write all buffers into output file */ (void)nbwrite(codebuf, (unsigned int)codesize, outfile); (void)nbwrite(constbuf, (unsigned int)constptr, outfile); if (lseek(outfile, 0L, 0) < 0) { prnerr0("unable to seek in temporary file"); exit(EXIT_SEEK); } return(outfile); }