/*
 * Assembler for MIPS32.
 * The syntax is compatible with GCC output.
 *
 * Copyright (C) 2011-2012 Serge Vakulenko, <serge@vak.ru>
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and this
 * permission notice and warranty disclaimer appear in supporting
 * documentation, and that the name of the author not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * The author disclaim all warranties with regard to this
 * software, including all implied warranties of merchantability
 * and fitness.  In no event shall the author be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether
 * in an action of contract, negligence or other tortious action,
 * arising out of or in connection with the use or performance of
 * this software.
 */
#ifdef CROSS
#   include <stdio.h>
#   include <nlist.h>
#else
#   include <stdio.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <a.out.h>

#define WORDSZ          4               /* word size in bytes */

/*
 * Locals beginning with L or dot are stripped off by -X flag.
 */
#define IS_LOCAL(s)     ((s)->n_name[0] == 'L' || (s)->n_name[0] == '.')

/*
 * Types of lexemes.
 */
enum {
    LEOF = 1,           /* end of file */
    LEOL,               /* end of line */
    LNAME,              /* identifier */
    LREG,               /* machine register */
    LNUM,               /* integer number */
    LLSHIFT,            /* << */
    LRSHIFT,            /* >> */
    LASCII,             /* .ascii */
    LBSS,               /* .bss */
    LCOMM,              /* .comm */
    LDATA,              /* .data */
    LGLOBL,             /* .globl */
    LHALF,              /* .half */
    LSTRNG,             /* .strng */
    LRDATA,             /* .rdata */
    LTEXT,              /* .text */
    LEQU,               /* .equ */
    LWORD,              /* .word */
    LBYTE,              /* .byte */
    LSPACE,             /* .space */
    LFILE,              /* .file */
    LSECTION,           /* .section */
    LSYMTYPE,           /* @type of symbol */
    LSECTYPE,           /* @type of section */
    LPREVIOUS,          /* .previous */
    LGNUATTR,           /* .gnu_attribute */
    LALIGN,             /* .align */
    LSET,               /* .set */
    LENT,               /* .ent */
    LTYPE,              /* .type */
    LFRAME,             /* .frame */
    LMASK,              /* .mask */
    LFMASK,             /* .fmask */
    LEND,               /* .end */
    LSIZE,              /* .size */
    LIDENT,             /* .ident */
    LWEAK,              /* .weak */
    LLOCAL,             /* .local */
};

/*
 * Segment ids.
 */
enum {
    STEXT,
    SDATA,
    SSTRNG,
    SBSS,
    SEXT,
    SABS,               /* special case for getexpr() */
};

/*
 * Sizes of tables.
 * Hash sizes should be powers of 2!
 */
#define HASHSZ  1024            /* symbol name hash table size */
#define HCMDSZ  256             /* instruction hash table size */
#define STSIZE  (HASHSZ*9/10)   /* symbol name table size */
#define MAXRLAB 200             /* max relative (digit) labels */

/*
 * On second pass, hashtab[] is not needed.
 * We use it under name newindex[] to reindex symbol references
 * when -x or -X options are enabled.
 */
#define newindex hashtab

/*
 * Convert segment id to symbol type.
 */
const int segmtype [] = {
    N_TEXT,             /* STEXT */
    N_DATA,             /* SDATA */
    N_STRNG,            /* SSTRNG */
    N_BSS,              /* SBSS */
    N_UNDF,             /* SEXT */
    N_ABS,              /* SABS */
};

/*
 * Convert segment id to relocation type.
 */
const int segmrel [] = {
    RTEXT,              /* STEXT */
    RDATA,              /* SDATA */
    RSTRNG,             /* SSTRNG */
    RBSS,               /* SBSS */
    REXT,               /* SEXT */
    RABS,               /* SABS */
};

/*
 * Convert symbol type to segment id.
 */
const int typesegm [] = {
    SEXT,               /* N_UNDF */
    SABS,               /* N_ABS */
    STEXT,              /* N_TEXT */
    SDATA,              /* N_DATA */
    SBSS,               /* N_BSS */
    SSTRNG,             /* N_STRNG */
};

/*
 * Table of local (numeric) labels.
 */
struct labeltab {
    int num;
    int value;
};

#define RLAB_OFFSET     (1 << 23)       /* index offset of relative label */
#define RLAB_MAXVAL     1000000         /* max value of relative label */

/*
 * Main opcode table.
 */
struct optable {
    unsigned opcode;                    /* instruction code */
    const char *name;                   /* instruction name */
    unsigned type;                      /* flags */
    void (*func) (unsigned, struct reloc*); /* handler for pseudo-instructions */
};

/*
 * Flags of instruction formats.
 */
#define FRD1    (1 << 0)        /* rd, ... */
#define FRD2    (1 << 1)        /* .., rd, ... */
#define FRT1    (1 << 2)        /* rt, ... */
#define FRT2    (1 << 3)        /* .., rt, ... */
#define FRT3    (1 << 4)        /* .., .., rt */
#define FRS1    (1 << 5)        /* rs, ... */
#define FRS2    (1 << 6)        /* .., rs, ... */
#define FRS3    (1 << 7)        /* .., .., rs */
#define FRSB    (1 << 8)        /* ... (rs) */
#define FCODE   (1 << 9)        /* immediate shifted <<6 */
#define FDSLOT  (1 << 10)       /* have delay slot */
#define FOFF16  (1 << 11)       /* 16-bit relocatable offset */
#define FHIGH16 (1 << 12)       /* high 16-bit relocatable offset */
#define FOFF18  (1 << 13)       /* 18-bit PC-relative relocatable offset shifted >>2 */
#define FAOFF18 (1 << 14)       /* 18-bit PC-relative relocatable offset shifted >>2 */
#define FAOFF28 (1 << 15)       /* 28-bit absolute relocatable offset shifted >>2 */
#define FSA     (1 << 16)       /* 5-bit shift amount */
#define FSEL    (1 << 17)       /* optional 3-bit COP0 register select */
#define FSIZE   (1 << 18)       /* bit field size */
#define FMSB    (1 << 19)       /* bit field msb */
#define FRTD    (1 << 20)       /* set rt to rd number */
#define FCODE16 (1 << 21)       /* immediate shifted <<16 */
#define FMOD    (1 << 22)       /* modifies the first register */

/*
 * Implement pseudo-instructions.
 * TODO: bge, bgeu, bgt, bgtu, ble, bleu, blt, bltu
 */
void emit_li (unsigned, struct reloc*);
void emit_la (unsigned, struct reloc*);

