root/usr.sbin/gspa/gspa/gsp_inst.c
/*      $NetBSD: gsp_inst.c,v 1.12 2025/11/24 08:04:28 nia Exp $        */
/*
 * TMS34010 GSP assembler - Instruction encoding
 *
 * Copyright (c) 1993 Paul Mackerras.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: gsp_inst.c,v 1.12 2025/11/24 08:04:28 nia Exp $");
#endif

#include <string.h>
#include <assert.h>
#include "gsp_ass.h"
#include "gsp_code.h"

struct inst {
        const char *opname;
        u_int16_t opcode;
        u_char  class;          /* instruction class + flags */
        u_char  optypes[4];     /* permissible operand classes */
};

/* Values for flags in class field */
#define NOIMM16 0x80            /* can't have 16-bit immediate */
#define K32     0x40            /* values 1..32 for K-type constant */
#define IMMCOM  0x20            /* immediate value is complemented */
#define IMMNEG  0x80            /* immediate value is negated */
#define NODSJS  0x80            /* can't use 5-bit branch offset */
#define DSHORT  0x40            /* must use 5-bit offset */

#define CLASS   0x1F

/* Values for class */
#define NOP     0               /* no operands */
#define ONEREG  1               /* reg */
#define TWOREG  2               /* reg, reg */
#define DYADIC  3               /* immediate or reg, reg */
#define ADD     (DYADIC|K32)
#define SUB     (DYADIC|IMMCOM|K32)
#define CMP     (DYADIC|IMMCOM)
#define AND     (DYADIC|NOIMM16|IMMCOM)
#define OR      (DYADIC|NOIMM16)
#define IMMREG  4               /* immediate, reg */
#define IMMREGC (IMMREG|IMMCOM)
#define LIMREG  (IMMREG|NOIMM16)
#define LIMREGC (LIMREG|IMMCOM)
#define KREG    5               /* short immediate, reg */
#define K32REG  (KREG|K32)
#define SRA     (KREG|IMMNEG)
#define BTST    (KREG|IMMCOM)
#define CALL    6               /* reg or address */
#define JUMP    7
#define CLR     8               /* reg appears twice in encoding */
#define DSJ     9
#define DSJEQ   (DSJ|NODSJS)
#define DSJS    (DSJ|DSHORT)
#define EXGF    10
#define SETF    11
#define FILL    12
#define LINE    13
#define PIXBLT  14
#define PIXT    15
#define MMFM    16
#define MOVB    17
#define MOVE    18
#define MOVEK   (MOVE|K32)
#define RETS    19
#define PSEUDO  20

/* Composite operand classes */
#define EXREG   (REG|EXPR)
#define EAREG   (REG|EA)
#define EXAREG  (REG|EXPR|EA)
#define OPTOPRN 0x80            /* signals optional operand */
#define SPEC    (0x10|EXPR)             /* field or length specifier */
#define OPTREG  (OPTOPRN|REG)
#define OPTEXPR (OPTOPRN|EXPR)
#define OPTSPEC (OPTOPRN|SPEC)
#define OPTXREG (OPTOPRN|EXREG)

#define MIN(a, b)       ((a) < (b)? (a): (b))

/*
 * N.B. This list must be sorted in order of opname.
 */
