text: /*  Pawn compiler - Binary code generation (the "assembler")
   *
   *  Copyright (c) ITB CompuPhase, 1997-2005
   *
   *  This software is provided "as-is", without any express or implied warranty.
   *  In no event will the authors be held liable for any damages arising from
   *  the use of this software.
   *
   *  Permission is granted to anyone to use this software for any purpose,
   *  including commercial applications, and to alter it and redistribute it
   *  freely, subject to the following restrictions:
   *
   *  1.  The origin of this software must not be misrepresented; you must not
   *      claim that you wrote the original software. If you use this software in
   *      a product, an acknowledgment in the product documentation would be
   *      appreciated but is not required.
   *  2.  Altered source versions must be plainly marked as such, and must not be
   *      misrepresented as being the original software.
   *  3.  This notice may not be removed or altered from any source distribution.
   */
  
  #include <assert.h>
  #include <stdio.h>
  #include <stdlib.h>     /* for macro max() */
  #include <string.h>
  #include <ctype.h>
  #if defined FORTIFY
    #include "fortify.h"
  #endif
  #include "sc.h"
  #include "amxdbg.h"
  #if defined LINUX || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__
    #include <sclinux.h>
  #endif
  
  
  static void append_dbginfo(FILE *fout);
  
  
  typedef cell (*OPCODE_PROC)(FILE *fbin,char *params,cell opcode);
  
  typedef struct {
    cell opcode;
    char *name;
    int segment;          /* sIN_CSEG=parse in cseg, sIN_DSEG=parse in dseg */
    OPCODE_PROC func;
  } OPCODE;
  
  static cell codeindex;  /* similar to "code_idx" */
  static cell *lbltab;    /* label table */
  static int writeerror;
  static int bytes_in, bytes_out;
  static jmp_buf compact_err;
  
  /* apparently, strtol() does not work correctly on very large (unsigned)
   * hexadecimal values */
  static ucell hex2long(char *s,char **n)
  {
    ucell result=0L;
    int negate=FALSE;
    int digit;
  
    /* ignore leading whitespace */
    while (*s==' ' || *s=='\t')
      s++;
  
    /* allow a negation sign to create the two's complement of numbers */
    if (*s=='-') {
      negate=TRUE;
      s++;
    } /* if */
  
    assert((*s>='0' && *s<='9') || (*s>='a' && *s<='f') || (*s>='a' && *s<='f'));
    for ( ;; ) {
      if (*s>='0' && *s<='9')
        digit=*s-'0';
      else if (*s>='a' && *s<='f')
        digit=*s-'a' + 10;
      else if (*s>='A' && *s<='F')
        digit=*s-'A' + 10;
      else
        break;    /* probably whitespace */
      result=(result<<4) | digit;
      s++;
    } /* for */
    if (n!=NULL)
      *n=s;
    if (negate)
      result=(~result)+1; /* take two's complement of the result */
    return (ucell)result;
  }
  
  static ucell getparam(char *s,char **n)
  {
    ucell result=0;
    for ( ;; ) {
      result+=hex2long(s,&s);
      if (*s!='+')
        break;
      s++;
    } /* for */
    if (n!=NULL)
      *n=s;
    return result;
  }
  
  #if BYTE_ORDER==BIG_ENDIAN
  static uint16_t *align16(uint16_t *v)
  {
    unsigned char *s = (unsigned char *)v;
    unsigned char t;
  
    /* swap two bytes */
    t=s[0];
    s[0]=s[1];
    s[1]=t;
    return v;
  }
  
  static uint32_t *align32(uint32_t *v)
  {
    unsigned char *s = (unsigned char *)v;
    unsigned char t;
  
    /* swap outer two bytes */
    t=s[0];
    s[0]=s[3];
    s[3]=t;
    /* swap inner two bytes */
    t=s[1];
    s[1]=s[2];
    s[2]=t;
    return v;
  }
  
  #if PAWN_CELL_SIZE>=64
  static uint64_t *align64(uint64_t *v)
  {
  	unsigned char *s = (unsigned char *)v;
  	unsigned char t;
  
  	t=s[0];
  	s[0]=s[7];
  	s[7]=t;
  
  	t=s[1];
  	s[1]=s[6];
  	s[6]=t;
  
  	t=s[2];
  	s[2]=s[5];
  	s[5]=t;
  
  	t=s[3];
  	s[3]=s[4];
  	s[4]=t;
  
  	return v;
  }
  #endif
  
    #if PAWN_CELL_SIZE==16
      #define aligncell(v)  align16(v)
    #elif PAWN_CELL_SIZE==32
      #define aligncell(v)  align32(v)
    #elif PAWN_CELL_SIZE==64
      #define aligncell(v)  align64(v)
    #endif
  #else
    #define align16(v)    (v)
    #define align32(v)    (v)
    #define aligncell(v)  (v)
  #endif
  
  static char *skipwhitespace(char *str)
  {
    while (isspace(*str))
      str++;
    return str;
  }
  
  static char *stripcomment(char *str)
  {
    char *ptr=strchr(str,';');
    if (ptr!=NULL) {
      *ptr++='\n';        /* terminate the line, but leave the '\n' */
      *ptr='\0';
    } /* if */
    return str;
  }
  
  static void write_encoded(FILE *fbin,ucell *c,int num)
  {
    #if PAWN_CELL_SIZE == 16
      #define ENC_MAX   3     /* a 16-bit cell is encoded in max. 3 bytes */
      #define ENC_MASK  0x03  /* after 2x7 bits, 2 bits remain to make 16 bits */
    #elif PAWN_CELL_SIZE == 32
      #define ENC_MAX   5     /* a 32-bit cell is encoded in max. 5 bytes */
      #define ENC_MASK  0x0f  /* after 4x7 bits, 4 bits remain to make 32 bits */
    #elif PAWN_CELL_SIZE == 64
      #define ENC_MAX   10    /* a 32-bit cell is encoded in max. 10 bytes */
      #define ENC_MASK  0x01  /* after 9x7 bits, 1 bit remains to make 64 bits */
    #endif
  
    assert(fbin!=NULL);
    while (num-->0) {
      if (sc_compress) {
        ucell p=(ucell)*c;
        unsigned char t[ENC_MAX];
        unsigned char code;
        int index;
        for (index=0; index<ENC_MAX; index++) {
          t[index]=(unsigned char)(p & 0x7f);     /* store 7 bits */
          p>>=7;
        } /* for */
        /* skip leading zeros */
        while (index>1 && t[index-1]==0 && (t[index-2] & 0x40)==0)
          index--;
        /* skip leading -1s */
        if (index==ENC_MAX && t[index-1]==ENC_MASK && (t[index-2] & 0x40)!=0)
          index--;
        while (index>1 && t[index-1]==0x7f && (t[index-2] & 0x40)!=0)
          index--;
        /* write high byte first, write continuation bits */
        assert(index>0);
        while (index-->0) {
          code=(unsigned char)((index==0) ? t[index] : (t[index]|0x80));
          writeerror |= !pc_writebin(fbin,&code,1);
          bytes_out++;
        } /* while */
        bytes_in+=sizeof *c;
        assert(AMX_COMPACTMARGIN>2);
        if (bytes_out-bytes_in>=AMX_COMPACTMARGIN-2)
          longjmp(compact_err,1);
      } else {
        assert((pc_lengthbin(fbin) % sizeof(cell)) == 0);
        writeerror |= !pc_writebin(fbin,aligncell(c),sizeof *c);
      } /* if */
      c++;
    } /* while */
  }
  
  #if defined __BORLANDC__ || defined __WATCOMC__
    #pragma argsused
  #endif
  static cell noop(FILE *fbin,char *params,cell opcode)
  {
    return 0;
  }
  
  #if defined __BORLANDC__ || defined __WATCOMC__
    #pragma argsused
  #endif
  static cell parm0(FILE *fbin,char *params,cell opcode)
  {
    if (fbin!=NULL)
      write_encoded(fbin,(ucell*)&opcode,1);
    return opcodes(1);
  }
  
  static cell parm1(FILE *fbin,char *params,cell opcode)
  {
    ucell p=getparam(params,NULL);
    if (fbin!=NULL) {
      write_encoded(fbin,(ucell*)&opcode,1);
      write_encoded(fbin,&p,1);
    } /* if */
    return opcodes(1)+opargs(1);
  }
  
  #if defined __BORLANDC__ || defined __WATCOMC__
    #pragma argsused
  #endif
  static cell do_dump(FILE *fbin,char *params,cell opcode)
  {
    ucell p;
    int num = 0;
  
    while (*params!='\0') {
      p=getparam(params,&params);
      if (fbin!=NULL)
        write_encoded(fbin,&p,1);
      num++;
      while (isspace(*params))
        params++;
    } /* while */
    return num*sizeof(cell);
  }
  
  static cell do_call(FILE *fbin,char *params,cell opcode)
  {
    char name[sNAMEMAX+1];
    int i;
    symbol *sym;
    ucell p;
  
    for (i=0; !isspace(*params); i++,params++) {
      assert(*params!='\0');
      assert(i<sNAMEMAX);
      name[i]=*params;
    } /* for */
    name[i]='\0';
  
    if (name[0]=='l' && name[1]=='.') {
      /* this is a label, not a function symbol */
      i=(int)hex2long(name+2,NULL);
      assert(i>=0 && i<sc_labnum);
      if (fbin!=NULL) {
        assert(lbltab!=NULL);
        p=lbltab[i];
      } /* if */
    } else {
      /* look up the function address; note that the correct file number must
       * already have been set (in order for static globals to be found).
       */
      sym=findglb(name);
      assert(sym!=NULL);
      assert(sym->ident==iFUNCTN || sym->ident==iREFFUNC);
      assert(sym->vclass==sGLOBAL);
      p=sym->addr;
    } /* if */
  
    if (fbin!=NULL) {
      write_encoded(fbin,(ucell*)&opcode,1);
      write_encoded(fbin,&p,1);
    } /* if */
    return opcodes(1)+opargs(1);
  }
  
  static cell do_jump(FILE *fbin,char *params,cell opcode)
  {
    int i;
    ucell p;
  
    i=(int)hex2long(params,NULL);
    assert(i>=0 && i<sc_labnum);
  
    if (fbin!=NULL) {
      assert(lbltab!=NULL);
      p=lbltab[i];
      write_encoded(fbin,(ucell*)&opcode,1);
      write_encoded(fbin,&p,1);
    } /* if */
    return opcodes(1)+opargs(1);
  }
  
  static cell do_switch(FILE *fbin,char *params,cell opcode)
  {
    int i;
    ucell p;
  
    i=(int)hex2long(params,NULL);
    assert(i>=0 && i<sc_labnum);
  
    if (fbin!=NULL) {
      assert(lbltab!=NULL);
      p=lbltab[i];
      write_encoded(fbin,(ucell*)&opcode,1);
      write_encoded(fbin,&p,1);
    } /* if */
    return opcodes(1)+opargs(1);
  }
  
  #if defined __BORLANDC__ || defined __WATCOMC__
    #pragma argsused
  #endif
  static cell do_case(FILE *fbin,char *params,cell opcode)
  {
    int i;
    ucell p,v;
  
    v=hex2long(params,&params);
    i=(int)hex2long(params,NULL);
    assert(i>=0 && i<sc_labnum);
  
    if (fbin!=NULL) {
      assert(lbltab!=NULL);
      p=lbltab[i];
      write_encoded(fbin,&v,1);
      write_encoded(fbin,&p,1);
    } /* if */
    return opcodes(0)+opargs(2);
  }
  
  static OPCODE opcodelist[] = {
    /* node for "invalid instruction" */
    {  0, NULL,         0,        noop },
    /* opcodes in sorted order */
    { 78, "add",        sIN_CSEG, parm0 },
    { 87, "add.c",      sIN_CSEG, parm1 },
    { 14, "addr.alt",   sIN_CSEG, parm1 },
    { 13, "addr.pri",   sIN_CSEG, parm1 },
    { 30, "align.alt",  sIN_CSEG, parm1 },
    { 29, "align.pri",  sIN_CSEG, parm1 },
    { 81, "and",        sIN_CSEG, parm0 },
    {121, "bounds",     sIN_CSEG, parm1 },
    {137, "break",      sIN_CSEG, parm0 },  /* version 8 */
    { 49, "call",       sIN_CSEG, do_call },
    { 50, "call.pri",   sIN_CSEG, parm0 },
    {  0, "case",       sIN_CSEG, do_case },
    {130, "casetbl",    sIN_CSEG, parm0 },  /* version 1 */
    {118, "cmps",       sIN_CSEG, parm1 },
    {  0, "code",       0,        noop },
    { 12, "const.alt",  sIN_CSEG, parm1 },
    { 11, "const.pri",  sIN_CSEG, parm1 },
    {  0, "data",       0,        noop },
    {114, "dec",        sIN_CSEG, parm1 },
    {113, "dec.alt",    sIN_CSEG, parm0 },
    {116, "dec.i",      sIN_CSEG, parm0 },
    {112, "dec.pri",    sIN_CSEG, parm0 },
    {115, "dec.s",      sIN_CSEG, parm1 },
    {  0, "dump",       sIN_DSEG, do_dump },
    { 95, "eq",         sIN_CSEG, parm0 },
    {106, "eq.c.alt",   sIN_CSEG, parm1 },
    {105, "eq.c.pri",   sIN_CSEG, parm1 },
  /*{124, "file",       sIN_CSEG, do_file }, */
    {119, "fill",       sIN_CSEG, parm1 },
    {100, "geq",        sIN_CSEG, parm0 },
    { 99, "grtr",       sIN_CSEG, parm0 },
    {120, "halt",       sIN_CSEG, parm1 },
    { 45, "heap",       sIN_CSEG, parm1 },
    { 27, "idxaddr",    sIN_CSEG, parm0 },
    { 28, "idxaddr.b",  sIN_CSEG, parm1 },
    {109, "inc",        sIN_CSEG, parm1 },
    {108, "inc.alt",    sIN_CSEG, parm0 },
    {111, "inc.i",      sIN_CSEG, parm0 },
    {107, "inc.pri",    sIN_CSEG, parm0 },
    {110, "inc.s",      sIN_CSEG, parm1 },
    { 86, "invert",     sIN_CSEG, parm0 },
    { 55, "jeq",        sIN_CSEG, do_jump },
    { 60, "jgeq",       sIN_CSEG, do_jump },
    { 59, "jgrtr",      sIN_CSEG, do_jump },
    { 58, "jleq",       sIN_CSEG, do_jump },
    { 57, "jless",      sIN_CSEG, do_jump },
    { 56, "jneq",       sIN_CSEG, do_jump },
    { 54, "jnz",        sIN_CSEG, do_jump },
    { 52, "jrel",       sIN_CSEG, parm1 },  /* always a number */
    { 64, "jsgeq",      sIN_CSEG, do_jump },
    { 63, "jsgrtr",     sIN_CSEG, do_jump },
    { 62, "jsleq",      sIN_CSEG, do_jump },
    { 61, "jsless",     sIN_CSEG, do_jump },
    { 51, "jump",       sIN_CSEG, do_jump },
    {128, "jump.pri",   sIN_CSEG, parm0 },  /* version 1 */
    { 53, "jzer",       sIN_CSEG, do_jump },
    { 31, "lctrl",      sIN_CSEG, parm1 },
    { 98, "leq",        sIN_CSEG, parm0 },
    { 97, "less",       sIN_CSEG, parm0 },
    { 25, "lidx",       sIN_CSEG, parm0 },
    { 26, "lidx.b",     sIN_CSEG, parm1 },
  /*{125, "line",       sIN_CSEG, parm2 }, */
    {  2, "load.alt",   sIN_CSEG, parm1 },
    {  9, "load.i",     sIN_CSEG, parm0 },
    {  1, "load.pri",   sIN_CSEG, parm1 },
    {  4, "load.s.alt", sIN_CSEG, parm1 },
    {  3, "load.s.pri", sIN_CSEG, parm1 },
    { 10, "lodb.i",     sIN_CSEG, parm1 },
    {  6, "lref.alt",   sIN_CSEG, parm1 },
    {  5, "lref.pri",   sIN_CSEG, parm1 },
    {  8, "lref.s.alt", sIN_CSEG, parm1 },
    {  7, "lref.s.pri", sIN_CSEG, parm1 },
    { 34, "move.alt",   sIN_CSEG, parm0 },
    { 33, "move.pri",   sIN_CSEG, parm0 },
    {117, "movs",       sIN_CSEG, parm1 },
    { 85, "neg",        sIN_CSEG, parm0 },
    { 96, "neq",        sIN_CSEG, parm0 },
    {134, "nop",        sIN_CSEG, parm0 },  /* version 6 */
    { 84, "not",        sIN_CSEG, parm0 },
    { 82, "or",         sIN_CSEG, parm0 },
    { 43, "pop.alt",    sIN_CSEG, parm0 },
    { 42, "pop.pri",    sIN_CSEG, parm0 },
    { 46, "proc",       sIN_CSEG, parm0 },
    { 40, "push",       sIN_CSEG, parm1 },
    { 37, "push.alt",   sIN_CSEG, parm0 },
    { 39, "push.c",     sIN_CSEG, parm1 },
    { 36, "push.pri",   sIN_CSEG, parm0 },
    { 38, "push.r",     sIN_CSEG, parm1 },
    { 41, "push.s",     sIN_CSEG, parm1 },
    {133, "pushaddr",   sIN_CSEG, parm1 },  /* version 4 */
    { 47, "ret",        sIN_CSEG, parm0 },
    { 48, "retn",       sIN_CSEG, parm0 },
    { 32, "sctrl",      sIN_CSEG, parm1 },
    { 73, "sdiv",       sIN_CSEG, parm0 },
    { 74, "sdiv.alt",   sIN_CSEG, parm0 },
    {104, "sgeq",       sIN_CSEG, parm0 },
    {103, "sgrtr",      sIN_CSEG, parm0 },
    { 65, "shl",        sIN_CSEG, parm0 },
    { 69, "shl.c.alt",  sIN_CSEG, parm1 },
    { 68, "shl.c.pri",  sIN_CSEG, parm1 },
    { 66, "shr",        sIN_CSEG, parm0 },
    { 71, "shr.c.alt",  sIN_CSEG, parm1 },
    { 70, "shr.c.pri",  sIN_CSEG, parm1 },
    { 94, "sign.alt",   sIN_CSEG, parm0 },
    { 93, "sign.pri",   sIN_CSEG, parm0 },
    {102, "sleq",       sIN_CSEG, parm0 },
    {101, "sless",      sIN_CSEG, parm0 },
    { 72, "smul",       sIN_CSEG, parm0 },
    { 88, "smul.c",     sIN_CSEG, parm1 },
  /*{127, "srange",     sIN_CSEG, parm2 }, -- version 1 */
    { 20, "sref.alt",   sIN_CSEG, parm1 },
    { 19, "sref.pri",   sIN_CSEG, parm1 },
    { 22, "sref.s.alt", sIN_CSEG, parm1 },
    { 21, "sref.s.pri", sIN_CSEG, parm1 },
    { 67, "sshr",       sIN_CSEG, parm0 },
    { 44, "stack",      sIN_CSEG, parm1 },
    {  0, "stksize",    0,        noop },
    { 16, "stor.alt",   sIN_CSEG, parm1 },
    { 23, "stor.i",     sIN_CSEG, parm0 },
    { 15, "stor.pri",   sIN_CSEG, parm1 },
    { 18, "stor.s.alt", sIN_CSEG, parm1 },
    { 17, "stor.s.pri", sIN_CSEG, parm1 },
    { 24, "strb.i",     sIN_CSEG, parm1 },
    { 79, "sub",        sIN_CSEG, parm0 },
    { 80, "sub.alt",    sIN_CSEG, parm0 },
    {132, "swap.alt",   sIN_CSEG, parm0 },  /* version 4 */
    {131, "swap.pri",   sIN_CSEG, parm0 },  /* version 4 */
    {129, "switch",     sIN_CSEG, do_switch }, /* version 1 */
  /*{126, "symbol",     sIN_CSEG, do_symbol }, */
  /*{136, "symtag",     sIN_CSEG, parm1 },  -- version 7 */
    {123, "sysreq.c",   sIN_CSEG, parm1 },
    {135, "sysreq.d",   sIN_CSEG, parm1 },  /* version 7, not generated directly */
    {122, "sysreq.pri", sIN_CSEG, parm0 },
    { 76, "udiv",       sIN_CSEG, parm0 },
    { 77, "udiv.alt",   sIN_CSEG, parm0 },
    { 75, "umul",       sIN_CSEG, parm0 },
    { 35, "xchg",       sIN_CSEG, parm0 },
    { 83, "xor",        sIN_CSEG, parm0 },
    { 91, "zero",       sIN_CSEG, parm1 },
    { 90, "zero.alt",   sIN_CSEG, parm0 },
    { 89, "zero.pri",   sIN_CSEG, parm0 },
    { 92, "zero.s",     sIN_CSEG, parm1 },
  };
  
  #define MAX_INSTR_LEN   30
  static int findopcode(char *instr,int maxlen)
  {
    int low,high,mid,cmp;
    char str[MAX_INSTR_LEN];
  
    if (maxlen>=MAX_INSTR_LEN)
      return 0;
    strncpy(str,instr,maxlen);
    str[maxlen]='\0';     /* make sure the string is zero terminated */
    /* look up the instruction with a binary search
     * the assembler is case insensitive to instructions (but case sensitive
     * to symbols)
     */
    low=1;                /* entry 0 is reserved (for "not found") */
    high=(sizeof opcodelist / sizeof opcodelist[0])-1;
    while (low<high) {
      mid=(low+high)/2;
      assert(opcodelist[mid].name!=NULL);
      cmp=stricmp(str,opcodelist[mid].name);
      if (cmp>0)
        low=mid+1;
      else
        high=mid;
    } /* while */
  
    assert(low==high);
    if (stricmp(str,opcodelist[low].name)==0)
      return low;         /* found */
    return 0;             /* not found, return special index */
  }
  
  SC_FUNC int assemble(FILE *fout,FILE *fin)
  {
    AMX_HEADER hdr;
    AMX_FUNCSTUBNT func;
    int numpublics,numnatives,numlibraries,numpubvars,numtags,padding;
    long nametablesize,nameofs;
    #if PAWN_CELL_SIZE > 32
      char line[512];
    #else
      char line[256];
    #endif
    char *instr,*params;
    int i,pass,size;
    int16_t count;
    symbol *sym, **nativelist;
    constvalue *constptr;
    cell mainaddr;
  
    fcurrent = -1;
  
    /* if compression failed, restart the assembly with compaction switched off */
    if (setjmp(compact_err)!=0) {
      assert(sc_compress);  /* cannot arrive here if compact encoding was disabled */
      sc_compress=FALSE;
      pc_resetbin(fout,0);
      error(232);           /* disabled compact encoding */
    } /* if */
  
    #if !defined NDEBUG
      /* verify that the opcode list is sorted (skip entry 1; it is reserved
       * for a non-existant opcode)
       */
      assert(opcodelist[1].name!=NULL);
      for (i=2; i<(sizeof opcodelist / sizeof opcodelist[0]); i++) {
        assert(opcodelist[i].name!=NULL);
        assert(stricmp(opcodelist[i].name,opcodelist[i-1].name)>0);
      } /* for */
    #endif
  
    writeerror=FALSE;
    nametablesize=sizeof(int16_t);
    numpublics=0;
    numnatives=0;
    numpubvars=0;
    mainaddr=-1;
    /* count number of public and native functions and public variables */
    for (sym=glbtab.next; sym!=NULL; sym=sym->next) {
      int match=0;
      if (sym->ident==iFUNCTN) {
        if ((sym->usage & uNATIVE)!=0 && (sym->usage & uREAD)!=0 && sym->addr>=0)
          match=++numnatives;
        if ((sym->usage & uPUBLIC)!=0 && (sym->usage & uDEFINE)!=0)
          match=++numpublics;
        if (strcmp(sym->name,uMAINFUNC)==0) {
          assert(sym->vclass==sGLOBAL);
          mainaddr=sym->addr;
        } /* if */
      } else if (sym->ident==iVARIABLE || sym->ident == iARRAY || sym->ident == iREFARRAY) {
        if ((sym->usage & uPUBLIC)!=0 && (sym->usage & (uREAD | uWRITTEN))!=0)
          match=++numpubvars;
      } /* if */
      if (match) {
        char alias[sNAMEMAX+1];
        assert(sym!=NULL);
        if ((sym->usage & uNATIVE)==0 || !lookup_alias(alias,sym->name)) {
          assert(strlen(sym->name)<=sNAMEMAX);
          strcpy(alias,sym->name);
        } /* if */
        nametablesize+=strlen(alias)+1;
      } /* if */
    } /* for */
    assert(numnatives==ntv_funcid);
  
    /* count number of libraries */
    numlibraries=0;
    if (pc_addlibtable) {
      for (constptr=libname_tab.next; constptr!=NULL; constptr=constptr->next) {
        if (constptr->value>0) {
          assert(strlen(constptr->name)>0);
          numlibraries++;
          nametablesize+=strlen(constptr->name)+1;
        } /* if */
      } /* for */
    } /* if */
  
    /* count number of public tags */
    numtags=0;
    for (constptr=tagname_tab.next; constptr!=NULL; constptr=constptr->next) {
      if ((constptr->value & PUBLICTAG)!=0) {
        assert(strlen(constptr->name)>0);
        numtags++;
        nametablesize+=strlen(constptr->name)+1;
      } /* if */
    } /* for */
  
    /* pad the header to sc_dataalign
     * => thereby the code segment is aligned
     * => since the code segment is padded to a sc_dataalign boundary, the data segment is aligned
     * => and thereby the stack top is aligned too
     */
    assert(sc_dataalign!=0);
    padding= (int)(sc_dataalign - (sizeof hdr + nametablesize) % sc_dataalign);
    if (padding==sc_dataalign)
      padding=0;
  
    /* write the abstract machine header */
    memset(&hdr, 0, sizeof hdr);
    hdr.magic=(unsigned short)AMX_MAGIC;
    hdr.file_version=CUR_FILE_VERSION;
    hdr.amx_version=MIN_AMX_VERSION;
    hdr.flags=(short)(sc_debug & sSYMBOLIC);
    if (sc_compress)
      hdr.flags|=AMX_FLAG_COMPACT;
    if (sc_debug==0)
      hdr.flags|=AMX_FLAG_NOCHECKS;
    hdr.defsize=sizeof(AMX_FUNCSTUBNT);
    hdr.publics=sizeof hdr; /* public table starts right after the header */
    hdr.natives=hdr.publics + numpublics*sizeof(AMX_FUNCSTUBNT);
    hdr.libraries=hdr.natives + numnatives*sizeof(AMX_FUNCSTUBNT);
    hdr.pubvars=hdr.libraries + numlibraries*sizeof(AMX_FUNCSTUBNT);
    hdr.tags=hdr.pubvars + numpubvars*sizeof(AMX_FUNCSTUBNT);
    hdr.nametable=hdr.tags + numtags*sizeof(AMX_FUNCSTUBNT);
    hdr.cod=hdr.nametable + nametablesize + padding;
    hdr.dat=hdr.cod + code_idx;
    hdr.hea=hdr.dat + glb_declared*sizeof(cell);
    hdr.stp=hdr.hea + sc_stksize*sizeof(cell);
    hdr.cip=mainaddr;
    hdr.size=hdr.hea; /* preset, this is incorrect in case of compressed output */
    pc_writebin(fout,&hdr,sizeof hdr);
  
    /* dump zeros up to the rest of the header, so that we can easily "seek" */
    for (nameofs=sizeof hdr; nameofs<hdr.cod; nameofs++)
      putc(0,fout);
    nameofs=hdr.nametable+sizeof(int16_t);
  
    /* write the public functions table */
    count=0;
    for (sym=glbtab.next; sym!=NULL; sym=sym->next) {
      if (sym->ident==iFUNCTN
          && (sym->usage & uPUBLIC)!=0 && (sym->usage & uDEFINE)!=0)
      {
        assert(sym->vclass==sGLOBAL);
        func.address=sym->addr;
        func.nameofs=nameofs;
        #if BYTE_ORDER==BIG_ENDIAN
          align32(&func.address);
          align32(&func.nameofs);
        #endif
        pc_resetbin(fout,hdr.publics+count*sizeof(AMX_FUNCSTUBNT));
        pc_writebin(fout,&func,sizeof func);
        pc_resetbin(fout,nameofs);
        pc_writebin(fout,sym->name,strlen(sym->name)+1);
        nameofs+=strlen(sym->name)+1;
        count++;
      } /* if */
    } /* for */
  
    /* write the natives table */
    /* The native functions must be written in sorted order. (They are
     * sorted on their "id", not on their name). A nested loop to find
     * each successive function would be an O(n^2) operation. But we
     * do not really need to sort, because the native function id's
     * are sequential and there are no duplicates. So we first walk
     * through the complete symbol list and store a pointer to every
     * native function of interest in a temporary table, where its id
     * serves as the index in the table. Now we can walk the table and
     * have all native functions in sorted order.
     */
    if (numnatives>0) {
      nativelist=(symbol **)malloc(numnatives*sizeof(symbol *));
      if (nativelist==NULL)
        error(103);               /* insufficient memory */
      #if !defined NDEBUG
        memset(nativelist,0,numnatives*sizeof(symbol *)); /* for NULL checking */
      #endif
      for (sym=glbtab.next; sym!=NULL; sym=sym->next) {
        if (sym->ident==iFUNCTN && (sym->usage & uNATIVE)!=0 && (sym->usage & uREAD)!=0 && sym->addr>=0) {
          assert(sym->addr < numnatives);
          nativelist[(int)sym->addr]=sym;
        } /* if */
      } /* for */
      count=0;
      for (i=0; i<numnatives; i++) {
        char alias[sNAMEMAX+1];
        sym=nativelist[i];
        assert(sym!=NULL);
        if (!lookup_alias(alias,sym->name)) {
          assert(strlen(sym->name)<=sNAMEMAX);
          strcpy(alias,sym->name);
        } /* if */
        assert(sym->vclass==sGLOBAL);
        func.address=0;
        func.nameofs=nameofs;
        #if BYTE_ORDER==BIG_ENDIAN
          align32(&func.address);
          align32(&func.nameofs);
        #endif
        pc_resetbin(fout,hdr.natives+count*sizeof(AMX_FUNCSTUBNT));
        pc_writebin(fout,&func,sizeof func);
        pc_resetbin(fout,nameofs);
        pc_writebin(fout,alias,strlen(alias)+1);
        nameofs+=strlen(alias)+1;
        count++;
      } /* for */
      free(nativelist);
    } /* if */
  
    /* write the libraries table */
    if (pc_addlibtable) {
      count=0;
      for (constptr=libname_tab.next; constptr!=NULL; constptr=constptr->next) {
        if (constptr->value>0) {
          assert(strlen(constptr->name)>0);
          func.address=0;
          func.nameofs=nameofs;
          #if BYTE_ORDER==BIG_ENDIAN
            align32(&func.address);
            align32(&func.nameofs);
          #endif
          pc_resetbin(fout,hdr.libraries+count*sizeof(AMX_FUNCSTUBNT));
          pc_writebin(fout,&func,sizeof func);
          pc_resetbin(fout,nameofs);
          pc_writebin(fout,constptr->name,strlen(constptr->name)+1);
          nameofs+=strlen(constptr->name)+1;
          count++;
        } /* if */
      } /* for */
    } /* if */
  
    /* write the public variables table */
    count=0;
    for (sym=glbtab.next; sym!=NULL; sym=sym->next) {
      if ((sym->ident==iVARIABLE || sym->ident==iARRAY || sym->ident==iREFARRAY) 
          && (sym->usage & uPUBLIC)!=0 && (sym->usage & (uREAD | uWRITTEN))!=0) {
        assert((sym->usage & uDEFINE)!=0);
        assert(sym->vclass==sGLOBAL);
        func.address=sym->addr;
        func.nameofs=nameofs;
        #if BYTE_ORDER==BIG_ENDIAN
          align32(&func.address);
          align32(&func.nameofs);
        #endif
        pc_resetbin(fout,hdr.pubvars+count*sizeof(AMX_FUNCSTUBNT));
        pc_writebin(fout,&func,sizeof func);
        pc_resetbin(fout,nameofs);
        pc_writebin(fout,sym->name,strlen(sym->name)+1);
        nameofs+=strlen(sym->name)+1;
        count++;
      } /* if */
    } /* for */
  
    /* write the public tagnames table */
    count=0;
    for (constptr=tagname_tab.next; constptr!=NULL; constptr=constptr->next) {
      if ((constptr->value & PUBLICTAG)!=0) {
        assert(strlen(constptr->name)>0);
        func.address=constptr->value & TAGMASK;
        func.nameofs=nameofs;
        #if BYTE_ORDER==BIG_ENDIAN
          align32(&func.address);
          align32(&func.nameofs);
        #endif
        pc_resetbin(fout,hdr.tags+count*sizeof(AMX_FUNCSTUBNT));
        pc_writebin(fout,&func,sizeof func);
        pc_resetbin(fout,nameofs);
        pc_writebin(fout,constptr->name,strlen(constptr->name)+1);
        nameofs+=strlen(constptr->name)+1;
        count++;
      } /* if */
    } /* for */
  
    /* write the "maximum name length" field in the name table */
    assert(nameofs==hdr.nametable+nametablesize);
    pc_resetbin(fout,hdr.nametable);
    count=sNAMEMAX;
    #if BYTE_ORDER==BIG_ENDIAN
      align16(&count);
    #endif
    pc_writebin(fout,&count,sizeof count);
    pc_resetbin(fout,hdr.cod);
  
    /* First pass: relocate all labels */
    /* This pass is necessary because the code addresses of labels is only known
     * after the peephole optimization flag. Labels can occur inside expressions
     * (e.g. the conditional operator), which are optimized.
     */
    lbltab=NULL;
    if (sc_labnum>0) {
      /* only very short programs have zero labels; no first pass is needed
       * if there are no labels */
      lbltab=(cell *)malloc(sc_labnum*sizeof(cell));
      if (lbltab==NULL)
        error(103);               /* insufficient memory */
      codeindex=0;
      pc_resetasm(fin);
      while (pc_readasm(fin,line,sizeof line)!=NULL) {
        stripcomment(line);
        instr=skipwhitespace(line);
        /* ignore empty lines */
        if (*instr=='\0')
          continue;
        if (tolower(*instr)=='l' && *(instr+1)=='.') {
          int lindex=(int)hex2long(instr+2,NULL);
          assert(lindex>=0 && lindex<sc_labnum);
          lbltab[lindex]=codeindex;
        } else {
          /* get to the end of the instruction (make use of the '\n' that fgets()
           * added at the end of the line; this way we will *always* drop on a
           * whitespace character) */
          for (params=instr; *params!='\0' && !isspace(*params); params++)
            /* nothing */;
          assert(params>instr);
          i=findopcode(instr,(int)(params-instr));
          if (opcodelist[i].name==NULL) {
            *params='\0';
            error(104,instr);     /* invalid assembler instruction */
          } /* if */
          if (opcodelist[i].segment==sIN_CSEG)
            codeindex+=opcodelist[i].func(NULL,skipwhitespace(params),opcodelist[i].opcode);
        } /* if */
      } /* while */
    } /* if */
  
    /* Second pass (actually 2 more passes, one for all code and one for all data) */
    bytes_in=0;
    bytes_out=0;
    for (pass=sIN_CSEG; pass<=sIN_DSEG; pass++) {
      pc_resetasm(fin);
      while (pc_readasm(fin,line,sizeof line)!=NULL) {
        stripcomment(line);
        instr=skipwhitespace(line);
        /* ignore empty lines and labels (labels have a special syntax, so these
         * must be parsed separately) */
        if (*instr=='\0' || (tolower(*instr)=='l' && *(instr+1)=='.'))
          continue;
        /* get to the end of the instruction (make use of the '\n' that fgets()
         * added at the end of the line; this way we will *always* drop on a
         * whitespace character) */
        for (params=instr; *params!='\0' && !isspace(*params); params++)
          /* nothing */;
        assert(params>instr);
        i=findopcode(instr,(int)(params-instr));
        assert(opcodelist[i].name!=NULL);
        if (opcodelist[i].segment==pass)
          opcodelist[i].func(fout,skipwhitespace(params),opcodelist[i].opcode);
      } /* while */
    } /* for */
    if (bytes_out-bytes_in>0)
      error(106);         /* compression buffer overflow */
  
    if (lbltab!=NULL) {
      free(lbltab);
      #if !defined NDEBUG
        lbltab=NULL;
      #endif
    } /* if */
  
    if (sc_compress)
      hdr.size=pc_lengthbin(fout);/* get this value before appending debug info */
    if (!writeerror && (sc_debug & sSYMBOLIC)!=0)
      append_dbginfo(fout);       /* optionally append debug file */
  
    if (writeerror)
      error(101,"disk full");
  
    /* adjust the header */
    size=(int)hdr.cod;    /* save, the value in the header may be swapped */
    #if BYTE_ORDER==BIG_ENDIAN
      align32(&hdr.size);
      align16(&hdr.magic);
      align16(&hdr.flags);
      align16(&hdr.defsize);
      align32(&hdr.publics);
      align32(&hdr.natives);
      align32(&hdr.libraries);
      align32(&hdr.pubvars);
      align32(&hdr.tags);
      align32(&hdr.nametable);
      align32(&hdr.cod);
      align32(&hdr.dat);
      align32(&hdr.hea);
      align32(&hdr.stp);
      align32(&hdr.cip);
    #endif
    pc_resetbin(fout,0);
    pc_writebin(fout,&hdr,sizeof hdr);
  
    /* return the size of the header (including name tables, but excluding code
     * or data sections)
     */
    return size;
  }
  
  static void append_dbginfo(FILE *fout)
  {
    AMX_DBG_HDR dbghdr;
    AMX_DBG_LINE dbgline;
    AMX_DBG_SYMBOL dbgsym;
    AMX_DBG_SYMDIM dbgidxtag[sDIMEN_MAX];
    int index,dim;
    char *str,*prevstr,*name,*prevname;
    ucell codeidx,previdx;
    constvalue *constptr;
    char symname[2*sNAMEMAX+16];
    int16_t id1,id2;
    ucell address;
  
    /* header with general information */
    memset(&dbghdr, 0, sizeof dbghdr);
    dbghdr.size=sizeof dbghdr;
    dbghdr.magic=AMX_DBG_MAGIC;
    dbghdr.file_version=CUR_FILE_VERSION;
    dbghdr.amx_version=MIN_AMX_VERSION;
  
    /* first pass: collect the number of items in various tables */
  
    /* file table */
    previdx=0;
    prevstr=NULL;
    prevname=NULL;
    for (index=0; (str=get_dbgstring(index))!=NULL; index++) {
      assert(str!=NULL);
      assert(str[0]!='\0' && str[1]==':');
      if (str[0]=='F') {
        codeidx=hex2long(str+2,&name);
        if (codeidx!=previdx) {
          if (prevstr!=NULL) {
            assert(prevname!=NULL);
            dbghdr.files++;
            dbghdr.size+=sizeof(cell)+strlen(prevname)+1;
          } /* if */
          previdx=codeidx;
        } /* if */
        prevstr=str;
        prevname=skipwhitespace(name);
      } /* if */
    } /* for */
    if (prevstr!=NULL) {
      assert(prevname!=NULL);
      dbghdr.files++;
      dbghdr.size+=sizeof(cell)+strlen(prevname)+1;
    } /* if */
  
    /* line number table */
    for (index=0; (str=get_dbgstring(index))!=NULL; index++) {
      assert(str!=NULL);
      assert(str[0]!='\0' && str[1]==':');
      if (str[0]=='L') {
        dbghdr.lines++;
        dbghdr.size+=sizeof(AMX_DBG_LINE);
      } /* if */
    } /* for */
  
    /* symbol table */
    for (index=0; (str=get_dbgstring(index))!=NULL; index++) {
      assert(str!=NULL);
      assert(str[0]!='\0' && str[1]==':');
      if (str[0]=='S') {
        dbghdr.symbols++;
        name=strchr(str+2,':');
        assert(name!=NULL);
        dbghdr.size+=sizeof(AMX_DBG_SYMBOL)+strlen(skipwhitespace(name+1));
        if ((prevstr=strchr(name,'['))!=NULL)
          while ((prevstr=strchr(prevstr+1,':'))!=NULL)
            dbghdr.size+=sizeof(AMX_DBG_SYMDIM);
      } /* if */
    } /* for */
  
    /* tag table */
    for (constptr=tagname_tab.next; constptr!=NULL; constptr=constptr->next) {
      assert(strlen(constptr->name)>0);
      dbghdr.tags++;
      dbghdr.size+=sizeof(AMX_DBG_TAG)+strlen(constptr->name);
    } /* for */
  
    /* automaton table */
    for (constptr=sc_automaton_tab.next; constptr!=NULL; constptr=constptr->next) {
      assert(constptr->index==0 && strlen(constptr->name)==0 || strlen(constptr->name)>0);
      dbghdr.automatons++;
      dbghdr.size+=sizeof(AMX_DBG_MACHINE)+strlen(constptr->name);
    } /* for */
  
    /* state table */
    for (constptr=sc_state_tab.next; constptr!=NULL; constptr=constptr->next) {
      assert(strlen(constptr->name)>0);
      dbghdr.states++;
      dbghdr.size+=sizeof(AMX_DBG_STATE)+strlen(constptr->name);
    } /* for */
  
  
    /* pass 2: generate the tables */
  
    writeerror |= !pc_writebin(fout,&dbghdr,sizeof dbghdr);
  
    /* file table */
    previdx=0;
    prevstr=NULL;
    prevname=NULL;
    for (index=0; (str=get_dbgstring(index))!=NULL; index++) {
      assert(str!=NULL);
      assert(str[0]!='\0' && str[1]==':');
      if (str[0]=='F') {
        codeidx=hex2long(str+2,&name);
        if (codeidx!=previdx) {
          if (prevstr!=NULL) {
            assert(prevname!=NULL);
            writeerror |= !pc_writebin(fout,&previdx,sizeof previdx);
            writeerror |= !pc_writebin(fout,prevname,strlen(prevname)+1);
          } /* if */
          previdx=codeidx;
        } /* if */
        prevstr=str;
        prevname=skipwhitespace(name);
      } /* if */
    } /* for */
    if (prevstr!=NULL) {
      assert(prevname!=NULL);
      writeerror |= !pc_writebin(fout,&previdx,sizeof previdx);
      writeerror |= !pc_writebin(fout,prevname,strlen(prevname)+1);
    } /* if */
  
    /* line number table */
    for (index=0; (str=get_dbgstring(index))!=NULL; index++) {
      assert(str!=NULL);
      assert(str[0]!='\0' && str[1]==':');
      if (str[0]=='L') {
        dbgline.address=hex2long(str+2,&str);
        dbgline.line=(int32_t)hex2long(str,NULL);
        writeerror |= !pc_writebin(fout,&dbgline,sizeof dbgline);
      } /* if */
    } /* for */
  
    /* symbol table */
    for (index=0; (str=get_dbgstring(index))!=NULL; index++) {
      assert(str!=NULL);
      assert(str[0]!='\0' && str[1]==':');
      if (str[0]=='S') {
        dbgsym.address=hex2long(str+2,&str);
        dbgsym.tag=(int16_t)hex2long(str,&str);
        str=skipwhitespace(str);
        assert(*str==':');
        name=skipwhitespace(str+1);
        str=strchr(name,' ');
        assert(str!=NULL);
        assert((int)(str-name)<sizeof symname);
        strncpy(symname,name,(int)(str-name));
        symname[(int)(str-name)]='\0';
        dbgsym.codestart=hex2long(str,&str);
        dbgsym.codeend=hex2long(str,&str);
        dbgsym.ident=(char)hex2long(str,&str);
        dbgsym.vclass=(char)hex2long(str,&str);
        dbgsym.dim=0;
        str=skipwhitespace(str);
        if (*str=='[') {
          while (*(str=skipwhitespace(str+1))!=']') {
            dbgidxtag[dbgsym.dim].tag=(int16_t)hex2long(str,&str);
            str=skipwhitespace(str);
            assert(*str==':');
            dbgidxtag[dbgsym.dim].size=hex2long(str+1,&str);
            dbgsym.dim++;
          } /* while */
        } /* if */
        writeerror |= !pc_writebin(fout,&dbgsym,sizeof dbgsym-1);
        writeerror |= !pc_writebin(fout,symname,strlen(symname)+1);
        for (dim=0; dim<dbgsym.dim; dim++)
          writeerror |= !pc_writebin(fout,&dbgidxtag[dim],sizeof dbgidxtag[dim]);
      } /* if */
    } /* for */
  
    /* tag table */
    for (constptr=tagname_tab.next; constptr!=NULL; constptr=constptr->next) {
      assert(strlen(constptr->name)>0);
      id1=(int16_t)(constptr->value & TAGMASK);
      writeerror |= !pc_writebin(fout,&id1,sizeof id1);
      writeerror |= !pc_writebin(fout,constptr->name,strlen(constptr->name)+1);
    } /* for */
  
    /* automaton table */
    for (constptr=sc_automaton_tab.next; constptr!=NULL; constptr=constptr->next) {
      assert(constptr->index==0 && strlen(constptr->name)==0 || strlen(constptr->name)>0);
      id1=(int16_t)constptr->index;
      address=(ucell)constptr->value;
      writeerror |= !pc_writebin(fout,&id1,sizeof id1);
      writeerror |= !pc_writebin(fout,&address,sizeof address);
      writeerror |= !pc_writebin(fout,constptr->name,strlen(constptr->name)+1);
    } /* for */
  
    /* state table */
    for (constptr=sc_state_tab.next; constptr!=NULL; constptr=constptr->next) {
      assert(strlen(constptr->name)>0);
      id1=(int16_t)constptr->value;
      id2=(int16_t)constptr->index;
      address=(ucell)constptr->value;
      writeerror |= !pc_writebin(fout,&id1,sizeof id1);
      writeerror |= !pc_writebin(fout,&id2,sizeof id2);
      writeerror |= !pc_writebin(fout,constptr->name,strlen(constptr->name)+1);
    } /* for */
  
    delete_dbgstringtable();
  }
  