const struct optable optable [] = {
    { 0x00000020,   "add",      FRD1 | FRS2 | FRT3 | FMOD },
    { 0x20000000,   "addi",     FRT1 | FRS2 | FOFF16 | FMOD },
    { 0x24000000,   "addiu",    FRT1 | FRS2 | FOFF16 | FMOD },
    { 0x00000021,   "addu",     FRD1 | FRS2 | FRT3 | FMOD },
    { 0x00000024,   "and",      FRD1 | FRS2 | FRT3 | FMOD },
    { 0x30000000,   "andi",     FRT1 | FRS2 | FOFF16 | FMOD },
    { 0x10000000,   "b",        FAOFF18 | FDSLOT },
    { 0x04110000,   "bal",      FAOFF18 | FDSLOT },
    { 0x10000000,   "beq",      FRS1 | FRT2 | FOFF18 | FDSLOT },
    { 0x50000000,   "beql",	FRS1 | FRT2 | FOFF18 | FDSLOT },
    { 0x50000000,   "beqz",	FRS1 | FOFF18 | FDSLOT },
    { 0x04010000,   "bgez",	FRS1 | FOFF18 | FDSLOT },
    { 0x04110000,   "bgezal",	FRS1 | FOFF18 | FDSLOT },
    { 0x04130000,   "bgezall",	FRS1 | FOFF18 | FDSLOT },
    { 0x04030000,   "bgezl",	FRS1 | FOFF18 | FDSLOT },
    { 0x1c000000,   "bgtz",	FRS1 | FOFF18 | FDSLOT },
    { 0x5c000000,   "bgtzl",	FRS1 | FOFF18 | FDSLOT },
    { 0x18000000,   "blez",	FRS1 | FOFF18 | FDSLOT },
    { 0x58000000,   "blezl",	FRS1 | FOFF18 | FDSLOT },
    { 0x04000000,   "bltz",	FRS1 | FOFF18 | FDSLOT },
    { 0x04100000,   "bltzal",	FRS1 | FOFF18 | FDSLOT },
    { 0x04120000,   "bltzall",	FRS1 | FOFF18 | FDSLOT },
    { 0x04020000,   "bltzl",	FRS1 | FOFF18 | FDSLOT },
    { 0x14000000,   "bne",	FRS1 | FRT2 | FOFF18 | FDSLOT },
    { 0x54000000,   "bnel",	FRS1 | FRT2 | FOFF18 | FDSLOT },
    { 0x54000000,   "bnez",	FRS1 | FOFF18 | FDSLOT },
    { 0x0000000d,   "break",	FCODE16 },
    { 0x70000021,   "clo",	FRD1 | FRS2 | FRTD | FMOD },
    { 0x70000020,   "clz",	FRD1 | FRS2 | FRTD | FMOD },
    { 0x4200001f,   "deret",	0 },
    { 0x41606000,   "di",	FRT1 | FMOD },
    { 0x0000001a,   "div",	FRS1 | FRT2 },
    { 0x0000001b,   "divu",	FRS1 | FRT2 },
    { 0x000000c0,   "ehb",	0 },
    { 0x41606020,   "ei",	FRT1 | FMOD },
    { 0x42000018,   "eret",	0 },
    { 0x7c000000,   "ext",	FRT1 | FRS2 | FSA | FSIZE | FMOD },
    { 0x7c000004,   "ins",	FRT1 | FRS2 | FSA | FMSB | FMOD },
    { 0x08000000,   "j",	FAOFF28 | FDSLOT },
    { 0x0c000000,   "jal",	FAOFF28 | FDSLOT },
    { 0x00000009,   "jalr",	FRD1 | FRS2 | FDSLOT },
    { 0x00000409,   "jalr.hb",	FRD1 | FRS2 | FDSLOT },
    { 0x00000008,   "jr",	FRS1 | FDSLOT },
    { 0x00000408,   "jr.hb",	FRS1 | FDSLOT },
    {          0,   "la",	FRT1 | FMOD, emit_la },
    { 0x80000000,   "lb",	FRT1 | FOFF16 | FRSB | FMOD },
    { 0x90000000,   "lbu",	FRT1 | FOFF16 | FRSB | FMOD },
    { 0x84000000,   "lh",	FRT1 | FOFF16 | FRSB | FMOD },
    { 0x94000000,   "lhu",	FRT1 | FOFF16 | FRSB | FMOD },
    {          0,   "li",	FRT1 | FMOD, emit_li },
    { 0xc0000000,   "ll",	FRT1 | FOFF16 | FRSB | FMOD },
    { 0x3c000000,   "lui",	FRT1 | FHIGH16 | FMOD },
    { 0x8c000000,   "lw",	FRT1 | FOFF16 | FRSB | FMOD },
    { 0x88000000,   "lwl",	FRT1 | FOFF16 | FRSB | FMOD },
    { 0x98000000,   "lwr",	FRT1 | FOFF16 | FRSB | FMOD },
    { 0x70000000,   "madd",	FRS1 | FRT2 | FMOD },
    { 0x70000001,   "maddu",	FRS1 | FRT2 | FMOD },
    { 0x40000000,   "mfc0",	FRT1 | FRD2 | FSEL | FMOD },
    { 0x00000010,   "mfhi",	FRD1 | FMOD },
    { 0x00000012,   "mflo",	FRD1 | FMOD },
    { 0x00000021,   "move",	FRD1 | FRS2 | FMOD },          // addu
    { 0x0000000b,   "movn",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0x0000000a,   "movz",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0x70000004,   "msub",	FRS1 | FRT2 | FMOD },
    { 0x70000005,   "msubu",	FRS1 | FRT2 | FMOD },
    { 0x40800000,   "mtc0",	FRT1 | FRD2 | FSEL },
    { 0x00000011,   "mthi",	FRS1 },
    { 0x00000013,   "mtlo",	FRS1 },
    { 0x70000002,   "mul",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0x00000018,   "mult",	FRS1 | FRT2 },
    { 0x00000019,   "multu",	FRS1 | FRT2 },
    { 0x00000000,   "nop",	0 },
    { 0x00000027,   "nor",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0x00000025,   "or",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0x34000000,   "ori",	FRT1 | FRS2 | FOFF16 | FMOD },
    { 0x7c00003b,   "rdhwr",	FRT1 | FRD2 | FMOD },
    { 0x41400000,   "rdpgpr",	FRD1 | FRT2 | FMOD },
    { 0x00200002,   "ror",	FRD1 | FRT2 | FSA | FMOD },
    { 0x00000046,   "rorv",	FRD1 | FRT2 | FRS3 | FMOD },
    { 0xa0000000,   "sb",	FRT1 | FOFF16 | FRSB },
    { 0xe0000000,   "sc",	FRT1 | FOFF16 | FRSB },
    { 0x7000003f,   "sdbbp",	FCODE },
    { 0x7c000420,   "seb",	FRD1 | FRT2 },
    { 0x7c000620,   "seh",	FRD1 | FRT2 },
    { 0xa4000000,   "sh",	FRT1 | FOFF16 | FRSB },
    { 0x00000000,   "sll",	FRD1 | FRT2 | FSA | FMOD },
    { 0x00000004,   "sllv",	FRD1 | FRT2 | FRS3 | FMOD },
    { 0x0000002a,   "slt",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0x28000000,   "slti",	FRT1 | FRS2 | FOFF16 | FMOD },
    { 0x2c000000,   "sltiu",	FRT1 | FRS2 | FOFF16 | FMOD },
    { 0x0000002b,   "sltu",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0x00000003,   "sra",	FRD1 | FRT2 | FSA | FMOD },
    { 0x00000007,   "srav",	FRD1 | FRT2 | FRS3 | FMOD },
    { 0x00000002,   "srl",	FRD1 | FRT2 | FSA | FMOD },
    { 0x00000006,   "srlv",	FRD1 | FRT2 | FRS3 | FMOD },
    { 0x00000040,   "ssnop",	0 },
    { 0x00000022,   "sub",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0x00000023,   "subu",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0xac000000,   "sw",	FRT1 | FOFF16 | FRSB },
    { 0xa8000000,   "swl",	FRT1 | FOFF16 | FRSB },
    { 0xb8000000,   "swr",	FRT1 | FOFF16 | FRSB },
    { 0x0000000f,   "sync",	FCODE },
    { 0x0000000c,   "syscall",	FCODE },
    { 0x00000034,   "teq",	FRS1 | FRT2 | FCODE },
    { 0x040c0000,   "teqi",	FRS1 | FOFF16 },
    { 0x00000030,   "tge",	FRS1 | FRT2 | FCODE },
    { 0x04080000,   "tgei",	FRS1 | FOFF16 },
    { 0x04090000,   "tgeiu",	FRS1 | FOFF16 },
    { 0x00000031,   "tgeu",	FRS1 | FRT2 | FCODE },
    { 0x00000032,   "tlt",	FRS1 | FRT2 | FCODE },
    { 0x040a0000,   "tlti",	FRS1 | FOFF16 },
    { 0x040b0000,   "tltiu",	FRS1 | FOFF16 },
    { 0x00000033,   "tltu",	FRS1 | FRT2 | FCODE },
    { 0x00000036,   "tne",	FRS1 | FRT2 | FCODE },
    { 0x040e0000,   "tnei",	FRS1 | FOFF16 },
    { 0x42000020,   "wait",	FCODE },
    { 0x41c00000,   "wrpgpr",	FRD1 | FRT2 },
    { 0x7c0000a0,   "wsbh",	FRD1 | FRT2 | FMOD },
    { 0x00000026,   "xor",	FRD1 | FRS2 | FRT3 | FMOD },
    { 0x38000000,   "xori",	FRT1 | FRS2 | FOFF16 | FMOD },
    { 0,            0,          0 },
};

/*
 * Character classes.
 */
#define ISHEX(c)        (ctype[(c)&0377] & 1)
#define ISOCTAL(c)      (ctype[(c)&0377] & 2)
#define ISDIGIT(c)      (ctype[(c)&0377] & 4)
#define ISLETTER(c)     (ctype[(c)&0377] & 8)

const char ctype [256] = {
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,8,0,0,0,0,0,0,0,0,0,8,0,7,7,7,7,7,7,7,7,5,5,0,0,0,0,0,0,
    8,9,9,9,9,9,9,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,0,8,
    0,9,9,9,9,9,9,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,
};

FILE *sfile [SABS], *rfile [SABS];
unsigned count [SABS];
int segm;
char *infile, *outfile = "a.out";
char tfilename[] = "/tmp/asXXXXXX";
int line;                               /* Source line number */
int xflags, Xflag, uflag;
int stlength;                           /* Symbol table size in bytes */
int stalign;                            /* Symbol table alignment */
unsigned tbase, dbase, adbase, bbase;
struct nlist stab [STSIZE];
int stabfree;
char space [STSIZE*8];                  /* Area for symbol names */
int lastfree;                           /* Free space offset */
char name [256];
unsigned intval;
int extref;
int blexflag, backlex, blextype;
short hashtab [HASHSZ], hashctab [HCMDSZ];
struct labeltab labeltab [MAXRLAB];     /* relative labels */
int nlabels;
int mode_reorder = 1;                   /* .set reorder option (default) */
int mode_macro;                         /* .set macro option */
int mode_mips16;                        /* .set mips16 option */
int mode_micromips;                     /* .set micromips option */
int mode_at;                            /* .set at option */
int reorder_full;                       /* instruction buffered for reorder */
unsigned reorder_word;                  /* buffered instruction... */
unsigned reorder_clobber;               /* ...modified this register */
struct reloc reorder_rel;               /* buffered relocation */
struct reloc relabs = { RABS };         /* absolute relocation */

int expr_flags;                         /* flags set by getexpr */
#define EXPR_GPREL  1                   /* gp relative relocation */
#define EXPR_HI     2                   /* %hi function */
#define EXPR_LO     4                   /* %lo function */

/* Forward declarations. */
unsigned getexpr (int *s);

/*
 * Fatal error message.
 */
void uerror (char *fmt, ...)
{
    va_list ap;

    va_start (ap, fmt);
    fprintf (stderr, "as: ");
    if (infile)
        fprintf (stderr, "%s, ", infile);
    if (line)
        fprintf (stderr, "%d: ", line);
    vfprintf (stderr, fmt, ap);
    va_end (ap);
    fprintf (stderr, "\n");
    exit (1);
}

/*
 * Read a 4-byte word from the file.
 * Little-endian.
 */
unsigned fgetword (f)
    FILE *f;
{
    unsigned int w;

    if (fread (&w, sizeof(w), 1, f) != 1)
        return 0;
    return w;
}

/*
 * Write a 4-byte word to the file.
 * Little-endian.
 */
void fputword (w, f)
    unsigned int w;
    FILE *f;
{
    fwrite (&w, sizeof(w), 1, f);
}

/*
* Read a relocation record: 1 to 6 bytes.
*/
void fgetrel (f, r)
    register FILE *f;
    register struct reloc *r;
{
    r->flags = getc (f);
    if ((r->flags & RSMASK) == REXT) {
        r->index = getc (f);
        r->index |= getc (f) << 8;
        r->index |= getc (f) << 16;
    }
    if ((r->flags & RFMASK) == RHIGH16 ||
        (r->flags & RFMASK) == RHIGH16S)
    {
        r->offset = getc (f);
        r->offset |= getc (f) << 8;
    }
}

/*
* Emit a relocation record: 1 to 6 bytes.
* Return a written length.
*/
unsigned fputrel (r, f)
    register struct reloc *r;
    register FILE *f;
{
    register unsigned nbytes = 1;

    putc (r->flags, f);
    if ((r->flags & RSMASK) == REXT) {
        putc (r->index, f);
        putc (r->index >> 8, f);
        putc (r->index >> 16, f);
        nbytes += 3;
    }
    if ((r->flags & RFMASK) == RHIGH16 ||
        (r->flags & RFMASK) == RHIGH16S)
    {
        putc (r->offset, f);
        putc (r->offset >> 8, f);
        nbytes += 2;
    }
    return nbytes;
}

/*
 * Write the a.out header to the file.
 * Little-endian.
 */
void fputhdr (filhdr, coutb)
    register struct exec *filhdr;
    register FILE *coutb;
{
    fputword (filhdr->a_magic, coutb);
    fputword (filhdr->a_text, coutb);
    fputword (filhdr->a_data, coutb);
    fputword (filhdr->a_bss, coutb);
    fputword (filhdr->a_reltext, coutb);
    fputword (filhdr->a_reldata, coutb);
    fputword (filhdr->a_syms, coutb);
    fputword (filhdr->a_entry, coutb);
}