struct inst instructions[] = {
        {".BLKB", BLKB, PSEUDO, {0,     0,      0,      0}},
        {".BLKL", BLKL, PSEUDO, {0,     0,      0,      0}},
        {".BLKW", BLKW, PSEUDO, {0,     0,      0,      0}},
#ifdef EQU
        {".EQU", EQU,   PSEUDO, {0,     0,      0,      0}},
#endif
        {".INCLUDE", INCL, PSEUDO, {0,  0,      0,      0}},
        {".LONG", LONG, PSEUDO, {0,     0,      0,      0}},
        {".ORG", ORG,   PSEUDO, {0,     0,      0,      0}},
        {".START",START,PSEUDO, {0,     0,      0,      0}},
        {".WORD",WORD,  PSEUDO, {0,     0,      0,      0}},
        {"ABS", 0x0380, ONEREG, {REG,   0,      0,      0}},
        {"ADD", 0x4000, ADD,    {EXREG, REG,    OPTSPEC,0}},
        {"ADDC",0x4200, TWOREG, {REG,   REG,    0,      0}},
        {"ADDI",0x0B20, IMMREG, {EXPR,  REG,    OPTSPEC,0}},
        {"ADDK",0x1000, K32REG, {EXPR,  REG,    0,      0}},
        {"ADDXY",0xE000,TWOREG, {REG,   REG,    0,      0}},
        {"AND", 0x5000, AND,    {EXREG, REG,    0,      0}},
        {"ANDI",0x0B80, LIMREGC,{EXPR,  REG,    0,      0}},
        {"ANDN",0x5200, OR,     {EXREG, REG,    0,      0}},
        {"ANDNI",0x0B80,LIMREG, {EXPR,  REG,    0,      0}},
        {"BTST",0x1C00, BTST,   {EXREG, REG,    0,      0}},
        {"CALL",0x0920, CALL,   {EXREG, 0,      0,      0}},
        {"CALLA",0x0D5F,CALL,   {EXPR,  0,      0,      0}},
        {"CALLR",0x0D3F,CALL,   {EXPR,  0,      0,      0}},
        {"CLR", 0x5600, CLR,    {REG,   0,      0,      0}},
        {"CLRC",0x0320, NOP,    {0,     0,      0,      0}},
        {"CMP", 0x4800, CMP,    {EXREG, REG,    OPTSPEC,0}},
        {"CMPI",0x0B60, IMMREGC,{EXPR,  REG,    OPTSPEC,0}},
        {"CMPXY",0xE400,TWOREG, {REG,   REG,    0,      0}},
        {"CPW", 0xE600, TWOREG, {REG,   REG,    0,      0}},
        {"CVXYL",0xE800,TWOREG, {REG,   REG,    0,      0}},
        {"DEC", 0x1420, ONEREG, {REG,   0,      0,      0}},
        {"DINT",0x0360, NOP,    {0,     0,      0,      0}},
        {"DIVS",0x5800, TWOREG, {REG,   REG,    0,      0}},
        {"DIVU",0x5A00, TWOREG, {REG,   REG,    0,      0}},
        {"DRAV",0xF600, TWOREG, {REG,   REG,    0,      0}},
        {"DSJ", 0x0D80, DSJ,    {REG,   EXPR,   0,      0}},
        {"DSJEQ",0x0DA0,DSJEQ,  {REG,   EXPR,   0,      0}},
        {"DSJNE",0x0DC0,DSJEQ,  {REG,   EXPR,   0,      0}},
        {"DSJS",0x3800, DSJS,   {REG,   EXPR,   0,      0}},
        {"EINT",0x0D60, NOP,    {0,     0,      0,      0}},
        {"EMU", 0x0100, NOP,    {0,     0,      0,      0}},
        {"EXGF",0xD500, EXGF,   {REG,   OPTSPEC,0,      0}},
        {"EXGPC",0x0120,ONEREG, {REG,   0,      0,      0}},
        {"FILL",0x0FC0, FILL,   {SPEC,  0,      0,      0}},
        {"GETPC",0x0140,ONEREG, {REG,   0,      0,      0}},
        {"GETST",0x0180,ONEREG, {REG,   0,      0,      0}},
        {"INC", 0x1020, ONEREG, {REG,   0,      0,      0}},
        {"JAB", 0xC880, JUMP,   {EXPR,  0,      0,      0}},
        {"JAC", 0xC880, JUMP,   {EXPR,  0,      0,      0}},
        {"JAEQ",0xCA80, JUMP,   {EXPR,  0,      0,      0}},
        {"JAGE",0xC580, JUMP,   {EXPR,  0,      0,      0}},
        {"JAGT",0xC780, JUMP,   {EXPR,  0,      0,      0}},
        {"JAHI",0xC380, JUMP,   {EXPR,  0,      0,      0}},
        {"JAHS",0xC980, JUMP,   {EXPR,  0,      0,      0}},
        {"JALE",0xC680, JUMP,   {EXPR,  0,      0,      0}},
        {"JALO",0xC880, JUMP,   {EXPR,  0,      0,      0}},
        {"JALS",0xC280, JUMP,   {EXPR,  0,      0,      0}},
        {"JALT",0xC480, JUMP,   {EXPR,  0,      0,      0}},
        {"JAN", 0xCE80, JUMP,   {EXPR,  0,      0,      0}},
        {"JANB",0xC980, JUMP,   {EXPR,  0,      0,      0}},
        {"JANC",0xC980, JUMP,   {EXPR,  0,      0,      0}},
        {"JANE",0xCB80, JUMP,   {EXPR,  0,      0,      0}},
        {"JANN",0xCF80, JUMP,   {EXPR,  0,      0,      0}},
        {"JANV",0xCD80, JUMP,   {EXPR,  0,      0,      0}},
        {"JANZ",0xCB80, JUMP,   {EXPR,  0,      0,      0}},
        {"JAP", 0xC180, JUMP,   {EXPR,  0,      0,      0}},
        {"JAUC",0xC080, JUMP,   {EXPR,  0,      0,      0}},
        {"JAV", 0xCC80, JUMP,   {EXPR,  0,      0,      0}},
        {"JAZ", 0xCA80, JUMP,   {EXPR,  0,      0,      0}},
        {"JRB", 0xC800, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRC", 0xC800, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JREQ",0xCA00, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRGE",0xC500, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRGT",0xC700, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRHI",0xC300, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRHS",0xC900, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRLE",0xC600, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRLO",0xC800, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRLS",0xC200, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRLT",0xC400, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRN", 0xCE00, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRNB",0xC900, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRNC",0xC900, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRNE",0xCB00, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRNN",0xCF00, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRNV",0xCD00, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRNZ",0xCB00, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRP", 0xC100, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRUC",0xC000, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRV", 0xCC00, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JRZ", 0xCA00, JUMP,   {EXPR,  OPTSPEC,0,      0}},
        {"JUMP",0x0160, JUMP,   {EXREG, OPTSPEC,0,      0}},
        {"LINE",0xDF1A, LINE,   {SPEC,  0,      0,      0}},
        {"LMO", 0x6A00, TWOREG, {REG,   REG,    0,      0}},
        {"MMFM",0x09A0, MMFM,   {REG,   OPTXREG,OPTREG, OPTREG}},
        {"MMTM",0x0980, MMFM,   {REG,   OPTXREG,OPTREG, OPTREG}},
        {"MODS",0x6C00, TWOREG, {REG,   REG,    0,      0}},
        {"MODU",0x6E00, TWOREG, {REG,   REG,    0,      0}},
        {"MOVB",0,      MOVB,   {EAREG, EAREG,  0,      0}},
        {"MOVE",0x4C00, MOVEK,  {EXAREG,EAREG,  OPTSPEC,0}},
        {"MOVI",0x09E0, IMMREG, {EXPR,  REG,    OPTSPEC,0}},
        {"MOVK",0x1800, K32REG, {EXPR,  REG,    0,      0}},
        {"MOVX",0xEC00, TWOREG, {REG,   REG,    0,      0}},
        {"MOVY",0xEE00, TWOREG, {REG,   REG,    0,      0}},
        {"MPYS",0x5C00, TWOREG, {REG,   REG,    0,      0}},
        {"MPYU",0x5E00, TWOREG, {REG,   REG,    0,      0}},
        {"NEG", 0x03A0, ONEREG, {REG,   0,      0,      0}},
        {"NEGB",0x03C0, ONEREG, {REG,   0,      0,      0}},
        {"NOP", 0x0300, NOP,    {0,     0,      0,      0}},
        {"NOT", 0x03E0, ONEREG, {REG,   0,      0,      0}},
        {"OR",  0x5400, OR,     {EXREG, REG,    0,      0}},
        {"ORI", 0x0BA0, LIMREG, {EXPR,  REG,    0,      0}},
        {"PIXBLT",0x0F00,PIXBLT,{SPEC,  SPEC,   0,      0}},
        {"PIXT",0,      PIXT,   {EAREG, EAREG,  0,      0}},
        {"POPST",0x01C0,NOP,    {0,     0,      0,      0}},
        {"PUSHST",0x01E0,NOP,   {0,     0,      0,      0}},
        {"PUTST",0x01A0,ONEREG, {REG,   0,      0,      0}},
        {"RETI",0x0940, NOP,    {0,     0,      0,      0}},
        {"RETS",0x0960, RETS,   {OPTEXPR,0,     0,      0}},
        {"REV", 0x0020, ONEREG, {REG,   0,      0,      0}},
        {"RL",  0x3000, KREG,   {EXREG, REG,    0,      0}},
        {"SETC",0x0DE0, NOP,    {0,     0,      0,      0}},
        {"SETF",0x0540, SETF,   {EXPR,  EXPR,   OPTSPEC,0}},
        {"SEXT",0x0500, EXGF,   {REG,   OPTSPEC,0,      0}},
        {"SLA", 0x2000, KREG,   {EXREG, REG,    0,      0}},
        {"SLL", 0x2400, KREG,   {EXREG, REG,    0,      0}},
        {"SRA", 0x2800, SRA,    {EXREG, REG,    0,      0}},
        {"SRL", 0x2C00, SRA,    {EXREG, REG,    0,      0}},
        {"SUB", 0x4400, SUB,    {EXREG, REG,    OPTSPEC,0}},
        {"SUBB",0x4600, TWOREG, {REG,   REG,    0,      0}},
        {"SUBI",0x0D00, IMMREGC,{EXPR,  REG,    OPTSPEC,0}},
        {"SUBK",0x1400, K32REG, {EXPR,  REG,    0,      0}},
        {"SUBXY",0xE200,TWOREG, {REG,   REG,    0,      0}},
        {"TRAP",0x0900, RETS,   {EXPR,  0,      0,      0}},
        {"XOR", 0x5600, OR,     {EXREG, REG,    0,      0}},
        {"XORI",0x0BC0, LIMREG, {EXPR,  REG,    0,      0}},
        {"ZEXT",0x0520, EXGF,   {REG,   OPTSPEC,0,      0}},
        {NULL,  0,      0,      {0,     0,      0,      0}}
};

int check_spec(int spec, const char *valid, const char *what);
void do_statement(char *opcode, operand operands);
int encode_instr(struct inst *ip, operand ops, int *spec, u_int16_t *iwords);
int specifier(operand op);

void
statement(char *opcode, operand operands)
{
        do_statement(opcode, operands);
        free_operands(operands);
}

void
do_statement(char *opcode, operand operands)
{
        struct inst *ip;
        int i, req;
        unsigned nop;
        operand op;
        int spec[3];
        u_int16_t iwords[6];

        ucasify(opcode);
        i = 1;
        for( ip = instructions; ip->opname != NULL; ++ip )
                if( opcode[0] == ip->opname[0] ){
                        i = strcmp(opcode, ip->opname);
                        if( i <= 0 )
                                break;
                }
        if( i != 0 ){
                perr("Unknown instruction code %s", opcode);
                return;
        }
        if( ip->class == PSEUDO ){
                pseudo(ip->opcode, operands);
                return;
        }

        /* Check correspondence of operands with instruction requirements */
        nop = 0;
        spec[0] = spec[1] = spec[2] = 0;
        for( op = operands; op != NULL; op = op->next ){
                req = ip->optypes[MIN(nop, 3)];
                if( req == 0 )
                        break;
                if( (op->type & req) == 0 ){
                        perr("Inappropriate type for operand %u", nop+1);
                        return;
                }
                if( (req & ~OPTOPRN) == SPEC ) {
                        if (nop >= sizeof(spec) / sizeof(spec[0])) {
                                perr("Spec out of bounds");
                                return;
                        }
                        /* operand is a field/type/length specifier */
                        spec[nop] = specifier(op);
                }
                ++nop;
        }
        if( nop < 4 && ip->optypes[nop] != 0
           && (ip->optypes[nop] & OPTOPRN) == 0 ){
                perr("Insufficient operands");
                return;
        }
        if( op != NULL )
                perr("Extra operands ignored");

        i = encode_instr(ip, operands, spec, iwords);

        /* Pass 1 processing */
        if( !pass2 ){
                /* for pass 1, just work out the instruction size */
/*              printf("pc = %#x, size = %d\n", pc, i); */
                pc += i << 4;
                return;
        }

        /* Pass 2 processing */
        if( i > 0 )
                putcode(iwords, i);
}

const char *specs[] = { "B", "L", "W", "XY", NULL };

int
specifier(operand op)
{
        const char **sl;
        expr e;
        char sp[4];

        if( op->type != EXPR )
                return '?';
        e = op->op_u.value;
        if( e->e_op == CONST ){
                if( e->e_val == 0 || e->e_val == 1 )
                        return e->e_val + '0';
        } else if( e->e_op == SYM ){
                if( strlen(e->e_sym->name) > 2 )
                        return '?';
                strcpy(sp, e->e_sym->name);
                ucasify(sp);
                for( sl = specs; *sl != NULL; ++sl )
                        if( strcmp(*sl, sp) == 0 )
                                return sp[0];
        }
        return '?';
}

int
check_spec(int spec, const char *valid, const char *what)
{
        char *p;

        if( spec == 0 )
                return 0;
        p = strchr(valid, spec);
        if( p == NULL ){
                perr("Invalid %s specifier", what);
                return 0;
        }
        return p - valid;
}

u_int16_t code_to_imm[] = {
        0x0B20,         /* ADDI */
        0,
        0x0D00,         /* SUBI */
        0,
        0x0B60,         /* CMPI */
        0,
        0x09E0,         /* MOVI */
        0,
        0x0B80,         /* ANDI */
        0x0B80,         /* ANDNI */
        0x0BA0,         /* ORI */
        0x0BC0,         /* XORI */
};

/* Opcodes for MOVE instruction */
u_int16_t move_opc[7][7] = {
/*                              Source */
/*      Reg     *Reg    *Reg+   *-Reg   *Reg.XY *Reg(n) @addr     Dest */
        {0x4C00,0x8400, 0x9400, 0xA400, 0,      0xB400, 0x05A0}, /* R */
        {0x8000,0x8800, 0,      0,      0,      0,      0},     /* *R */
        {0x9000,0,      0x9800, 0,      0,      0xD000, 0xD400}, /* *R+ */
        {0xA000,0,      0,      0xA800, 0,      0,      0},     /* *-R */
        {0,     0,      0,      0,      0,      0,      0},     /* *R.XY */
        {0xB000,0,      0,      0,      0,      0xB800, 0},     /* *R(n) */
        {0x0580,0,      0,      0,      0,      0,      0x05C0} /* @adr */
};

/* Opcodes for MOVB instruction */
u_int16_t movb_opc[7][7] = {
/*                              Source */
/*      Reg     *Reg    *Reg+   *-Reg   *Reg.XY *Reg(n) @addr     Dest */
        {0,     0x8E00, 0,      0,      0,      0xAE00, 0x07E0},/* R */
        {0x8C00,0x9C00, 0,      0,      0,      0,      0},     /* *R */
        {0,     0,      0,      0,      0,      0,      0},     /* *R+ */
        {0,     0,      0,      0,      0,      0,      0},     /* *-R */
        {0,     0,      0,      0,      0,      0,      0},     /* *R.XY */
        {0xAC00,0,      0,      0,      0,      0xBC00, 0},     /* *R(n) */
        {0x05E0,0,      0,      0,      0,      0,      0x0340} /* @adr */
};

/* Opcodes for PIXT instruction */
u_int16_t pixt_opc[7][7] = {
/*                              Source */
/*      Reg     *Reg    *Reg+   *-Reg   *Reg.XY *Reg(n) @addr     Dest */
        {0,     0xFA00, 0,      0,      0xF200, 0,      0},     /* R */
        {0xF800,0xFC00, 0,      0,      0,      0,      0},     /* *R */
        {0,     0,      0,      0,      0,      0,      0},     /* *R+ */
        {0,     0,      0,      0,      0,      0,      0},     /* *-R */
        {0xF000,0,      0,      0,      0xF400, 0,      0},     /* *R.XY */
        {0,     0,      0,      0,      0,      0,      0},     /* *R(n) */
        {0,     0,      0,      0,      0,      0,      0}      /* @adr */
};

#define USES_REG(op)    ((op)->type == REG \
                         || ((op)->type == EA && (op)->mode != M_ABSOLUTE))