/*
 * Emit the nlist record for the symbol.
 */
void fputsym (s, file)
    register struct nlist *s;
    register FILE *file;
{
    register int i;

    putc (s->n_len, file);
    putc (s->n_type & ~N_LOC, file);
    fputword (s->n_value, file);
    for (i=0; i<s->n_len; i++)
        putc (s->n_name[i], file);
}

/*
 * Create temporary files for STEXT, SDATA and SSTRNG segments.
 */
void startup ()
{
    register int i;

    mktemp (tfilename);
    for (i=STEXT; i<SBSS; i++) {
        sfile [i] = fopen (tfilename, "w+");
        if (! sfile [i])
            uerror ("cannot open %s", tfilename);
        unlink (tfilename);
        rfile [i] = fopen (tfilename, "w+");
        if (! rfile [i])
            uerror ("cannot open %s", tfilename);
        unlink (tfilename);
    }
    line = 1;
}

/*
 * Suboptimal 32-bit hash function.
 * Copyright (C) 2006 Serge Vakulenko.
 */
unsigned hash_rot13 (s)
    register const char *s;
{
    register unsigned hash, c;

    hash = 0;
    while ((c = (unsigned char) *s++) != 0) {
        hash += c;
        hash -= (hash << 13) | (hash >> 19);
    }
    return hash;
}

void hashinit ()
{
    register int i, h;
    register const struct optable *p;

    for (i=0; i<HCMDSZ; i++)
        hashctab[i] = -1;
    for (p=optable; p->name; p++) {
        h = hash_rot13 (p->name) & (HCMDSZ-1);
        while (hashctab[h] != -1)
            if (--h < 0)
                h += HCMDSZ;
        hashctab[h] = p - optable;
    }
    for (i=0; i<HASHSZ; i++)
        hashtab[i] = -1;
}

int hexdig (c)
    register int c;
{
    if (c <= '9')
        return (c - '0');
    else if (c <= 'F')
        return (c - 'A' + 10);
    else
        return (c - 'a' + 10);
}

/*
 * Get hexadecimal number 0xZZZ
 */
void gethnum ()
{
    register int c;
    register char *cp;

    c = getchar ();
    for (cp=name; ISHEX(c); c=getchar())
        *cp++ = hexdig (c);
    ungetc (c, stdin);
    intval = 0;
    for (c=0; c<32; c+=4) {
        if (--cp < name)
            return;
        intval |= *cp << c;
    }
}

/*
 * Get a number.
 * 1234 - decimal
 * 01234 - octal
 */
void getnum (c)
    register int c;
{
    register char *cp;
    int leadingzero;

    leadingzero = (c=='0');
    for (cp=name; ISDIGIT(c); c=getchar())
        *cp++ = hexdig (c);
    ungetc (c, stdin);
    intval = 0;
    if (leadingzero) {
        /* Octal. */
        for (c=0; c<=27; c+=3) {
            if (--cp < name)
                return;
            intval |= *cp << c;
        }
        if (--cp < name)
            return;
        intval |= *cp << 30;
        return;
    }
    /* Decimal. */
    for (c=1; ; c*=10) {
        if (--cp < name)
            return;
        intval += *cp * c;
    }
}

void getname (c)
    register int c;
{
    register char *cp;

    for (cp=name; ISLETTER (c) || ISDIGIT (c); c=getchar())
        *cp++ = c;
    *cp = 0;
    ungetc (c, stdin);
}

int looktype ()
{
    switch (name [1]) {
    case 'c':
        if (! strcmp ("@common", name)) return (LSYMTYPE);
        break;
    case 'f':
        if (! strcmp ("@fini_array", name)) return (LSECTYPE);
        if (! strcmp ("@function", name)) return (LSYMTYPE);
        break;
    case 'g':
        if (! strcmp ("@gnu_indirect_function", name)) return (LSYMTYPE);
        if (! strcmp ("@gnu_unique_object", name)) return (LSYMTYPE);
        break;
    case 'i':
        if (! strcmp ("@init_array", name)) return (LSECTYPE);
        break;
    case 'n':
        if (! strcmp ("@nobits", name)) return (LSECTYPE);
        if (! strcmp ("@note", name)) return (LSECTYPE);
        if (! strcmp ("@notype", name)) return (LSYMTYPE);
        break;
    case 'o':
        if (! strcmp ("@object", name)) return (LSYMTYPE);
        break;
    case 'p':
        if (! strcmp ("@progbits", name)) return (LSECTYPE);
        if (! strcmp ("@preinit_array", name)) return (LSECTYPE);
        break;
    case 't':
        if (! strcmp ("@tls_object", name)) return (LSYMTYPE);
        break;
    }
    return (-1);
}

int lookacmd ()
{
    switch (name [1]) {
    case 'a':
        if (! strcmp (".ascii", name)) return (LASCII);
        if (! strcmp (".align", name)) return (LALIGN);
        break;
    case 'b':
        if (! strcmp (".bss", name)) return (LBSS);
        if (! strcmp (".byte", name)) return (LBYTE);
        break;
    case 'c':
        if (! strcmp (".comm", name)) return (LCOMM);
        break;
    case 'd':
        if (! strcmp (".data", name)) return (LDATA);
        break;
    case 'e':
        if (! strcmp (".equ", name)) return (LEQU);
        if (! strcmp (".end", name)) return (LEND);
        if (! strcmp (".ent", name)) return (LENT);
        break;
    case 'f':
        if (! strcmp (".file", name)) return (LFILE);
        if (! strcmp (".fmask", name)) return (LFMASK);
        if (! strcmp (".frame", name)) return (LFRAME);
        break;
    case 'g':
        if (! strcmp (".globl", name)) return (LGLOBL);
        if (! strcmp (".gnu_attribute", name)) return (LGNUATTR);
        break;
    case 'h':
        if (! strcmp (".half", name)) return (LHALF);
        break;
    case 'i':
        if (! strcmp (".ident", name)) return (LIDENT);
        break;
    case 'l':
        if (! strcmp (".local", name)) return (LLOCAL);
        break;
    case 'm':
        if (! strcmp (".mask", name)) return (LMASK);
        break;
    case 'p':
        if (! strcmp (".previous", name)) return (LPREVIOUS);
        break;
    case 'r':
        if (! strcmp (".rdata", name)) return (LRDATA);
        break;
    case 's':
        if (! strcmp (".section", name)) return (LSECTION);
        if (! strcmp (".set", name)) return (LSET);
        if (! strcmp (".size", name)) return (LSIZE);
        if (! strcmp (".space", name)) return (LSPACE);
        if (! strcmp (".strng", name)) return (LSTRNG);
        break;
    case 't':
        if (! strcmp (".text", name)) return (LTEXT);
        if (! strcmp (".type", name)) return (LTYPE);
        break;
    case 'w':
        if (! strcmp (".word", name)) return (LWORD);
        if (! strcmp (".weak", name)) return (LWEAK);
        break;
    }
    return (-1);
}

/*
 * Change a segment based on a section name.
 */
void setsection ()
{
    struct {
        const char *name;
        int len;
        int segm;
    } const *p, map[] = {
        { ".text",   5, STEXT  },
        { ".data",   5, SDATA  },
        { ".sdata",  6, SDATA  },
        { ".rodata", 7, SSTRNG },
        { ".bss",    4, SBSS   },
        { ".sbss",   5, SBSS   },
        { ".mdebug", 7, SSTRNG },
        { 0 },
    };

    for (p=map; p->name; p++) {
        if (strncmp (name, p->name, p->len) == 0 &&
            (p->name [p->len] == 0 ||
             p->name [p->len] == '.'))
        {
            segm = p->segm;
            return;
        }
    }
    uerror ("bad .section name");
}

int lookreg ()
{
    int val;
    char *cp;

    switch (name [1]) {
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
        val = 0;
        for (cp=name+1; ISDIGIT (*cp); cp++) {
            val *= 10;
            val += *cp - '0';
        }
        if (*cp != 0)
            break;
        return val;
    case 'a':
        if (name[3] == 0) {
            switch (name[2]) {
            case '0': return 4;         /* $a0 */
            case '1': return 5;         /* $a1 */
            case '2': return 6;         /* $a2 */
            case '3': return 7;         /* $a3 */
            case 't': return 1;         /* $at */
            }
        }
        break;
    case 'f':
        if (name[3] == 0 && name[2] == 'p')
            return 30;                  /* $fp */
        break;
    case 'g':
        if (name[3] == 0 && name[2] == 'p')
            return 28;                  /* $gp */
        break;
    case 'k':
        if (name[3] == 0) {
            switch (name[2]) {
            case '0': return 26;        /* $k0 */
            case '1': return 27;        /* $k1 */
            }
        }
        break;
    case 'r':
        if (name[3] == 0 && name[2] == 'a')
            return 31;                  /* $ra */
        break;
    case 's':
        if (name[3] == 0) {
            switch (name[2]) {
            case '0': return 16;        /* $s0 */
            case '1': return 17;        /* $s1 */
            case '2': return 18;        /* $s2 */
            case '3': return 19;        /* $s3 */
            case '4': return 20;        /* $s4 */
            case '5': return 21;        /* $s5 */
            case '6': return 22;        /* $s6 */
            case '7': return 23;        /* $s7 */
            case '8': return 30;        /* $s8 */
            case 'p': return 29;        /* $sp */
            }
        }
        break;
    case 't':
        if (name[3] == 0) {
            switch (name[2]) {
            case '0': return 8;         /* $t0 */
            case '1': return 9;         /* $t1 */
            case '2': return 10;        /* $t2 */
            case '3': return 11;        /* $t3 */
            case '4': return 12;        /* $t4 */
            case '5': return 13;        /* $t5 */
            case '6': return 14;        /* $t6 */
            case '7': return 15;        /* $t7 */
            case '8': return 24;        /* $t8 */
            case '9': return 25;        /* $t9 */
            }
        }
        break;
    case 'v':
        if (name[3] == 0) {
            switch (name[2]) {
            case '0': return 2;         /* $v0 */
            case '1': return 3;         /* $v1 */
            }
        }
        break;
    case 'z':
        if (! strcmp (name+2, "ero"))
            return 0;                   /* $zero */
        break;
    }
    return -1;
}

int lookcmd ()
{
    register int i, h;

    h = hash_rot13 (name) & (HCMDSZ-1);
    while ((i = hashctab[h]) != -1) {
        if (! strcmp (optable[i].name, name))
            return (i);
        if (--h < 0)
            h += HCMDSZ;
    }
    return (-1);
}

char *alloc (len)
{
    register int r;

    r = lastfree;
    lastfree += len;
    if (lastfree > sizeof(space))
        uerror ("out of memory");
    return (space + r);
}

int lookname ()
{
    register int i, h;

    /* Search for symbol name. */
    h = hash_rot13 (name) & (HASHSZ-1);
    while ((i = hashtab[h]) != -1) {
        if (! strcmp (stab[i].n_name, name))
            return (i);
        if (--h < 0)
            h += HASHSZ;
    }

    /* Add a new symbol to table. */
    if ((i = stabfree++) >= STSIZE)
        uerror ("symbol table overflow");
    stab[i].n_len = strlen (name);
    stab[i].n_name = alloc (1 + stab[i].n_len);
    strcpy (stab[i].n_name, name);
    stab[i].n_value = 0;
    stab[i].n_type = N_UNDF;
    hashtab[h] = i;
    return (i);
}

/*
 * Read a lexical element, return it's type and store a value into *val.
 * Returned type codes:
 * LEOL    - End of line.  Value is a line number.
 * LEOF    - End of file.
 * LNUM    - Integer value (into intval), *val undefined.
 * LNAME   - Identifier.  String value is in name[] array.
 * LREG    - Machine register.  Value is a register number.
 * LLSHIFT - << operator.
 * LRSHIFT - >> operator.
 * LASCII  - .ascii assembler instruction.
 * LBSS    - .bss assembler instruction.
 * LCOMM   - .comm assembler instruction.
 * LDATA   - .data assembler instruction.
 * LGLOBL  - .globl assembler instruction.
 * LHALF   - .half assembler instruction.
 * LSTRNG  - .strng assembler instruction.
 * LTEXT   - .text assembler instruction.
 * LEQU    - .equ assembler instruction.
 * LWORD   - .word assembler instruction.
 * LFILE   - .file assembler instruction.
 * LSECTION - .section assembler instruction.
 */
int getlex (pval)
    register int *pval;
{
    register int c;

    if (blexflag) {
        blexflag = 0;
        *pval = blextype;
        return (backlex);
    }
    for (;;) {
        switch (c = getchar()) {
        case '#':
skiptoeol:  while ((c = getchar()) != '\n')
                if (c == EOF)
                    return (LEOF);
        case '\n':
            ++line;
            c = getchar ();
            if (c == '#')
                goto skiptoeol;
            ungetc (c, stdin);
        case ';':
            *pval = line;
            return (LEOL);
        case ' ':
        case '\t':
            continue;
        case EOF:
            return (LEOF);
        case '\\':
            c = getchar ();
            if (c=='<')
                return (LLSHIFT);
            if (c=='>')
                return (LRSHIFT);
            ungetc (c, stdin);
            return ('\\');
        case '\'':      case '%':
        case '^':       case '&':       case '|':       case '~':
        case '+':       case '-':       case '*':       case '/':
        case '"':       case ',':       case '[':       case ']':
        case '(':       case ')':       case '{':       case '}':
        case '<':       case '>':       case '=':       case ':':
            return (c);
        case '0':
            if ((c = getchar ()) == 'x' || c=='X') {
                gethnum ();
                return (LNUM);
            }
            ungetc (c, stdin);
            c = '0';
        case '1':       case '2':       case '3':
        case '4':       case '5':       case '6':       case '7':
        case '8':       case '9':
            getnum (c);
            return (LNUM);
        default:
            if (! ISLETTER (c))
                uerror ("bad character: \\%o", c & 0377);
            getname (c);
            if (name[0] == '.') {
                if (name[1] == 0)
                    return ('.');
                *pval = lookacmd();
                if (*pval != -1)
                    return (*pval);
            }
            if (name[0] == '$') {
                *pval = lookreg();
                if (*pval != -1)
                    return (LREG);
            }
            if (name[0] == '@') {
                *pval = looktype();
                if (*pval != -1)
                    return (*pval);
            }
            return (LNAME);
        }
    }
}

void ungetlex (val, type)
{
    blexflag = 1;
    backlex = val;
    blextype = type;
}

int getterm ()
{
    register int ty;
    int cval, s;

    switch (getlex (&cval)) {
    default:
        uerror ("operand missed");
    case LNUM:
        cval = getchar ();
        if (cval == 'b' || cval == 'B')
            extref = RLAB_OFFSET - intval;
        else if (cval == 'f' || cval == 'F')
            extref = RLAB_OFFSET + intval;
        else {
            /* Integer literal. */
            ungetc (cval, stdin);
            return (SABS);
        }
        /* Local label. */
        if (intval >= RLAB_MAXVAL)
            uerror ("too large relative label");
        intval = 0;
        return (SEXT);
    case LNAME:
        intval = 0;
        cval = lookname();
        ty = stab[cval].n_type & N_TYPE;
        if (ty==N_UNDF || ty==N_COMM) {
            extref = cval;
            return (SEXT);
        }
        intval = stab[cval].n_value;
        return (typesegm [ty]);
    case '.':
        intval = count[segm];
        return (segm);
    case '%':
        if (getlex (&cval) != LNAME)
            uerror ("bad %function name");
        if (strcmp (name, "gp_rel") == 0) {
            /* GP relative reverence. */
            expr_flags |= EXPR_GPREL;
        } else if (strcmp (name, "hi") == 0) {
            expr_flags |= EXPR_HI;
        } else if (strcmp (name, "lo") == 0) {
            expr_flags |= EXPR_LO;
        } else
            uerror ("unknown function %s", name);
        if (getlex (&cval) != '(')
            uerror ("bad %s syntax", name);
        /* fall through */
    case '(':
        getexpr (&s);
        if (getlex (&cval) != ')')
            uerror ("bad () syntax");
        return (s);
    }
}

/*
 * Get an expression.
 * Return a value, put a base segment id to *s.
 * A copy of value is saved in intval.
 *
 * expression = [term] {op term}...
 * term       = LNAME | LNUM | "." | "(" expression ")"
 * op         = "+" | "-" | "&" | "|" | "^" | "~" | "<<" | ">>" | "/" | "*"
 */
unsigned getexpr (s)
    register int *s;
{
    register int clex;
    int cval, s2;
    unsigned rez;

    /* look a first lexeme */
    switch (clex = getlex (&cval)) {
    default:
        ungetlex (clex, cval);
        rez = 0;
        *s = SABS;
        break;
    case LNUM:
    case LNAME:
    case '.':
    case '(':
    case '%':
        ungetlex (clex, cval);
        *s = getterm ();
        rez = intval;
        break;
    }
    for (;;) {
        switch (clex = getlex (&cval)) {
        case '+':
            s2 = getterm ();
            if (*s == SABS)
                *s = s2;
            else if (s2 != SABS)
                uerror ("too complex expression");
            rez += intval;
            break;
        case '-':
            s2 = getterm ();
            if (s2 == *s && s2 != SEXT)
                *s = SABS;
            else if (s2 != SABS)
                uerror ("too complex expression");
            rez -= intval;
            break;
        case '&':
            s2 = getterm ();
            if (*s != SABS || s2 != SABS)
                uerror ("too complex expression");
            rez &= intval;
            break;
        case '|':
            s2 = getterm ();
            if (*s != SABS || s2 != SABS)
                uerror ("too complex expression");
            rez |= intval;
            break;
        case '^':
            s2 = getterm ();
            if (*s != SABS || s2 != SABS)
                uerror ("too complex expression");
            rez ^= intval;
            break;
        case '~':
            s2 = getterm ();
            if (*s != SABS || s2 != SABS)
                uerror ("too complex expression");
            rez ^= ~intval;
            break;
        case LLSHIFT:           /* сдвиг влево */
            s2 = getterm ();
            if (*s != SABS || s2 != SABS)
                uerror ("too complex expression");
            rez <<= intval & 037;
            break;
        case LRSHIFT:           /* сдвиг вправо */
            s2 = getterm ();
            if (*s != SABS || s2 != SABS)
                uerror ("too complex expression");
            rez >>= intval & 037;
            break;
        case '*':
            s2 = getterm ();
            if (*s != SABS || s2 != SABS)
                uerror ("too complex expression");
            rez *= intval;
            break;
        case '/':
            s2 = getterm ();
            if (*s != SABS || s2 != SABS)
                uerror ("too complex expression");
            if (intval == 0)
                uerror ("division by zero");
            rez /= intval;
            break;
        default:
            ungetlex (clex, cval);
            intval = rez;
            return (rez);
        }
    }
    /* NOTREACHED */
}

void reorder_flush ()
{
    if (reorder_full) {
        fputword (reorder_word, sfile[segm]);
        fputrel (&reorder_rel, rfile[segm]);
        reorder_full = 0;
    }
}

/*
 * Default emit function.
 */
void emitword (w, r, clobber_reg)
    register unsigned w;
    register struct reloc *r;
    int clobber_reg;
{
    if (mode_reorder && segm == STEXT) {
        reorder_flush();
        reorder_word = w;
        reorder_rel = *r;
        reorder_full = 1;
        reorder_clobber = clobber_reg;
    } else {
        fputword (w, sfile[segm]);
        fputrel (r, rfile[segm]);
    }
    count[segm] += WORDSZ;
}

/*
 * LI pseudo instruction.
 */
void emit_li (opcode, relinfo)
    register unsigned opcode;
    register struct reloc *relinfo;
{
    register unsigned value;
    int cval, segment;

    if (getlex (&cval) != ',')
        uerror ("comma expected");
    value = getexpr (&segment);
    if (segment != SABS)
        uerror ("absolute value required");
    if (value <= 0xffff) {
        /* ori d, $zero, value */
        opcode |= 0x34000000 | value;
    } else if (value >= -0x8000) {
        /* addiu d, $zero, value */
        opcode |= 0x24000000 | (value & 0xffff);
    } else {
        /* lui d, value[31:16]
         * ori d, d, value[15:0]) */
        emitword (opcode | 0x3c000000 | (value >> 16), &relabs, value >> 16);
        opcode |= 0x34000000 | (opcode & 0x1f0000) << 5 | (value & 0xffff);
    }
    emitword (opcode, relinfo, value >> 16);
}

/*
 * LA pseudo instruction.
 */