#define USES_EXPR(op)   ((op)->type == EXPR \
                         || ((op)->type == EA && (op)->mode >= M_INDEX))

int
encode_instr(struct inst *ip, operand ops, int *spec, u_int16_t *iwords)
{
        int rs, rd;
        int opc, nw, class, flags, ms, md, off;
        int mask, file, bit, i;
        operand op0, op1;
        unsigned eline[2];
        int32_t val[2];

        rs = rd = 0;
        opc = ip->opcode;
        nw = 1;
        op0 = ops;
        if( op0 != NULL ){
                if( spec[0] == 0 && USES_EXPR(op0) )
                        eval_expr(op0->op_u.value, &val[0], &eline[0]);
                op1 = ops->next;
                if( op1 != NULL && spec[1] == 0 && USES_EXPR(op1) )
                        eval_expr(op1->op_u.value, &val[1], &eline[1]);
        } else
                op1 = NULL;
        class = ip->class & CLASS;
        flags = ip->class & ~CLASS;
        if (class == MOVE && op0 && op1 && op1->type == REG) {
                if (op0->type == REG) {
                        class = DYADIC;
                        if ((op0->reg_no & op1->reg_no & GSPA_REGFILE) == 0) {
                                opc += 0x0200;
                                op1->reg_no ^= GSPA_A0 ^ GSPA_B0;
                        }
                } else if ( op0->type == EXPR )
                        class = DYADIC;
        }
        if( class == DYADIC ){
                /* turn it into TWOREG, IMMREG or KREG */
                if( op0->type == REG ){
                        class = TWOREG;
                } else if( (flags & K32) != 0 && eline[0] <= lineno
                          && spec[2] == 0
                          && 0 < val[0] && val[0] <= 32 ){
                        /* use 5-bit immediate */
                        class = KREG;
                        opc -= 0x3000;
                        if( opc == 0x1C00 )
                                opc = 0x1800;
                        flags &= ~IMMCOM;
                } else {
                        class = IMMREG;
                        opc = code_to_imm[(opc - 0x4000) >> 9];
                }
                if( (class == TWOREG || class == KREG)
                   && spec[2] != 0 && op1->next->next == NULL )
                        perr("Extra operands ignored");
        } else if( class == KREG ){
                if( op0 && op0->type == REG ){
                        class = TWOREG;
                        if( opc < 0x2000 )
                                opc = 0x4A00;   /* BTST */
                        else
                                opc = (opc >> 1) + 0x5000;
                }
        }

        if( op0 != NULL )
                rs = op0->reg_no;
        if( op1 != NULL ){
                rd = op1->reg_no;
                if( USES_REG(op0) && USES_REG(op1) ){
                        if ((rs & rd & GSPA_REGFILE) == 0)
                                perr("Registers must be in the same register file");
                        /* force SP to the file of the other operand */
                        if (rs == GSPA_SP)
                                rs |= rd;
                        if (rd == GSPA_SP)
                                rd |= rs;
                }
        }

        switch( class ){
        case NOP:                       /* no operands */
                break;
        case ONEREG:                    /* reg */
                opc |= rs & 0x1F;
                break;
        case TWOREG:                    /* reg, reg */
                opc |= ((rs & 0x0F) << 5) | (rd & 0x1F);
                break;
        case IMMREG:                    /* immediate, reg */
                opc |= rd & 0x1F;
                if( (flags & IMMCOM) != 0 )
                        val[0] = ~ val[0];
                i = check_spec(spec[2], " WL", "length");
                if( i == 1
                   || (i == 0 && (flags & NOIMM16) == 0 && eline[0] <= lineno
                      && (int16_t)val[0] == val[0] )){
                        if( (int16_t) val[0] != val[0] )
                                perr("Value truncated to 16 bits");
                        opc -= 0x20;
                        if( opc == 0x0CE0 )     /* SUBI,W */
                                opc = 0x0BE0;
                        nw = 2;
                } else {
                        iwords[2] = (val[0] >> 16);
                        nw = 3;
                }
                iwords[1] = val[0];
                break;
        case KREG:                      /* short immediate, reg */
                opc |= rd & 0x1F;
                if( val[0] < 0 || ((flags & K32) == 0 && val[0] > 31)
                   || ((flags & K32) != 0 && val[0] <= 0) || val[0] > 32 )
                        perr("5-bit constant out of range");
                rs = val[0];
                if( (flags & IMMCOM) != 0 )
                        rs = ~rs;
                else if( (flags & IMMNEG) != 0 )
                        rs = -rs;
                opc |= (rs & 0x1F) << 5;
                break;
        case CALL:                      /* reg or address */
                if( op0 && op0->type == REG ){
                        opc |= rs & 0x1F;
                        break;
                }
                off = (int)(val[0] - pc - 0x20) >> 4;
                if( opc == 0x0920 ){            /* CALL */
                        if( eline[0] <= lineno && (int16_t) off == off )
                                opc = 0x0D3F;   /* CALLR */
                        else
                                opc = 0x0D5F;   /* CALLA */
                }
                if( opc == 0x0D3F ){    /* CALLR */
                        if( (int16_t) off != off )
                                perr("Displacement too large");
                        iwords[1] = off;
                        nw = 2;
                } else {                /* CALLA */
                        iwords[1] = val[0];
                        iwords[2] = val[0] >> 16;
                        nw = 3;
                }
                break;
        case JUMP:
                if( op0 && op0->type == REG ){
                        opc |= rs & 0x1F;
                        break;
                }
                off = (int)(val[0] - pc - 0x10) >> 4;
                if( (opc & 0x80) != 0 )         /* JAcc */
                        i = 2;
                else
                        i = check_spec(spec[1], " WL", "length");
                if( opc == 0x0160 ){    /* JUMP */
                        opc = 0xC000;   /* JRUC */
                        if( i == 0 )
                                i = 1;  /* ,W is the default for JUMP */
                }
                switch( i ){
                case 2:         /* JAcc */
                        iwords[1] = val[0];
                        iwords[2] = val[0] >> 16;
                        opc |= 0x80;
                        nw = 3;
                        break;
                case 1:
                        --off;
                        if( (int16_t) off != off )
                                perr("Displacement too large (word)");
                        iwords[1] = off;
                        nw = 2;
                        break;
                default:
                        if( off == 0 || off < -127 || off > 127 )
                                perr("Short displacement too large or 0");
                        opc |= off & 0xFF;
                }
                break;
        case CLR:                       /* reg appears twice in encoding */
                opc |= (rs & 0x1F) | ((rs & 0x0F) << 5);
                break;
        case DSJ:
                off = (int)(val[1] - pc - 0x10) >> 4;
                if( flags == 0 ){       /* DSJ */
                        if( off != 0 && off >= -31 && off <= 31 ){
                                flags = DSHORT;
                                opc = 0x3800;   /* DSJS */
                        }
                }
                if( flags == DSHORT ){
                        if( off == 0 || off < -31 || off > 31 )
                                perr("DSJS displacement too large");
                        if( off > 0 )
                                opc |= (off & 0x1F) << 5;
                        else
                                opc |= 0x400 | ((-off & 0x1F) << 5);
                } else {
                        --off;
                        if( (int16_t) off != off )
                                perr("Displacement too large (word)");
                        iwords[1] = off;
                        nw = 2;
                }
                opc |= rs & 0x1F;
                break;
        case EXGF:
                opc |= rs & 0x1F;
                opc |= check_spec(spec[1], "01", "field") << 9;
                break;
        case SETF:
                rs = val[0];
                rd = val[1];
                if( rs <= 0 || rs > 32 )
                        perr("Field size must be 1..32");
                if( rd != 0 && rd != 1 )
                        perr("Field extension must be 0 or 1");
                opc |= (rs & 0x1F) | ((rd & 1) << 5);
                opc |= check_spec(spec[2], "01", "field") << 9;
                break;
        case FILL:
                opc |= check_spec(spec[0], "LX", "array type") << 5;
                break;
        case LINE:
                opc |= check_spec(spec[0], "01", "algorithm") << 7;
                break;
        case PIXBLT:
                rs = check_spec(spec[0], "LXB", "source array type");
                rd = check_spec(spec[1], "LX", "destination array type");
                opc |= (rs << 6) | (rd << 5);
                break;
        case MMFM:
                opc |= rs & 0xF;
                file = rs & GSPA_REGFILE;
                if( op1 == NULL )
                        mask = 0xFFFF;
                else if( op1->type == REG ){
                        file &= rd;
                        mask = 0;
                        for( ; op1 != NULL; op1 = op1->next ){
                                rd = op1->reg_no;
                                bit = 1 << (~rd & 0xF);
                                if( file != 0 && (file &= rd) == 0 )
                                        perr("Registers must all be in the same file");
                                if( file != 0 && (mask & bit) != 0 )
                                        perr("Register name repeated");
                                mask |= bit;
                        }
                } else {
                        if( val[1] < 0 || val[1] > 0xFFFFL )
                                perr("Mask value out of range");
                        mask = val[1];
                        if( op1->next != NULL )
                                perr("Extra operands ignored");
                }
                if ((file & GSPA_A0 & GSPA_REGFILE) == 0)
                        opc |= 0x10;
                if ((opc & 0x20) != 0) {
                        /* mask reversed for MMFM */
                        rs = 0;
                        for( bit = 16; bit != 0; --bit ){
                                rs <<= 1;
                                rs |= mask & 1;
                                mask >>= 1;
                        }
                        mask = rs;
                }
                iwords[1] = mask;
                nw = 2;
                break;
        case PIXT:
        case MOVB:
        case MOVE:
                ms = op0 && op0->type == REG? M_REG: op0->mode;
                assert(op1 != NULL);
                md = op1->type == REG? M_REG: op1->mode;
                opc = class == MOVE? move_opc[md][ms]:
                      class == MOVB? movb_opc[md][ms]: pixt_opc[md][ms];
                if( opc == 0 ){
                        perr("Illegal combination of addressing modes");
                        nw = 0;
                        break;
                }
                if( ms == M_INDEX ){
                        if( (int16_t) val[0] != val[0] )
                                perr("Source displacement too large");
                        iwords[1] = val[0];
                        nw = 2;
                } else if( ms == M_ABSOLUTE ){
                        iwords[1] = val[0];
                        iwords[2] = val[0] >> 16;
                        nw = 3;
                        rs = 0;
                }
                if( md == M_INDEX ){
                        if( (int16_t) val[1] != val[1] )
                                perr("Destination displacement too large");
                        iwords[nw] = val[1];
                        ++nw;
                } else if( md == M_ABSOLUTE ){
                        iwords[nw] = val[1];
                        iwords[nw+1] = val[1] >> 16;
                        nw += 2;
                        rd = rs;
                        rs = 0;
                }
                opc |= (rd & 0x1F) | ((rs & 0xF) << 5);
                opc |= check_spec(spec[2], "01", "field") << 9;
                break;
        case RETS:
                if( op0 == NULL )
                        val[0] = 0;
                else if( val[0] < 0 || val[0] > 31 )
                        perr("%s out of range",
                             (opc > 0x900? "Pop count": "Trap number"));
                opc |= val[0] & 0x1F;
                break;
        default:
                perr("BUG: unknown instruction class %d", class);
        }
        iwords[0] = opc;
        return nw;
}