void emit_la (opcode, relinfo)
    register unsigned opcode;
    register struct reloc *relinfo;
{
    register unsigned value;
    int cval, segment;

    if (getlex (&cval) != ',')
        uerror ("comma expected");
    expr_flags = 0;
    value = getexpr (&segment);
    if (segment == SABS)
	uerror ("relocatable value required");
    relinfo->flags = segmrel [segment];
    if (relinfo->flags == REXT)
	relinfo->index = extref;
    if (expr_flags & EXPR_GPREL)
	relinfo->flags |= RGPREL;

    /* lui d, %hi(value)
     * addiu d, d, %lo(value) */
    relinfo->flags |= RHIGH16S;
    relinfo->offset = value & 0xffff;
    emitword (opcode | 0x3c000000 | (value >> 16), relinfo, (value + 0x8000) >> 16);

    relinfo->flags &= ~RHIGH16S;
    opcode |= 0x24000000 | (opcode & 0x1f0000) << 5 | (value & 0xffff);
    emitword (opcode, relinfo, value >> 16);
}

/*
 * Build and emit a machine instruction code.
 */
void makecmd (opcode, type, emitfunc)
    unsigned opcode;
    void (*emitfunc) (unsigned, struct reloc*);
{
    register int clex;
    register unsigned offset;
    struct reloc relinfo;
    int cval, segment, clobber_reg, negate_literal;

    offset = 0;
    relinfo.flags = RABS;
    negate_literal = 0;

    /*
     * GCC can generate "j" instead of "jr".
     * Need to detect it early.
     */
    if (type == (FAOFF28 | FDSLOT)) {
        clex = getlex (&cval);
        ungetlex (clex, cval);
        if (clex == LREG) {
            if (opcode == 0x08000000) { /* j - replace by jr */
                opcode = 0x00000008;
                type = FRS1 | FDSLOT;
            }
            if (opcode == 0x0c000000) { /* jal - replace by jalr */
                opcode = 0x00000009;
                type = FRD1 | FRS2 | FDSLOT;
            }
        }
    }

    /*
     * First register.
     */
    cval = 0;
    clobber_reg = 0;
    if (type & FRD1) {
        clex = getlex (&cval);
        if (clex != LREG)
            uerror ("bad rd register");
        opcode |= cval << 11;           /* rd, ... */
    }
    if (type & FRT1) {
        clex = getlex (&cval);
        if (clex != LREG)
            uerror ("bad rt register");
        opcode |= cval << 16;           /* rt, ... */
    }
    if (type & FRS1) {
frs1:   clex = getlex (&cval);
        if (clex != LREG)
            uerror ("bad rs register");
        if (cval == 0 && (opcode == 0x0000001a ||   /* div */
                          opcode == 0x0000001b)) {  /* divu */
            /* Div instruction with three args.
             * Treat it as a 2-arg variant. */
            if (getlex (&cval) != ',')
                uerror ("comma expected");
            goto frs1;
        }
        opcode |= cval << 21;           /* rs, ... */
    }
    if (type & FRTD) {
        opcode |= cval << 16;           /* rt equals rd */
    }
    if ((type & FMOD) && (type & (FRD1 | FRT1 | FRS1)))
        clobber_reg = cval;

    /*
     * Second register.
     */
    if (type & FRD2) {
        if (getlex (&cval) != ',')
            uerror ("comma expected");
        clex = getlex (&cval);
        if (clex != LREG)
            uerror ("bad rd register");
        opcode |= cval << 11;           /* .., rd, ... */
    }
    if (type & FRT2) {
        if (getlex (&cval) != ',')
            uerror ("comma expected");
        clex = getlex (&cval);
        if (clex != LREG) {
            if ((type & FRD1) && (type & FSA)) {
                /* Second register operand omitted.
                 * Need to restore the missing operand. */
                ungetlex (clex, cval);
                cval = (opcode >> 11) & 31; /* get 1-st register */
                opcode |= cval << 16;       /* use 1-st reg as 2-nd */
                goto fsa;
            }
            uerror ("bad rt register");
        }
        opcode |= cval << 16;           /* .., rt, ... */
    }
    if (type & FRS2) {
        clex = getlex (&cval);
        if (clex != ',') {
            if ((opcode & 0xfc00003f) != 0x00000009)
                uerror ("comma expected");
            /* Jalr with one argument.
             * Treat as if the first argument is $31. */
            ungetlex (clex, cval);
            cval = (opcode >> 11) & 31; /* get 1-st register */
            opcode |= cval << 21;       /* use 1-st reg as 2-nd */
            opcode |= 31 << 11;         /* set 1-st reg to 31 */
            clobber_reg = 31;
            goto done3;
        }
        clex = getlex (&cval);
        if (clex != LREG) {
            if ((type & FRT1) && (type & FOFF16)) {
                /* Second register operand omitted.
                 * Need to restore the missing operand. */
                ungetlex (clex, cval);
                cval = (opcode >> 16) & 31; /* get 1-st register */
                opcode |= cval << 21;       /* use 1-st reg as 2-nd */
                goto foff16;
            }
            uerror ("bad rs register");
        }
        opcode |= cval << 21;           /* .., rs, ... */
    }

    /*
     * Third register.
     */
    if (type & FRT3) {
        clex = getlex (&cval);
        if (clex != ',') {
            /* Three-operand instruction used with two operands.
             * Need to restore the missing operand. */
            ungetlex (clex, cval);
            cval = (opcode >> 21) & 31;
            opcode &= ~(31 << 21);                  /* clear 2-nd register */
            opcode |= ((opcode >> 11) & 31) << 21;  /* use 1-st reg as 2-nd */
            opcode |= cval << 16;                   /* add 3-rd register */
            goto done3;
        }
        clex = getlex (&cval);
        if (clex != LREG) {
            if ((type & FRD1) && (type & FRS2)) {
                /* Three-operand instruction used with literal operand.
                 * Convert it to immediate type. */
                unsigned newop;
                switch (opcode & 0xfc0007ff) {
                case 0x00000020: newop = 0x20000000; break; // add -> addi
                case 0x00000021: newop = 0x24000000; break; // addu -> addiu
                case 0x00000024: newop = 0x30000000; break; // and -> andi
                case 0x00000025: newop = 0x34000000; break; // or -> ori
                case 0x0000002a: newop = 0x28000000; break; // slt -> slti
                case 0x0000002b: newop = 0x2c000000; break; // sltu -> sltiu
                case 0x00000022: newop = 0x20000000;        // sub -> addi, negate
                                 negate_literal = 1; break;
                case 0x00000023: newop = 0x24000000;        // subu -> addiu, negate
                                 negate_literal = 1; break;
                case 0x00000026: newop = 0x38000000; break; // xor -> xori
                default:
                    uerror ("bad rt register");
                    return;
                }
                ungetlex (clex, cval);
                cval = (opcode >> 11) & 31;         /* get 1-st register */
                newop |= cval << 16;                /* set 1-st register */
                newop |= opcode & (31 << 21);       /* set 2-nd register */
                opcode = newop;
                type = FRT1 | FRS2 | FOFF16 | FMOD;
                goto foff16;
            }
            uerror ("bad rt register");
        }
        opcode |= cval << 16;           /* .., .., rt */
    }
    if (type & FRS3) {
        clex = getlex (&cval);
        if (clex != ',') {
            /* Three-operand instruction used with two operands.
             * Need to restore the missing operand. */
            ungetlex (clex, cval);
            cval = (opcode >> 16) & 31;
            opcode &= ~(31 << 16);                  /* clear 2-nd register */
            opcode |= ((opcode >> 11) & 31) << 16;  /* use 1-st reg as 2-nd */
            opcode |= cval << 21;                   /* add 3-rd register */
            goto done3;
        }
        clex = getlex (&cval);
        if (clex != LREG)
            uerror ("bad rs register");
        opcode |= cval << 21;           /* .., .., rs */
    }
done3:

    /*
     * Immediate argument.
     */
    if (type & FSEL) {
        /* optional COP0 register select */
        clex = getlex (&cval);
        if (clex == ',') {
            offset = getexpr (&segment);
            if (segment != SABS)
                uerror ("absolute value required");
            opcode |= offset & 7;
        } else
            ungetlex (clex, cval);

    } else if (type & (FCODE | FCODE16 | FSA)) {
        /* Non-relocatable offset */
        if (type & FSA) {
            if (getlex (&cval) != ',')
                uerror ("comma expected");
            clex = getlex (&cval);
            if (clex == LREG && type == (FRD1 | FRT2 | FSA | FMOD)) {
                /* Literal-operand shift instruction used with register operand.
                 * Convert it to 3-register type. */
                unsigned newop;
                switch (opcode & 0xffe0003f) {
                case 0x00200002: newop = 0x00000046; break; // ror -> rorv
                case 0x00000000: newop = 0x00000004; break; // sll -> sllv
                case 0x00000003: newop = 0x00000007; break; // sra -> srav
                case 0x00000002: newop = 0x00000006; break; // srl -> srlv
                default:
                    uerror ("bad shift amount");
                    return;
                }
                newop |= opcode & (0x3ff << 11);    /* set 1-st and 2-nd regs */
                newop |= cval << 21;                /* set 3-rd register */
                opcode = newop;
                type = FRD1 | FRT2 | FRS3 | FMOD;
                goto done3;
            }
            ungetlex (clex, cval);
        }
        if ((type & FCODE) && (type & FRT2)) {
            /* Optional code for trap instruction. */
            clex = getlex (&cval);
            if (clex != ',') {
                ungetlex (clex, cval);
                goto done;
            }
        }
fsa:    offset = getexpr (&segment);
        if (segment != SABS)
            uerror ("absolute value required");
        switch (type & (FCODE | FCODE16 | FSA)) {
        case FCODE:                     /* immediate shifted <<6 */
            opcode |= offset << 6;
            break;
        case FCODE16:                   /* immediate shifted <<16 */
            opcode |= offset << 16;
            break;
        case FSA:                       /* shift amount */
            opcode |= (offset & 0x1f) << 6;
            break;
        }
    } else if (type & (FOFF16 | FOFF18 | FAOFF18 | FAOFF28 | FHIGH16)) {
        /* Relocatable offset */
        if ((type & (FOFF16 | FOFF18 | FHIGH16)) && getlex (&cval) != ',')
            uerror ("comma expected");
foff16: expr_flags = 0;
        offset = getexpr (&segment);
        relinfo.flags = segmrel [segment];
        if (negate_literal) {
            // Negate literal arg for sub and subu
            offset = -offset;
            if (relinfo.flags != RABS)
                uerror ("cannot negate relocatable literal");
        }
        if (relinfo.flags == REXT)
            relinfo.index = extref;
        if (expr_flags & EXPR_GPREL)
            relinfo.flags |= RGPREL;
        switch (type & (FOFF16 | FOFF18 | FAOFF18 | FAOFF28 | FHIGH16)) {
        case FOFF16:                    /* low 16-bit byte address */
            opcode |= offset & 0xffff;
            break;
        case FHIGH16:                   /* high 16-bit byte address */
            if (relinfo.flags != RABS) {
                /* %hi function - assume signed offset */
                relinfo.flags |= (expr_flags & EXPR_HI) ? RHIGH16S : RHIGH16;
                relinfo.offset = offset & 0xffff;
                offset += 0x8000;
            }
            opcode |= offset >> 16;
            break;
        case FOFF18:                    /* 18-bit PC-relative word address */
        case FAOFF18:
            if (segment == segm) {
                offset -= count[segm] + 4;
                relinfo.flags = RABS;
            } else if (segment == SEXT) {
                relinfo.flags |= RWORD16;
            } else
                uerror ("invalid segment %d", segment);
            opcode |= (offset >> 2) & 0xffff;
            break;
        case FAOFF28:                   /* 28-bit word address */
            opcode |= (offset >> 2) & 0x3ffffff;
            relinfo.flags |= RWORD26;
            break;
        }
    }

    /*
     * Last argument.
     */
    if (type & FRSB) {
        if (getlex (&cval) != '(')
            uerror ("left par expected");
        clex = getlex (&cval);
        if (clex != LREG)
            uerror ("bad rs register");
        if (getlex (&cval) != ')')
            uerror ("right par expected");
        opcode |= cval << 21;           /* ... (rs) */
    }
    if (type & FSIZE) {
        if (getlex (&cval) != ',')
            uerror ("comma expected");
        offset = getexpr (&segment);
        if (segment != SABS)
            uerror ("absolute value required");
        opcode |= ((offset - 1) & 0x1f) << 11; /* bit field size */
    }
    if (type & FMSB) {
        if (getlex (&cval) != ',')
            uerror ("comma expected");
        offset += getexpr (&segment);
        if (segment != SABS)
            uerror ("absolute value required");
        if (offset > 32)
            offset = 32;
        opcode |= ((offset - 1) & 0x1f) << 11; /* msb */
    }
done:

    /* Output resulting values. */
    if (emitfunc) {
        emitfunc (opcode, &relinfo);
    } else if (mode_reorder && (type & FDSLOT) && segm == STEXT) {
        /* Need a delay slot. */
        if (reorder_full && reorder_clobber != 0) {
            /* Analyse register dependency.
             * Flush the instruction if needed. */
            int rt = (opcode >> 16) & 31;
            int rs = (opcode >> 21) & 31;
            if (((type & (FRS1 | FRS2)) && rs == reorder_clobber) ||
                ((type & FRT2) && rt == reorder_clobber))
                reorder_flush();
        }
        fputword (opcode, sfile[segm]);
        fputrel (&relinfo, rfile[segm]);
        if (reorder_full) {
            /* Delay slot: insert a previous instruction. */
            reorder_flush();
        } else {
            /* Insert NOP in delay slot. */
            fputword (0, sfile[segm]);
            fputrel (&relabs, rfile[segm]);
            count[segm] += WORDSZ;
        }
        count[segm] += WORDSZ;
    } else {
        emitword (opcode, &relinfo, clobber_reg);
    }
}

/*
 * Increment the current segment by nbytes.
 * Emit a needed amount of zeroes to sfile and rfile.
 * Part of data have already been sent to rfile;
 * length specified by 'done' argument.
 */
void add_space (nbytes, fill_data)
    unsigned nbytes, fill_data;
{
    unsigned c;

    if (segm < SBSS) {
        /* Emit data and relocation. */
        for (c=0; c<nbytes; c++) {
            count[segm]++;
            if (fill_data)
                fputc (0, sfile[segm]);
            if (! (count[segm] & 3))
                fputrel (&relabs, rfile[segm]);
        }
    } else
        count[segm] += nbytes;
}

void makeascii ()
{
    register int c, nbytes;
    int cval;

    c = getlex (&cval);
    if (c != '"')
        uerror ("no .ascii parameter");
    nbytes = 0;
    for (;;) {
        c = getchar ();
        switch (c) {
        case EOF:
            uerror ("EOF in text string");
        case '"':
            break;
        case '\\':
            c = getchar ();
            switch (c) {
            case EOF:
                uerror ("EOF in text string");
            case '\n':
                continue;
            case '0': case '1': case '2': case '3':
            case '4': case '5': case '6': case '7':
                cval = c & 07;
                c = getchar ();
                if (c>='0' && c<='7') {
                    cval = (cval << 3) | (c & 7);
                    c = getchar ();
                    if (c>='0' && c<='7') {
                        cval = (cval << 3) | (c & 7);
                    } else
                        ungetc (c, stdin);
                } else
                    ungetc (c, stdin);
                c = cval;
                break;
            case 't':
                c = '\t';
                break;
            case 'b':
                c = '\b';
                break;
            case 'r':
                c = '\r';
                break;
            case 'n':
                c = '\n';
                break;
            case 'f':
                c = '\f';
                break;
            }
        default:
            fputc (c, sfile[segm]);
            nbytes++;
            continue;
        }
        break;
    }
    add_space (nbytes, 0);
}

/*
 * Skip a string from the input file.
 */
void skipstring ()
{
    int c, cval;

    c = getlex (&cval);
    if (c != '"')
        uerror ("no string parameter");
    for (;;) {
        c = getchar ();
        switch (c) {
        case EOF:
            uerror ("EOF in text string");
        case '"':
            break;
        case '\\':
            c = getchar ();
            switch (c) {
            case EOF:
                uerror ("EOF in text string");
            case '\n':
                continue;
            case '0': case '1': case '2': case '3':
            case '4': case '5': case '6': case '7':
                c = getchar ();
                if (c>='0' && c<='7') {
                    c = getchar ();
                    if (c>='0' && c<='7') {
                    } else
                        ungetc (c, stdin);
                } else
                    ungetc (c, stdin);
                break;
            }
        default:
            continue;
        }
        break;
    }
}

/*
 * Set assembler option.
 */
void setoption ()
{
    const char *option = name;
    int enable = 1;

    if (option[0] == 'n' && option[1] == 'o') {
        enable = 0;
        option += 2;
    }
    if (! strcmp ("reorder", option)) {
        /* reorder mode */
        mode_reorder = enable;
        if (! mode_reorder)
            reorder_flush();
        return;
    }
    if (! strcmp ("macro", option)) {
        /* macro mode */
        mode_macro = enable;
        return;
    }
    if (! strcmp ("mips16", option)) {
        /* mips16 mode */
        mode_mips16 = enable;
        return;
    }
    if (! strcmp ("micromips", option)) {
        /* micromips mode */
        mode_micromips = enable;
        return;
    }
    if (! strcmp ("at", option)) {
        /* at mode */
        mode_at = enable;
        return;
    }
    uerror ("unknown option %s", option);
}

/*
 * Align the current segment.
 */
void align (align_bits)
{
    unsigned nbytes, align_mask, c;

    align_mask = (1 << align_bits) - 1;
    nbytes = count[segm] & align_mask;
    if (nbytes == 0)
        return;
    nbytes = align_mask + 1 - nbytes;
    if (segm < SBSS) {
        /* Emit data and relocation. */
        for (c=0; c<nbytes; c++) {
            count[segm]++;
            fputc (0, sfile[segm]);
            if (! (count[segm] & 3))
                fputrel (&relabs, rfile[segm]);
        }
    } else
        count[segm] += nbytes;
}

void pass1 ()
{
    register int clex;
    int cval, tval, csegm, nbytes;
    register unsigned addr;

    segm = STEXT;
    for (;;) {
        clex = getlex (&cval);
        switch (clex) {
        case LEOF:
            reorder_flush();
            segm = STEXT;
            align (2);
            segm = SDATA;
            align (2);
            segm = SSTRNG;
            align (2);
            segm = SBSS;
            align (2);
            return;
        case LEOL:
            continue;
        case ':':
            continue;
        case '.':
            if (getlex (&cval) != '=')
                uerror ("bad instruction");
            addr = getexpr (&csegm);
            if (csegm != segm)
                uerror ("bad count assignment");
            if (addr < count[segm])
                uerror ("negative count increment");
            reorder_flush();
            if (segm == SBSS)
                count [segm] = addr;
            else {
                while (count[segm] < addr) {
                    emitword (0, &relabs, 0);
                }
            }
            break;
        case LNAME:
            cval = lookcmd();
            clex = getlex (&tval);
            if (clex == ':') {
                /* Label. */
                reorder_flush();
                cval = lookname();
                stab[cval].n_value = count[segm];
                stab[cval].n_type &= ~N_TYPE;
                stab[cval].n_type |= segmtype [segm];
                continue;
            } else if (clex=='=' || clex==LEQU) {
                /* Symbol definition. */
                cval = lookname();
                stab[cval].n_value = getexpr (&csegm);
                if (csegm == SEXT)
                    uerror ("indirect equivalence");
                stab[cval].n_type &= N_EXT;
                stab[cval].n_type |= segmtype [csegm];
                break;
            } else if (clex==LCOMM) {
                /* name .comm len */
                cval = lookname();
                if (stab[cval].n_type != N_UNDF &&
                    stab[cval].n_type != N_LOC &&
                    (stab[cval].n_type & N_TYPE) != N_COMM)
                    uerror ("name already defined");
                if (stab[cval].n_type & N_LOC)
                    stab[cval].n_type = N_COMM;
                else
                    stab[cval].n_type = N_EXT | N_COMM;
                getexpr (&tval);
                if (tval != SABS)
                    uerror ("bad .comm length");
                stab[cval].n_value = intval;
                break;
            }
            /* Machine instruction. */
            if (cval < 0)
                uerror ("bad instruction");
            ungetlex (clex, tval);
            align (2);
            makecmd (optable[cval].opcode, optable[cval].type,
                optable[cval].func);
            break;
        case LNUM:
            /* Local label. */
            if (nlabels >= MAXRLAB)
                uerror ("too many digital labels");
            reorder_flush();
            labeltab[nlabels].num = intval;
            labeltab[nlabels].value = count[segm];
            ++nlabels;
            clex = getlex (&tval);
            if (clex != ':')
                uerror ("bad digital label");
            continue;
        case LTEXT:
            segm = STEXT;
            reorder_flush();
            break;
        case LDATA:
            segm = SDATA;
            break;
        case LSTRNG:
        case LRDATA:
            segm = SSTRNG;
            break;
        case LBSS:
            segm = SBSS;
            break;
        case LWORD:
            reorder_flush();
            align (2);
            for (;;) {
                struct reloc relinfo;
                expr_flags = 0;
                getexpr (&cval);
                relinfo.flags = RBYTE32 | segmrel [cval];
                if (cval == SEXT)
                    relinfo.index = extref;
                if (expr_flags & EXPR_GPREL)
                    relinfo.flags |= RGPREL;
                emitword (intval, &relinfo, 0);
                clex = getlex (&cval);
                if (clex != ',') {
                    ungetlex (clex, cval);
                    break;
                }
            }
            break;
        case LBYTE:
            reorder_flush();
	    nbytes = 0;
            for (;;) {
                getexpr (&cval);
		fputc (intval, sfile[segm]);
		nbytes++;
                clex = getlex (&cval);
                if (clex != ',') {
                    ungetlex (clex, cval);
                    break;
                }
            }
            add_space (nbytes, 0);
            break;
        case LHALF:
            reorder_flush();
            align (1);
	    nbytes = 0;
            for (;;) {
                getexpr (&cval);
		fputc (intval, sfile[segm]);
		fputc (intval >> 8, sfile[segm]);
		nbytes += 2;
                clex = getlex (&cval);
                if (clex != ',') {
                    ungetlex (clex, cval);
                    break;
                }
            }
            add_space (nbytes, 0);
            break;
        case LSPACE:
            /* .space num */
            getexpr (&cval);
            reorder_flush();
            add_space (intval, 1);
            break;
        case LALIGN:
            /* .align num */
            if (getlex (&cval) != LNUM)
                uerror ("bad parameter of .align");
            reorder_flush();
            align (intval);
            break;
        case LASCII:
            reorder_flush();
            makeascii ();
            break;
        case LGLOBL:
            /* .globl name, ... */
            for (;;) {
                clex = getlex (&cval);
                if (clex != LNAME)
                    uerror ("bad parameter of .globl");
                cval = lookname();
                if (stab[cval].n_type & N_LOC)
                    uerror ("local name redefined as global");
                stab[cval].n_type |= N_EXT;
                clex = getlex (&cval);
                if (clex != ',') {
                    ungetlex (clex, cval);
                    break;
                }
            }
            break;
        case LLOCAL:
            /* .local name, ... */
            for (;;) {
                clex = getlex (&cval);
                if (clex != LNAME)
                    uerror ("bad parameter of .local");
                cval = lookname();
                if (stab[cval].n_type & N_EXT)
                    uerror ("global name redefined as local");
                stab[cval].n_type |= N_LOC;
                clex = getlex (&cval);
                if (clex != ',') {
                    ungetlex (clex, cval);
                    break;
                }
            }
            break;
        case LWEAK:
            /* .weak name */
            for (;;) {
                clex = getlex (&cval);
                if (clex != LNAME)
                    uerror ("bad parameter of .weak");
                cval = lookname();
                stab[cval].n_type |= N_WEAK;
                clex = getlex (&cval);
                if (clex != ',') {
                    ungetlex (clex, cval);
                    break;
                }
            }
            break;
        case LCOMM:
            /* .comm name,len */
            if (getlex (&cval) != LNAME)
                uerror ("bad parameter of .comm");
            cval = lookname();
            if (stab[cval].n_type != N_UNDF &&
                stab[cval].n_type != N_LOC &&
                (stab[cval].n_type & N_TYPE) != N_COMM)
                uerror ("name already defined");
            if (stab[cval].n_type & N_LOC)
                stab[cval].n_type = N_COMM;
            else
                stab[cval].n_type = N_EXT | N_COMM;
            clex = getlex (&tval);
            if (clex == ',') {
                getexpr (&tval);
                if (tval != SABS)
                    uerror ("bad length of .comm");
            } else {
                ungetlex (clex, cval);
                intval = 1;
            }
            stab[cval].n_value = intval;
            clex = getlex (&cval);
            if (clex != ',') {
                ungetlex (clex, cval);
                break;
            }
            getexpr (&tval);
            if (tval != SABS)
                uerror ("bad .comm alignment");
            break;
        case LFILE:
            /* .file line filename */
            if (getlex (&cval) != LNUM)
                uerror ("bad parameter of .file");
            skipstring();
            break;
        case LIDENT:
            /* .ident string */
            skipstring();
            break;
        case LSECTION:
            /* .section name[,"flags"[,type[,entsize]]] */
            clex = getlex (&cval);
            if (clex != LNAME && clex != LBSS)
                uerror ("bad name of .section");
            setsection();
            clex = getlex (&cval);
            if (clex != ',') {
                ungetlex (clex, cval);
                break;
            }
            skipstring();
            clex = getlex (&cval);
            if (clex != ',') {
                ungetlex (clex, cval);
                break;
            }
            if (getlex (&cval) != LSECTYPE)
                uerror ("bad type of .section");
            clex = getlex (&cval);
            if (clex != ',') {
                ungetlex (clex, cval);
                break;
            }
            if (getlex (&cval) != LNUM)
                uerror ("bad entry size of .section");
            break;
        case LPREVIOUS:
            /* .previous - ignore */
            break;
        case LGNUATTR:
            /* .gnu_attribute num[,num] */
            if (getlex (&cval) != LNUM)
                uerror ("bad parameter of .gnu_attribute");
            clex = getlex (&cval);
            if (clex != ',' || getlex (&cval) != LNUM)
                uerror ("bad parameter of .gnu_attribute");
            break;
        case LSET:
            /* .set option */
            if (getlex (&cval) != LNAME)
                uerror ("bad parameter of .set");
            setoption();
            break;
        case LENT:
            /* .ent name */
            clex = getlex (&cval);
            if (clex != LNAME)
                uerror ("bad parameter of .ent");
            cval = lookname();
            break;
        case LEND:
            /* .end name */
            clex = getlex (&cval);
            if (clex != LNAME)
                uerror ("bad parameter of .end");
            cval = lookname();
            break;
        case LTYPE:
            /* .type name,type */
            if (getlex (&cval) != LNAME)
                uerror ("bad name of .type");
            clex = getlex (&cval);
            if (clex != ',') {
                ungetlex (clex, cval);
                break;
            }
            if (getlex (&cval) != LSYMTYPE)
                uerror ("bad type of .type");
            break;
        case LFRAME:
            /* .frame reg,num,reg */
            if (getlex (&cval) != LREG)
                uerror ("bad register of .frame");
            clex = getlex (&cval);
            if (clex != ',' || getlex (&cval) != LNUM)
                uerror ("bad parameter of .frame");
            clex = getlex (&cval);
            if (clex != ',' || getlex (&cval) != LREG)
                uerror ("bad register of .frame");
            break;
        case LMASK:
            /* .mask mask,expr */
            if (getlex (&cval) != LNUM)
                uerror ("bad mask of .mask");
            clex = getlex (&cval);
            if (clex != ',')
                uerror ("bad parameter of .mask");
            getexpr (&cval);
            if (cval != SABS)
                uerror ("bad expression of .mask");
            break;
        case LFMASK:
            /* .fmask mask,expr */
            if (getlex (&cval) != LNUM)
                uerror ("bad mask of .fmask");
            clex = getlex (&cval);
            if (clex != ',')
                uerror ("bad parameter of .fmask");
            getexpr (&cval);
            if (cval != SABS)
                uerror ("bad expression of .fmask");
            break;
        case LSIZE:
            /* .size name,expr */
            if (getlex (&cval) != LNAME)
                uerror ("bad name of .size");
            clex = getlex (&cval);
            if (clex != ',') {
                ungetlex (clex, cval);
                break;
            }
            nbytes = getexpr (&csegm);
            if (csegm != SABS)
                uerror ("bad value of .size");
            break;
        default:
            uerror ("bad syntax");
        }
        clex = getlex (&cval);
        if (clex != LEOL) {
            if (clex == LEOF)
                return;
            uerror ("bad instruction arguments");
        }
    }
}

/*
 * Find the relative label address,
 * by the reference address and the label number.
 * Backward references have negative label numbers.
 */
int findlabel (int addr, int sym)
{
    struct labeltab *p;

    if (sym < 0) {
        /* Backward reference. */
        for (p=labeltab+nlabels-1; p>=labeltab; --p) {
            if (p->value <= addr && p->num == -sym) {
                return p->value;
            }
        }
        uerror ("undefined label %db at address %d", -sym, addr);
    } else {
        /* Forward reference. */
        for (p=labeltab; p<labeltab+nlabels; ++p) {
            if (p->value > addr && p->num == sym) {
                return p->value;
            }
        }
        uerror ("undefined label %df at address %d", sym, addr);
    }
    return 0;
}

void middle ()
{
    register int i, snum;

    stlength = 0;
    for (snum=0, i=0; i<stabfree; i++) {
        /* Without -u option, undefined symbol is considered external */
        if (stab[i].n_type == N_UNDF) {
            if (uflag)
                uerror ("name undefined", stab[i].n_name);
            stab[i].n_type |= N_EXT;
        }
        if (xflags)
            newindex[i] = snum;
        if (! xflags || (stab[i].n_type & N_EXT) ||
            (Xflag && ! IS_LOCAL(&stab[i])))
        {
            stlength += 2 + WORDSZ + stab[i].n_len;
            snum++;
        }
    }
    stalign = WORDSZ - stlength % WORDSZ;
    stlength += stalign;
    line = 0;
}

void makeheader (rtsize, rdsize)
{
    struct exec hdr;

    hdr.a_magic = RMAGIC;
    hdr.a_text = count [STEXT];
    hdr.a_data = count [SDATA] + count [SSTRNG];
    hdr.a_bss = count [SBSS];
    hdr.a_reltext = rtsize;
    hdr.a_reldata = rdsize;
    hdr.a_syms = stlength;
    hdr.a_entry = 0;
    fseek (stdout, 0, 0);
    fputhdr (&hdr, stdout);
}

unsigned relocate (opcode, offset, relinfo)
    register unsigned opcode, offset;
    register struct reloc *relinfo;
{
    switch (relinfo->flags & RFMASK) {
    case RBYTE32:                       /* 32 bits of byte address */
        opcode += offset;
        break;
    case RBYTE16:                       /* low 16 bits of byte address */
        offset += opcode & 0xffff;
        opcode &= ~0xffff;
        opcode |= offset & 0xffff;
        break;
    case RHIGH16:                       /* high 16 bits of byte address */
        offset += (opcode & 0xffff) << 16;
        offset += relinfo->offset;
        opcode &= ~0xffff;
        opcode |= (offset >> 16) & 0xffff;
        relinfo->offset = offset & 0xffff;
        break;
    case RHIGH16S:                      /* high 16 bits of byte address */
        offset += (opcode & 0xffff) << 16;
        offset += (signed short) relinfo->offset;
        opcode &= ~0xffff;
        opcode |= ((offset + 0x8000) >> 16) & 0xffff;
        relinfo->offset = offset & 0xffff;
        break;
    case RWORD16:                       /* 16 bits of relative word address */
        uerror ("bad relative relocation: opcode %08x, relinfo %02x", opcode, relinfo->flags);
        break;
    case RWORD26:                       /* 26 bits of word address */
        offset += (opcode & 0x3ffffff) << 2;
        opcode &= ~0x3ffffff;
        opcode |= (offset >> 2) & 0x3ffffff;
        break;
    }
    return (opcode);
}

unsigned makeword (opcode, relinfo, offset)
    register unsigned opcode, offset;
    register struct reloc *relinfo;
{
    struct nlist *sym;
    unsigned value;

    switch (relinfo->flags & RSMASK) {
    case RABS:
        break;
    case RTEXT:
        opcode = relocate (opcode, tbase, relinfo);
        break;
    case RDATA:
        opcode = relocate (opcode, dbase, relinfo);
        break;
    case RSTRNG:
        opcode = relocate (opcode, adbase, relinfo);
        break;
    case RBSS:
        opcode = relocate (opcode, bbase, relinfo);
        break;
    case REXT:
        if (relinfo->index >= RLAB_OFFSET - RLAB_MAXVAL) {
            /* Relative label.
             * Change relocation to segment type. */
            sym = 0;
            value = findlabel (offset, relinfo->index - RLAB_OFFSET);
            relinfo->flags &= RGPREL | RFMASK;
            relinfo->flags |= segmrel[segm];
        } else {
            /* Symbol name. */
            sym = &stab[relinfo->index];
            if (sym->n_type == N_EXT+N_UNDF || sym->n_type == N_EXT+N_COMM)
                return opcode;
            value = sym->n_value;
        }

        switch (relinfo->flags & RFMASK) {
        case RWORD16:
            /* Relative word address.
             * Change relocation to absolute. */
            if (sym && (sym->n_type & N_TYPE) != segmtype[segm])
                uerror ("%s: bad segment for relative relocation, offset %u",
                    sym->n_name, offset);
            offset = value - offset - 4;
            if (segm == SDATA)
                offset -= dbase;
            else if (segm == SSTRNG)
                offset -= adbase;
            offset += (opcode & 0xffff) << 2;
            opcode &= ~0xffff;
            opcode |= (offset >> 2) & 0xffff;
            relinfo->flags = RABS;
            return opcode;
        case RHIGH16:
            value += relinfo->offset;
            break;
        case RHIGH16S:
            value += (signed short) relinfo->offset;
            break;
        }
        opcode = relocate (opcode, value, relinfo);
        break;
    }
    return opcode;
}

void pass2 ()
{
    register int i;
    register unsigned h;

    tbase = 0;
    dbase = tbase + count[STEXT];
    adbase = dbase + count[SDATA];
    bbase = adbase + count[SSTRNG];

    /* Adjust indexes in symbol name */
    for (i=0; i<stabfree; i++) {
        switch (stab[i].n_type & N_TYPE) {
        case N_UNDF:
        case N_ABS:
            break;
        case N_TEXT:
            stab[i].n_value += tbase;
            break;
        case N_DATA:
            stab[i].n_value += dbase;
            break;
        case N_STRNG:
            stab[i].n_value += adbase;
            stab[i].n_type += N_DATA - N_STRNG;
            break;
        case N_BSS:
            stab[i].n_value += bbase;
            break;
        }
    }
    fseek (stdout, sizeof(struct exec), 0);
    for (segm=STEXT; segm<SBSS; segm++) {
        /* Need to rewrite a relocation file. */
        FILE *rfd = fopen (tfilename, "w+");
        if (! rfd)
            uerror ("cannot open %s", tfilename);
        unlink (tfilename);

        rewind (sfile [segm]);
        rewind (rfile [segm]);
        for (h=0; h<count[segm]; h+=WORDSZ) {
            struct reloc relinfo;
            unsigned word = fgetword (sfile[segm]);
            fgetrel (rfile[segm], &relinfo);
            word = makeword (word, &relinfo, h);
            fputword (word, stdout);
            fputrel (&relinfo, rfd);
        }
        fclose (rfile [segm]);
        rfile [segm] = rfd;
    }
}

/*
 * Convert symbol type to relocation type.
 */
int typerel (t)
{
    switch (t & N_TYPE) {
    case N_ABS:     return (RABS);
    case N_TEXT:    return (RTEXT);
    case N_DATA:    return (RDATA);
    case N_BSS:     return (RBSS);
    case N_STRNG:   return (RDATA);
    case N_UNDF:
    case N_COMM:
    case N_FN:
    default:        return (0);
    }
}

/*
 * Relocate a relocation info word.
 * Remap symbol indexes.
 * Put string pseudo-section to data section.
 */
void relrel (relinfo)
    register struct reloc *relinfo;
{
    register unsigned type;

    switch ((int) relinfo->flags & REXT) {
    case RSTRNG:
        relinfo->flags &= ~RSMASK;
        relinfo->flags |= RDATA;
        break;
    case REXT:
        type = stab[relinfo->index].n_type;
        if (type == N_EXT+N_UNDF || type == N_EXT+N_COMM)
        {
            /* Reindexing */
            if (xflags)
                relinfo->index = newindex [relinfo->index];
        } else {
            relinfo->flags &= ~RSMASK;
            relinfo->flags |= typerel (type);
        }
        break;
    }
}

/*
 * Emit a relocation info for a given segment.
 * Copy it from scratch file to output.
 * Return a size of relocation data in bytes.
 */
unsigned makereloc (s)
    register int s;
{
    register unsigned i, nbytes;
    struct reloc relinfo;

    if (count [s] <= 0)
        return 0;
    rewind (rfile [s]);
    nbytes = 0;
    for (i=0; i<count[s]; i+=WORDSZ) {
        fgetrel (rfile[s], &relinfo);
        relrel (&relinfo);
        nbytes += fputrel (&relinfo, stdout);
    }
    while (nbytes % WORDSZ) {
        putchar (0);
        nbytes++;
    }
    return nbytes;
}

void makesymtab ()
{
    register int i;

    for (i=0; i<stabfree; i++) {
        if (! xflags || (stab[i].n_type & N_EXT) ||
            (Xflag && stab[i].n_name[0] != 'L'))
        {
            fputsym (&stab[i], stdout);
        }
    }
    while (stalign--)
        putchar (0);
}

void usage ()
{
    fprintf (stderr, "Usage:\n");
    fprintf (stderr, "  as [-uxX] [-o outfile] [infile]\n");
    fprintf (stderr, "Options:\n");
    fprintf (stderr, "  -o filename     Set output file name, default stdout\n");
    fprintf (stderr, "  -u              Treat undefined names as error\n");
    fprintf (stderr, "  -x              Discard local symbols\n");
    fprintf (stderr, "  -X              Discard locals starting with 'L' or '.'\n");
    exit (1);
}

int main (argc, argv)
    register char *argv[];
{
    register int i;
    register char *cp;
    int ofile = 0;
    unsigned rtsize, rdsize;

    /*
     * Parse options.
     */
    for (i=1; i<argc; i++) {
        switch (argv[i][0]) {
        case '-':
            for (cp=argv[i]+1; *cp; cp++) {
                switch (*cp) {
                case 'X':       /* strip L* and .* locals */
                    Xflag++;
                case 'x':       /* strip local symbols */
                    xflags++;
                    break;
                case 'u':       /* treat undefines as error */
                    uflag++;
                    break;
                case 'o':       /* output file name */
                    if (ofile)
                        uerror ("too many -o flags");
                    ofile = 1;
                    if (cp [1]) {
                        /* -ofile */
                        outfile = cp+1;
                        while (*++cp);
                        --cp;
                    } else if (i+1 < argc)
                        /* -o file */
                        outfile = argv[++i];
                    break;
                case 'v':       /* verbose mode */
                    // TODO
                    break;
                case 'g':       /* debug mode */
                    // TODO
                    break;
                case 'I':       /* include dir */
                    // TODO
                    while (*++cp);
                    --cp;
                    break;
                case 'O':       /* optimization level */
                    // TODO
                    while (*++cp);
                    --cp;
                    break;
                case '-':       /* long option - skip */
                    while (*++cp);
                    --cp;
                    break;
                case 'n':       /* -no-xyz option - skip */
                    while (*++cp);
                    --cp;
                    break;
                case 'm':       /* -mips32r2, -mabi=32 - ignore */
                    while (*++cp);
                    --cp;
                    break;
                case 'E':       /* -EL, -EB - endianness */
                    if (cp[1] != 'L')
                        uerror ("only little endian is supported");
                    while (*++cp);
                    --cp;
                    break;
                default:
                    fprintf (stderr, "Unknown option: %s\n", cp);
                    usage();
                }
            }
            break;
        default:
            if (infile)
                uerror ("too many input files");
            infile = argv[i];
            break;
        }
    }
    if (! infile && isatty(0))
        usage();

    /*
     * Setup input-output.
     */
    if (infile && ! freopen (infile, "r", stdin))
        uerror ("cannot open %s", infile);
    if (! freopen (outfile, "w", stdout))
        uerror ("cannot open %s", outfile);

    startup ();                         /* Open temporary files */
    hashinit ();                        /* Initialize hash tables */
    pass1 ();                           /* First pass */
    middle ();                          /* Prepare symbol table */
    pass2 ();                           /* Second pass */
    rtsize = makereloc (STEXT);         /* Emit relocation info */
    rdsize = makereloc (SDATA);
    rdsize += makereloc (SSTRNG);
    makesymtab ();                      /* Emit symbol table */
    makeheader (rtsize, rdsize);        /* Write a.out header */
    return 0;
}
