root/src/liblink/asm5.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. nocache
  2. scan
  3. casesz
  4. span5
  5. checkpool
  6. flushpool
  7. addpool
  8. regoff
  9. immrot
  10. immaddr
  11. immfloat
  12. immhalf
  13. aclass
  14. prasm
  15. oplook
  16. cmp
  17. ocmp
  18. buildop
  19. asmout
  20. oprrr
  21. opbra
  22. olr
  23. olhr
  24. osr
  25. oshr
  26. osrr
  27. oshrr
  28. olrr
  29. olhrr
  30. ofsr
  31. omvl
  32. chipzero5
  33. chipfloat5

// Inferno utils/5l/span.c
// http://code.google.com/p/inferno-os/source/browse/utils/5l/span.c
//
//      Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
//      Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//      Portions Copyright © 1997-1999 Vita Nuova Limited
//      Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
//      Portions Copyright © 2004,2006 Bruce Ellis
//      Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//      Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
//      Portions Copyright © 2009 The Go Authors.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Instruction layout.

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <link.h>
#include "../cmd/5l/5.out.h"
#include "../pkg/runtime/stack.h"

typedef struct  Optab   Optab;
typedef struct  Oprang  Oprang;
typedef uchar   Opcross[32][2][32];

struct  Optab
{
        char    as;
        uchar   a1;
        char    a2;
        uchar   a3;
        uchar   type;
        char    size;
        char    param;
        char    flag;
        uchar   pcrelsiz;
};
struct  Oprang
{
        Optab*  start;
        Optab*  stop;
};

enum
{
        LFROM           = 1<<0,
        LTO             = 1<<1,
        LPOOL           = 1<<2,
        LPCREL          = 1<<3,

        C_NONE          = 0,
        C_REG,
        C_REGREG,
        C_REGREG2,
        C_SHIFT,
        C_FREG,
        C_PSR,
        C_FCR,

        C_RCON,         /* 0xff rotated */
        C_NCON,         /* ~RCON */
        C_SCON,         /* 0xffff */
        C_LCON,
        C_LCONADDR,
        C_ZFCON,
        C_SFCON,
        C_LFCON,

        C_RACON,
        C_LACON,

        C_SBRA,
        C_LBRA,

        C_HAUTO,        /* halfword insn offset (-0xff to 0xff) */
        C_FAUTO,        /* float insn offset (0 to 0x3fc, word aligned) */
        C_HFAUTO,       /* both H and F */
        C_SAUTO,        /* -0xfff to 0xfff */
        C_LAUTO,

        C_HOREG,
        C_FOREG,
        C_HFOREG,
        C_SOREG,
        C_ROREG,
        C_SROREG,       /* both nil and R */
        C_LOREG,

        C_PC,
        C_SP,
        C_HREG,

        C_ADDR,         /* reference to relocatable address */

        C_GOK,
};

static Optab    optab[] =
{
        /* struct Optab:
          OPCODE,       from, prog->reg, to,             type,size,param,flag */
        { ATEXT,        C_ADDR, C_NONE, C_LCON,          0, 0, 0 },
        { ATEXT,        C_ADDR, C_REG,  C_LCON,          0, 0, 0 },

        { AADD,         C_REG,  C_REG,  C_REG,           1, 4, 0 },
        { AADD,         C_REG,  C_NONE, C_REG,           1, 4, 0 },
        { AMOVW,        C_REG,  C_NONE, C_REG,           1, 4, 0 },
        { AMVN,         C_REG,  C_NONE, C_REG,           1, 4, 0 },
        { ACMP,         C_REG,  C_REG,  C_NONE,          1, 4, 0 },

        { AADD,         C_RCON, C_REG,  C_REG,           2, 4, 0 },
        { AADD,         C_RCON, C_NONE, C_REG,           2, 4, 0 },
        { AMOVW,        C_RCON, C_NONE, C_REG,           2, 4, 0 },
        { AMVN,         C_RCON, C_NONE, C_REG,           2, 4, 0 },
        { ACMP,         C_RCON, C_REG,  C_NONE,          2, 4, 0 },

        { AADD,         C_SHIFT,C_REG,  C_REG,           3, 4, 0 },
        { AADD,         C_SHIFT,C_NONE, C_REG,           3, 4, 0 },
        { AMVN,         C_SHIFT,C_NONE, C_REG,           3, 4, 0 },
        { ACMP,         C_SHIFT,C_REG,  C_NONE,          3, 4, 0 },

        { AMOVW,        C_RACON,C_NONE, C_REG,           4, 4, REGSP },

        { AB,           C_NONE, C_NONE, C_SBRA,          5, 4, 0,       LPOOL },
        { ABL,          C_NONE, C_NONE, C_SBRA,          5, 4, 0 },
        { ABX,          C_NONE, C_NONE, C_SBRA,          74, 20, 0 },
        { ABEQ,         C_NONE, C_NONE, C_SBRA,          5, 4, 0 },

        { AB,           C_NONE, C_NONE, C_ROREG,         6, 4, 0,       LPOOL },
        { ABL,          C_NONE, C_NONE, C_ROREG,         7, 4, 0 },
        { ABL,          C_REG,  C_NONE, C_ROREG,         7, 4, 0 },
        { ABX,          C_NONE, C_NONE, C_ROREG,         75, 12, 0 },
        { ABXRET,       C_NONE, C_NONE, C_ROREG,         76, 4, 0 },

        { ASLL,         C_RCON, C_REG,  C_REG,           8, 4, 0 },
        { ASLL,         C_RCON, C_NONE, C_REG,           8, 4, 0 },

        { ASLL,         C_REG,  C_NONE, C_REG,           9, 4, 0 },
        { ASLL,         C_REG,  C_REG,  C_REG,           9, 4, 0 },

        { ASWI,         C_NONE, C_NONE, C_NONE,         10, 4, 0 },
        { ASWI,         C_NONE, C_NONE, C_LOREG,        10, 4, 0 },
        { ASWI,         C_NONE, C_NONE, C_LCON,         10, 4, 0 },

        { AWORD,        C_NONE, C_NONE, C_LCON,         11, 4, 0 },
        { AWORD,        C_NONE, C_NONE, C_LCONADDR,     11, 4, 0 },
        { AWORD,        C_NONE, C_NONE, C_ADDR,         11, 4, 0 },

        { AMOVW,        C_NCON, C_NONE, C_REG,          12, 4, 0 },
        { AMOVW,        C_LCON, C_NONE, C_REG,          12, 4, 0,       LFROM },
        { AMOVW,        C_LCONADDR,     C_NONE, C_REG,  12, 4, 0,       LFROM | LPCREL, 4},

        { AADD,         C_NCON, C_REG,  C_REG,          13, 8, 0 },
        { AADD,         C_NCON, C_NONE, C_REG,          13, 8, 0 },
        { AMVN,         C_NCON, C_NONE, C_REG,          13, 8, 0 },
        { ACMP,         C_NCON, C_REG,  C_NONE,         13, 8, 0 },
        { AADD,         C_LCON, C_REG,  C_REG,          13, 8, 0,       LFROM },
        { AADD,         C_LCON, C_NONE, C_REG,          13, 8, 0,       LFROM },
        { AMVN,         C_LCON, C_NONE, C_REG,          13, 8, 0,       LFROM },
        { ACMP,         C_LCON, C_REG,  C_NONE,         13, 8, 0,       LFROM },

        { AMOVB,        C_REG,  C_NONE, C_REG,           1, 4, 0 },
        { AMOVBS,       C_REG,  C_NONE, C_REG,          14, 8, 0 },
        { AMOVBU,       C_REG,  C_NONE, C_REG,          58, 4, 0 },
        { AMOVH,        C_REG,  C_NONE, C_REG,           1, 4, 0 },
        { AMOVHS,       C_REG,  C_NONE, C_REG,          14, 8, 0 },
        { AMOVHU,       C_REG,  C_NONE, C_REG,          14, 8, 0 },

        { AMUL,         C_REG,  C_REG,  C_REG,          15, 4, 0 },
        { AMUL,         C_REG,  C_NONE, C_REG,          15, 4, 0 },

        { ADIV,         C_REG,  C_REG,  C_REG,          16, 4, 0 },
        { ADIV,         C_REG,  C_NONE, C_REG,          16, 4, 0 },

        { AMULL,        C_REG,  C_REG,  C_REGREG,       17, 4, 0 },
        { AMULA,        C_REG,  C_REG,  C_REGREG2,      17, 4, 0 },

        { AMOVW,        C_REG,  C_NONE, C_SAUTO,        20, 4, REGSP },
        { AMOVW,        C_REG,  C_NONE, C_SOREG,        20, 4, 0 },
        { AMOVB,        C_REG,  C_NONE, C_SAUTO,        20, 4, REGSP },
        { AMOVB,        C_REG,  C_NONE, C_SOREG,        20, 4, 0 },
        { AMOVBS,       C_REG,  C_NONE, C_SAUTO,        20, 4, REGSP },
        { AMOVBS,       C_REG,  C_NONE, C_SOREG,        20, 4, 0 },
        { AMOVBU,       C_REG,  C_NONE, C_SAUTO,        20, 4, REGSP },
        { AMOVBU,       C_REG,  C_NONE, C_SOREG,        20, 4, 0 },

        { AMOVW,        C_SAUTO,C_NONE, C_REG,          21, 4, REGSP },
        { AMOVW,        C_SOREG,C_NONE, C_REG,          21, 4, 0 },
        { AMOVBU,       C_SAUTO,C_NONE, C_REG,          21, 4, REGSP },
        { AMOVBU,       C_SOREG,C_NONE, C_REG,          21, 4, 0 },

        { AMOVW,        C_REG,  C_NONE, C_LAUTO,        30, 8, REGSP,   LTO },
        { AMOVW,        C_REG,  C_NONE, C_LOREG,        30, 8, 0,       LTO },
        { AMOVW,        C_REG,  C_NONE, C_ADDR,         64, 8, 0,       LTO | LPCREL, 4 },
        { AMOVB,        C_REG,  C_NONE, C_LAUTO,        30, 8, REGSP,   LTO },
        { AMOVB,        C_REG,  C_NONE, C_LOREG,        30, 8, 0,       LTO },
        { AMOVB,        C_REG,  C_NONE, C_ADDR,         64, 8, 0,       LTO | LPCREL, 4 },
        { AMOVBS,       C_REG,  C_NONE, C_LAUTO,        30, 8, REGSP,   LTO },
        { AMOVBS,       C_REG,  C_NONE, C_LOREG,        30, 8, 0,       LTO },
        { AMOVBS,       C_REG,  C_NONE, C_ADDR,         64, 8, 0,       LTO | LPCREL, 4 },
        { AMOVBU,       C_REG,  C_NONE, C_LAUTO,        30, 8, REGSP,   LTO },
        { AMOVBU,       C_REG,  C_NONE, C_LOREG,        30, 8, 0,       LTO },
        { AMOVBU,       C_REG,  C_NONE, C_ADDR,         64, 8, 0,       LTO | LPCREL, 4 },

        { AMOVW,        C_LAUTO,C_NONE, C_REG,          31, 8, REGSP,   LFROM },
        { AMOVW,        C_LOREG,C_NONE, C_REG,          31, 8, 0,       LFROM },
        { AMOVW,        C_ADDR, C_NONE, C_REG,          65, 8, 0,       LFROM | LPCREL, 4 },
        { AMOVBU,       C_LAUTO,C_NONE, C_REG,          31, 8, REGSP,   LFROM },
        { AMOVBU,       C_LOREG,C_NONE, C_REG,          31, 8, 0,       LFROM },
        { AMOVBU,       C_ADDR, C_NONE, C_REG,          65, 8, 0,       LFROM | LPCREL, 4 },

        { AMOVW,        C_LACON,C_NONE, C_REG,          34, 8, REGSP,   LFROM },

        { AMOVW,        C_PSR,  C_NONE, C_REG,          35, 4, 0 },
        { AMOVW,        C_REG,  C_NONE, C_PSR,          36, 4, 0 },
        { AMOVW,        C_RCON, C_NONE, C_PSR,          37, 4, 0 },

        { AMOVM,        C_LCON, C_NONE, C_SOREG,        38, 4, 0 },
        { AMOVM,        C_SOREG,C_NONE, C_LCON,         39, 4, 0 },

        { ASWPW,        C_SOREG,C_REG,  C_REG,          40, 4, 0 },

        { ARFE,         C_NONE, C_NONE, C_NONE,         41, 4, 0 },

        { AMOVF,        C_FREG, C_NONE, C_FAUTO,        50, 4, REGSP },
        { AMOVF,        C_FREG, C_NONE, C_FOREG,        50, 4, 0 },

        { AMOVF,        C_FAUTO,C_NONE, C_FREG,         51, 4, REGSP },
        { AMOVF,        C_FOREG,C_NONE, C_FREG,         51, 4, 0 },

        { AMOVF,        C_FREG, C_NONE, C_LAUTO,        52, 12, REGSP,  LTO },
        { AMOVF,        C_FREG, C_NONE, C_LOREG,        52, 12, 0,      LTO },

        { AMOVF,        C_LAUTO,C_NONE, C_FREG,         53, 12, REGSP,  LFROM },
        { AMOVF,        C_LOREG,C_NONE, C_FREG,         53, 12, 0,      LFROM },

        { AMOVF,        C_FREG, C_NONE, C_ADDR,         68, 8, 0,       LTO | LPCREL, 4 },
        { AMOVF,        C_ADDR, C_NONE, C_FREG,         69, 8, 0,       LFROM | LPCREL, 4},

        { AADDF,        C_FREG, C_NONE, C_FREG,         54, 4, 0 },
        { AADDF,        C_FREG, C_REG,  C_FREG,         54, 4, 0 },
        { AMOVF,        C_FREG, C_NONE, C_FREG,         54, 4, 0 },

        { AMOVW,        C_REG,  C_NONE, C_FCR,          56, 4, 0 },
        { AMOVW,        C_FCR,  C_NONE, C_REG,          57, 4, 0 },

        { AMOVW,        C_SHIFT,C_NONE, C_REG,          59, 4, 0 },
        { AMOVBU,       C_SHIFT,C_NONE, C_REG,          59, 4, 0 },

        { AMOVB,        C_SHIFT,C_NONE, C_REG,          60, 4, 0 },
        { AMOVBS,       C_SHIFT,C_NONE, C_REG,          60, 4, 0 },

        { AMOVW,        C_REG,  C_NONE, C_SHIFT,        61, 4, 0 },
        { AMOVB,        C_REG,  C_NONE, C_SHIFT,        61, 4, 0 },
        { AMOVBS,       C_REG,  C_NONE, C_SHIFT,        61, 4, 0 },
        { AMOVBU,       C_REG,  C_NONE, C_SHIFT,        61, 4, 0 },

        { ACASE,        C_REG,  C_NONE, C_NONE,         62, 4, 0, LPCREL, 8 },
        { ABCASE,       C_NONE, C_NONE, C_SBRA,         63, 4, 0, LPCREL, 0 },

        { AMOVH,        C_REG,  C_NONE, C_HAUTO,        70, 4, REGSP,   0 },
        { AMOVH,        C_REG,  C_NONE, C_HOREG,        70, 4, 0,       0 },
        { AMOVHS,       C_REG,  C_NONE, C_HAUTO,        70, 4, REGSP,   0 },
        { AMOVHS,       C_REG,  C_NONE, C_HOREG,        70, 4, 0,       0 },
        { AMOVHU,       C_REG,  C_NONE, C_HAUTO,        70, 4, REGSP,   0 },
        { AMOVHU,       C_REG,  C_NONE, C_HOREG,        70, 4, 0,       0 },

        { AMOVB,        C_HAUTO,C_NONE, C_REG,          71, 4, REGSP,   0 },
        { AMOVB,        C_HOREG,C_NONE, C_REG,          71, 4, 0,       0 },
        { AMOVBS,       C_HAUTO,C_NONE, C_REG,          71, 4, REGSP,   0 },
        { AMOVBS,       C_HOREG,C_NONE, C_REG,          71, 4, 0,       0 },
        { AMOVH,        C_HAUTO,C_NONE, C_REG,          71, 4, REGSP,   0 },
        { AMOVH,        C_HOREG,C_NONE, C_REG,          71, 4, 0,       0 },
        { AMOVHS,       C_HAUTO,C_NONE, C_REG,          71, 4, REGSP,   0 },
        { AMOVHS,       C_HOREG,C_NONE, C_REG,          71, 4, 0,       0 },
        { AMOVHU,       C_HAUTO,C_NONE, C_REG,          71, 4, REGSP,   0 },
        { AMOVHU,       C_HOREG,C_NONE, C_REG,          71, 4, 0,       0 },

        { AMOVH,        C_REG,  C_NONE, C_LAUTO,        72, 8, REGSP,   LTO },
        { AMOVH,        C_REG,  C_NONE, C_LOREG,        72, 8, 0,       LTO },
        { AMOVH,        C_REG,  C_NONE, C_ADDR, 94, 8, 0,       LTO | LPCREL, 4 },
        { AMOVHS,       C_REG,  C_NONE, C_LAUTO,        72, 8, REGSP,   LTO },
        { AMOVHS,       C_REG,  C_NONE, C_LOREG,        72, 8, 0,       LTO },
        { AMOVHS,       C_REG,  C_NONE, C_ADDR, 94, 8, 0,       LTO | LPCREL, 4 },
        { AMOVHU,       C_REG,  C_NONE, C_LAUTO,        72, 8, REGSP,   LTO },
        { AMOVHU,       C_REG,  C_NONE, C_LOREG,        72, 8, 0,       LTO },
        { AMOVHU,       C_REG,  C_NONE, C_ADDR, 94, 8, 0,       LTO | LPCREL, 4 },

        { AMOVB,        C_LAUTO,C_NONE, C_REG,          73, 8, REGSP,   LFROM },
        { AMOVB,        C_LOREG,C_NONE, C_REG,          73, 8, 0,       LFROM },
        { AMOVB,        C_ADDR, C_NONE, C_REG,          93, 8, 0,       LFROM | LPCREL, 4 },
        { AMOVBS,       C_LAUTO,C_NONE, C_REG,          73, 8, REGSP,   LFROM },
        { AMOVBS,       C_LOREG,C_NONE, C_REG,          73, 8, 0,       LFROM },
        { AMOVBS,       C_ADDR, C_NONE, C_REG,          93, 8, 0,       LFROM | LPCREL, 4 },
        { AMOVH,        C_LAUTO,C_NONE, C_REG,          73, 8, REGSP,   LFROM },
        { AMOVH,        C_LOREG,C_NONE, C_REG,          73, 8, 0,       LFROM },
        { AMOVH,        C_ADDR, C_NONE, C_REG,          93, 8, 0,       LFROM | LPCREL, 4 },
        { AMOVHS,       C_LAUTO,C_NONE, C_REG,          73, 8, REGSP,   LFROM },
        { AMOVHS,       C_LOREG,C_NONE, C_REG,          73, 8, 0,       LFROM },
        { AMOVHS,       C_ADDR, C_NONE, C_REG,          93, 8, 0,       LFROM | LPCREL, 4 },
        { AMOVHU,       C_LAUTO,C_NONE, C_REG,          73, 8, REGSP,   LFROM },
        { AMOVHU,       C_LOREG,C_NONE, C_REG,          73, 8, 0,       LFROM },
        { AMOVHU,       C_ADDR, C_NONE, C_REG,          93, 8, 0,       LFROM | LPCREL, 4 },

        { ALDREX,       C_SOREG,C_NONE, C_REG,          77, 4, 0 },
        { ASTREX,       C_SOREG,C_REG,  C_REG,          78, 4, 0 },

        { AMOVF,        C_ZFCON,C_NONE, C_FREG,         80, 8, 0 },
        { AMOVF,        C_SFCON,C_NONE, C_FREG,         81, 4, 0 },

        { ACMPF,        C_FREG, C_REG,  C_NONE,         82, 8, 0 },
        { ACMPF,        C_FREG, C_NONE, C_NONE,         83, 8, 0 },

        { AMOVFW,       C_FREG, C_NONE, C_FREG,         84, 4, 0 },
        { AMOVWF,       C_FREG, C_NONE, C_FREG,         85, 4, 0 },

        { AMOVFW,       C_FREG, C_NONE, C_REG,          86, 8, 0 },
        { AMOVWF,       C_REG,  C_NONE, C_FREG,         87, 8, 0 },

        { AMOVW,        C_REG,  C_NONE, C_FREG,         88, 4, 0 },
        { AMOVW,        C_FREG, C_NONE, C_REG,          89, 4, 0 },

        { ATST,         C_REG,  C_NONE, C_NONE,         90, 4, 0 },

        { ALDREXD,      C_SOREG,C_NONE, C_REG,          91, 4, 0 },
        { ASTREXD,      C_SOREG,C_REG,  C_REG,          92, 4, 0 },

        { APLD,         C_SOREG,C_NONE, C_NONE,         95, 4, 0 },
        
        { AUNDEF,               C_NONE, C_NONE, C_NONE,         96, 4, 0 },

        { ACLZ,         C_REG,  C_NONE, C_REG,          97, 4, 0 },

        { AMULWT,       C_REG,  C_REG,  C_REG,          98, 4, 0 },
        { AMULAWT,      C_REG,  C_REG,  C_REGREG2,              99, 4, 0 },

        { AUSEFIELD,    C_ADDR, C_NONE, C_NONE,          0, 0, 0 },
        { APCDATA,      C_LCON, C_NONE, C_LCON,         0, 0, 0 },
        { AFUNCDATA,    C_LCON, C_NONE, C_ADDR, 0, 0, 0 },

        { ADUFFZERO,    C_NONE, C_NONE, C_SBRA,          5, 4, 0 },  // same as ABL
        { ADUFFCOPY,    C_NONE, C_NONE, C_SBRA,          5, 4, 0 },  // same as ABL

        { AXXX,         C_NONE, C_NONE, C_NONE,          0, 4, 0 },
};

static struct {
        uint32  start;
        uint32  size;
        uint32  extra;
} pool;

static int      checkpool(Link*, Prog*, int);
static int      flushpool(Link*, Prog*, int, int);
static void     addpool(Link*, Prog*, Addr*);
static void     asmout(Link*, Prog*, Optab*, int32*);
static Optab*   oplook(Link*, Prog*);
static int32    oprrr(Link*, int, int);
static int32    olr(Link*, int32, int, int, int);
static int32    olhr(Link*, int32, int, int, int);
static int32    olrr(Link*, int, int, int, int);
static int32    olhrr(Link*, int, int, int, int);
static int32    osr(Link*, int, int, int32, int, int);
static int32    oshr(Link*, int, int32, int, int);
static int32    ofsr(Link*, int, int, int32, int, int, Prog*);
static int32    osrr(Link*, int, int, int, int);
static int32    oshrr(Link*, int, int, int, int);
static int32    omvl(Link*, Prog*, Addr*, int);
static int32    immaddr(int32);
static int      aclass(Link*, Addr*);
static int32    immrot(uint32);
static int32    immaddr(int32);
static int32    opbra(Link*, int, int);

static  Opcross opcross[8];
static  Oprang  oprange[ALAST];
static  char    xcmp[C_GOK+1][C_GOK+1];
static  uchar   repop[ALAST];

static Prog zprg = {
        .as = AGOK,
        .scond = C_SCOND_NONE,
        .reg = NREG,
        .from = {
                .name = D_NONE,
                .type = D_NONE,
                .reg = NREG,
        },
        .to = {
                .name = D_NONE,
                .type = D_NONE,
                .reg = NREG,
        },
};

static void
nocache(Prog *p)
{
        p->optab = 0;
        p->from.class = 0;
        p->to.class = 0;
}

static int
scan(Link *ctxt, Prog *op, Prog *p, int c)
{
        Prog *q;

        for(q = op->link; q != p && q != nil; q = q->link){
                q->pc = c;
                c += oplook(ctxt, q)->size;
                nocache(q);
        }
        return c;
}

/* size of a case statement including jump table */
static int32
casesz(Link *ctxt, Prog *p)
{
        int jt = 0;
        int32 n = 0;
        Optab *o;

        for( ; p != nil; p = p->link){
                if(p->as == ABCASE)
                        jt = 1;
                else if(jt)
                        break;
                o = oplook(ctxt, p);
                n += o->size;
        }
        return n;
}

static void buildop(Link*);

void
span5(Link *ctxt, LSym *cursym)
{
        Prog *p, *op;
        Optab *o;
        int m, bflag, i, v;
        int32 c, out[6];
        uchar *bp;

        p = cursym->text;
        if(p == nil || p->link == nil) // handle external functions and ELF section symbols
                return;
 
        if(oprange[AAND].start == nil)
                buildop(ctxt);

        ctxt->cursym = cursym;

        ctxt->autosize = p->to.offset + 4;
        c = 0;  

        for(op = p, p = p->link; p != nil; op = p, p = p->link) {
                ctxt->curp = p;
                p->pc = c;
                o = oplook(ctxt, p);
                m = o->size;
                // must check literal pool here in case p generates many instructions
                if(ctxt->blitrl){
                        if(checkpool(ctxt, op, p->as == ACASE ? casesz(ctxt, p) : m)) {
                                p->pc = scan(ctxt, op, p, c);
                                c = p->pc;
                        }
                }
                if(m == 0 && (p->as != AFUNCDATA && p->as != APCDATA)) {
                        ctxt->diag("zero-width instruction\n%P", p);
                        continue;
                }
                switch(o->flag & (LFROM|LTO|LPOOL)) {
                case LFROM:
                        addpool(ctxt, p, &p->from);
                        break;
                case LTO:
                        addpool(ctxt, p, &p->to);
                        break;
                case LPOOL:
                        if ((p->scond&C_SCOND) == C_SCOND_NONE)
                                flushpool(ctxt, p, 0, 0);
                        break;
                }
                if(p->as==AMOVW && p->to.type==D_REG && p->to.reg==REGPC && (p->scond&C_SCOND) == C_SCOND_NONE)
                        flushpool(ctxt, p, 0, 0);
                c += m;
        }
        if(ctxt->blitrl){
                if(checkpool(ctxt, op, 0))
                        c = scan(ctxt, op, nil, c);
        }
        cursym->size = c;

        /*
         * if any procedure is large enough to
         * generate a large SBRA branch, then
         * generate extra passes putting branches
         * around jmps to fix. this is rare.
         */
        do {
                if(ctxt->debugvlog)
                        Bprint(ctxt->bso, "%5.2f span1\n", cputime());
                bflag = 0;
                c = 0;
                for(p = cursym->text; p != nil; p = p->link) {
                        ctxt->curp = p;
                        p->pc = c;
                        o = oplook(ctxt,p);
/* very large branches
                        if(o->type == 6 && p->pcond) {
                                otxt = p->pcond->pc - c;
                                if(otxt < 0)
                                        otxt = -otxt;
                                if(otxt >= (1L<<17) - 10) {
                                        q = ctxt->arch->prg();
                                        q->link = p->link;
                                        p->link = q;
                                        q->as = AB;
                                        q->to.type = D_BRANCH;
                                        q->pcond = p->pcond;
                                        p->pcond = q;
                                        q = ctxt->arch->prg();
                                        q->link = p->link;
                                        p->link = q;
                                        q->as = AB;
                                        q->to.type = D_BRANCH;
                                        q->pcond = q->link->link;
                                        bflag = 1;
                                }
                        }
 */
                        m = o->size;
                        if(m == 0 && (p->as != AFUNCDATA && p->as != APCDATA)) {
                                if(p->as == ATEXT) {
                                        ctxt->autosize = p->to.offset + 4;
                                        continue;
                                }
                                ctxt->diag("zero-width instruction\n%P", p);
                                continue;
                        }
                        c += m;
                }
                cursym->size = c;
        } while(bflag);

        /*
         * lay out the code.  all the pc-relative code references,
         * even cross-function, are resolved now;
         * only data references need to be relocated.
         * with more work we could leave cross-function
         * code references to be relocated too, and then
         * perhaps we'd be able to parallelize the span loop above.
         */
        if(ctxt->gmsym == nil)
                ctxt->gmsym = linklookup(ctxt, "runtime.tlsgm", 0);

        p = cursym->text;
        ctxt->autosize = p->to.offset + 4;
        symgrow(ctxt, cursym, cursym->size);

        bp = cursym->p;
        for(p = p->link; p != nil; p = p->link) {
                ctxt->pc = p->pc;
                ctxt->curp = p;
                o = oplook(ctxt, p);
                asmout(ctxt, p, o, out);
                for(i=0; i<o->size/4; i++) {
                        v = out[i];
                        *bp++ = v;
                        *bp++ = v>>8;
                        *bp++ = v>>16;
                        *bp++ = v>>24;
                }
        }
}

/*
 * when the first reference to the literal pool threatens
 * to go out of range of a 12-bit PC-relative offset,
 * drop the pool now, and branch round it.
 * this happens only in extended basic blocks that exceed 4k.
 */
static int
checkpool(Link *ctxt, Prog *p, int sz)
{
        if(pool.size >= 0xffc || immaddr((p->pc+sz+4)+4+pool.size - pool.start+8) == 0)
                return flushpool(ctxt, p, 1, 0);
        else if(p->link == nil)
                return flushpool(ctxt, p, 2, 0);
        return 0;
}

static int
flushpool(Link *ctxt, Prog *p, int skip, int force)
{
        Prog *q;

        if(ctxt->blitrl) {
                if(skip){
                        if(0 && skip==1)print("note: flush literal pool at %llux: len=%ud ref=%ux\n", p->pc+4, pool.size, pool.start);
                        q = ctxt->arch->prg();
                        q->as = AB;
                        q->to.type = D_BRANCH;
                        q->pcond = p->link;
                        q->link = ctxt->blitrl;
                        q->lineno = p->lineno;
                        ctxt->blitrl = q;
                }
                else if(!force && (p->pc+pool.size-pool.start < 2048))
                        return 0;
                ctxt->elitrl->link = p->link;
                p->link = ctxt->blitrl;
                // BUG(minux): how to correctly handle line number for constant pool entries?
                // for now, we set line number to the last instruction preceding them at least
                // this won't bloat the .debug_line tables
                while(ctxt->blitrl) {
                        ctxt->blitrl->lineno = p->lineno;
                        ctxt->blitrl = ctxt->blitrl->link;
                }
                ctxt->blitrl = 0;       /* BUG: should refer back to values until out-of-range */
                ctxt->elitrl = 0;
                pool.size = 0;
                pool.start = 0;
                pool.extra = 0;
                return 1;
        }
        return 0;
}

static void
addpool(Link *ctxt, Prog *p, Addr *a)
{
        Prog *q, t;
        int c;

        c = aclass(ctxt, a);

        t = zprg;
        t.as = AWORD;

        switch(c) {
        default:
                t.to = *a;
                if(ctxt->flag_shared && t.to.sym != nil)
                        t.pcrel = p;
                break;

        case C_SROREG:
        case C_LOREG:
        case C_ROREG:
        case C_FOREG:
        case C_SOREG:
        case C_HOREG:
        case C_FAUTO:
        case C_SAUTO:
        case C_LAUTO:
        case C_LACON:
                t.to.type = D_CONST;
                t.to.offset = ctxt->instoffset;
                break;
        }

        if(t.pcrel == nil) {
                for(q = ctxt->blitrl; q != nil; q = q->link)    /* could hash on t.t0.offset */
                        if(q->pcrel == nil && memcmp(&q->to, &t.to, sizeof(t.to)) == 0) {
                                p->pcond = q;
                                return;
                        }
        }

        q = ctxt->arch->prg();
        *q = t;
        q->pc = pool.size;

        if(ctxt->blitrl == nil) {
                ctxt->blitrl = q;
                pool.start = p->pc;
        } else
                ctxt->elitrl->link = q;
        ctxt->elitrl = q;
        pool.size += 4;

        p->pcond = q;
}

static int32
regoff(Link *ctxt, Addr *a)
{

        ctxt->instoffset = 0;
        aclass(ctxt, a);
        return ctxt->instoffset;
}

static int32
immrot(uint32 v)
{
        int i;

        for(i=0; i<16; i++) {
                if((v & ~0xff) == 0)
                        return (i<<8) | v | (1<<25);
                v = (v<<2) | (v>>30);
        }
        return 0;
}

static int32
immaddr(int32 v)
{
        if(v >= 0 && v <= 0xfff)
                return (v & 0xfff) |
                        (1<<24) |       /* pre indexing */
                        (1<<23);        /* pre indexing, up */
        if(v >= -0xfff && v < 0)
                return (-v & 0xfff) |
                        (1<<24);        /* pre indexing */
        return 0;
}

static int
immfloat(int32 v)
{
        return (v & 0xC03) == 0;        /* offset will fit in floating-point load/store */
}

static int
immhalf(int32 v)
{
        if(v >= 0 && v <= 0xff)
                return v|
                        (1<<24)|        /* pre indexing */
                        (1<<23);        /* pre indexing, up */
        if(v >= -0xff && v < 0)
                return (-v & 0xff)|
                        (1<<24);        /* pre indexing */
        return 0;
}

static int
aclass(Link *ctxt, Addr *a)
{
        LSym *s;
        int t;

        switch(a->type) {
        case D_NONE:
                return C_NONE;

        case D_REG:
                return C_REG;

        case D_REGREG:
                return C_REGREG;

        case D_REGREG2:
                return C_REGREG2;

        case D_SHIFT:
                return C_SHIFT;

        case D_FREG:
                return C_FREG;

        case D_FPCR:
                return C_FCR;

        case D_OREG:
                switch(a->name) {
                case D_EXTERN:
                case D_STATIC:
                        if(a->sym == 0 || a->sym->name == 0) {
                                print("null sym external\n");
                                print("%D\n", a);
                                return C_GOK;
                        }
                        ctxt->instoffset = 0;   // s.b. unused but just in case
                        return C_ADDR;

                case D_AUTO:
                        ctxt->instoffset = ctxt->autosize + a->offset;
                        t = immaddr(ctxt->instoffset);
                        if(t){
                                if(immhalf(ctxt->instoffset))
                                        return immfloat(t) ? C_HFAUTO : C_HAUTO;
                                if(immfloat(t))
                                        return C_FAUTO;
                                return C_SAUTO;
                        }
                        return C_LAUTO;

                case D_PARAM:
                        ctxt->instoffset = ctxt->autosize + a->offset + 4L;
                        t = immaddr(ctxt->instoffset);
                        if(t){
                                if(immhalf(ctxt->instoffset))
                                        return immfloat(t) ? C_HFAUTO : C_HAUTO;
                                if(immfloat(t))
                                        return C_FAUTO;
                                return C_SAUTO;
                        }
                        return C_LAUTO;
                case D_NONE:
                        ctxt->instoffset = a->offset;
                        t = immaddr(ctxt->instoffset);
                        if(t) {
                                if(immhalf(ctxt->instoffset))            /* n.b. that it will also satisfy immrot */
                                        return immfloat(t) ? C_HFOREG : C_HOREG;
                                if(immfloat(t))
                                        return C_FOREG; /* n.b. that it will also satisfy immrot */
                                t = immrot(ctxt->instoffset);
                                if(t)
                                        return C_SROREG;
                                if(immhalf(ctxt->instoffset))
                                        return C_HOREG;
                                return C_SOREG;
                        }
                        t = immrot(ctxt->instoffset);
                        if(t)
                                return C_ROREG;
                        return C_LOREG;
                }
                return C_GOK;

        case D_PSR:
                return C_PSR;

        case D_OCONST:
                switch(a->name) {
                case D_EXTERN:
                case D_STATIC:
                        ctxt->instoffset = 0;   // s.b. unused but just in case
                        return C_ADDR;
                }
                return C_GOK;

        case D_FCONST:
                if(chipzero5(ctxt, a->u.dval) >= 0)
                        return C_ZFCON;
                if(chipfloat5(ctxt, a->u.dval) >= 0)
                        return C_SFCON;
                return C_LFCON;

        case D_CONST:
        case D_CONST2:
                switch(a->name) {

                case D_NONE:
                        ctxt->instoffset = a->offset;
                        if(a->reg != NREG)
                                goto aconsize;

                        t = immrot(ctxt->instoffset);
                        if(t)
                                return C_RCON;
                        t = immrot(~ctxt->instoffset);
                        if(t)
                                return C_NCON;
                        return C_LCON;

                case D_EXTERN:
                case D_STATIC:
                        s = a->sym;
                        if(s == nil)
                                break;
                        ctxt->instoffset = 0;   // s.b. unused but just in case
                        return C_LCONADDR;

                case D_AUTO:
                        ctxt->instoffset = ctxt->autosize + a->offset;
                        goto aconsize;

                case D_PARAM:
                        ctxt->instoffset = ctxt->autosize + a->offset + 4L;
                aconsize:
                        t = immrot(ctxt->instoffset);
                        if(t)
                                return C_RACON;
                        return C_LACON;
                }
                return C_GOK;

        case D_BRANCH:
                return C_SBRA;
        }
        return C_GOK;
}

static void
prasm(Prog *p)
{
        print("%P\n", p);
}

static Optab*
oplook(Link *ctxt, Prog *p)
{
        int a1, a2, a3, r;
        char *c1, *c3;
        Optab *o, *e;

        a1 = p->optab;
        if(a1)
                return optab+(a1-1);
        a1 = p->from.class;
        if(a1 == 0) {
                a1 = aclass(ctxt, &p->from) + 1;
                p->from.class = a1;
        }
        a1--;
        a3 = p->to.class;
        if(a3 == 0) {
                a3 = aclass(ctxt, &p->to) + 1;
                p->to.class = a3;
        }
        a3--;
        a2 = C_NONE;
        if(p->reg != NREG)
                a2 = C_REG;
        r = p->as;
        o = oprange[r].start;
        if(o == 0) {
                a1 = opcross[repop[r]][a1][a2][a3];
                if(a1) {
                        p->optab = a1+1;
                        return optab+a1;
                }
                o = oprange[r].stop; /* just generate an error */
        }
        if(0 /*debug['O']*/) {
                print("oplook %A %d %d %d\n",
                        (int)p->as, a1, a2, a3);
                print("         %d %d\n", p->from.type, p->to.type);
        }
        e = oprange[r].stop;
        c1 = xcmp[a1];
        c3 = xcmp[a3];
        for(; o<e; o++)
                if(o->a2 == a2)
                if(c1[o->a1])
                if(c3[o->a3]) {
                        p->optab = (o-optab)+1;
                        return o;
                }
        ctxt->diag("illegal combination %P; %d %d %d, %d %d",
                p, a1, a2, a3, p->from.type, p->to.type);
        ctxt->diag("from %d %d to %d %d\n", p->from.type, p->from.name, p->to.type, p->to.name);
        prasm(p);
        if(o == 0)
                o = optab;
        return o;
}

static int
cmp(int a, int b)
{

        if(a == b)
                return 1;
        switch(a) {
        case C_LCON:
                if(b == C_RCON || b == C_NCON)
                        return 1;
                break;
        case C_LACON:
                if(b == C_RACON)
                        return 1;
                break;
        case C_LFCON:
                if(b == C_ZFCON || b == C_SFCON)
                        return 1;
                break;

        case C_HFAUTO:
                return b == C_HAUTO || b == C_FAUTO;
        case C_FAUTO:
        case C_HAUTO:
                return b == C_HFAUTO;
        case C_SAUTO:
                return cmp(C_HFAUTO, b);
        case C_LAUTO:
                return cmp(C_SAUTO, b);

        case C_HFOREG:
                return b == C_HOREG || b == C_FOREG;
        case C_FOREG:
        case C_HOREG:
                return b == C_HFOREG;
        case C_SROREG:
                return cmp(C_SOREG, b) || cmp(C_ROREG, b);
        case C_SOREG:
        case C_ROREG:
                return b == C_SROREG || cmp(C_HFOREG, b);
        case C_LOREG:
                return cmp(C_SROREG, b);

        case C_LBRA:
                if(b == C_SBRA)
                        return 1;
                break;

        case C_HREG:
                return cmp(C_SP, b) || cmp(C_PC, b);

        }
        return 0;
}

static int
ocmp(const void *a1, const void *a2)
{
        Optab *p1, *p2;
        int n;

        p1 = (Optab*)a1;
        p2 = (Optab*)a2;
        n = p1->as - p2->as;
        if(n)
                return n;
        n = p1->a1 - p2->a1;
        if(n)
                return n;
        n = p1->a2 - p2->a2;
        if(n)
                return n;
        n = p1->a3 - p2->a3;
        if(n)
                return n;
        return 0;
}

static void
buildop(Link *ctxt)
{
        int i, n, r;

        for(i=0; i<C_GOK; i++)
                for(n=0; n<C_GOK; n++)
                        xcmp[i][n] = cmp(n, i);
        for(n=0; optab[n].as != AXXX; n++) {
                if((optab[n].flag & LPCREL) != 0) {
                        if(ctxt->flag_shared)
                                optab[n].size += optab[n].pcrelsiz;
                        else
                                optab[n].flag &= ~LPCREL;
                }
        }
        qsort(optab, n, sizeof(optab[0]), ocmp);
        for(i=0; i<n; i++) {
                r = optab[i].as;
                oprange[r].start = optab+i;
                while(optab[i].as == r)
                        i++;
                oprange[r].stop = optab+i;
                i--;

                switch(r)
                {
                default:
                        ctxt->diag("unknown op in build: %A", r);
                        sysfatal("bad code");
                case AADD:
                        oprange[AAND] = oprange[r];
                        oprange[AEOR] = oprange[r];
                        oprange[ASUB] = oprange[r];
                        oprange[ARSB] = oprange[r];
                        oprange[AADC] = oprange[r];
                        oprange[ASBC] = oprange[r];
                        oprange[ARSC] = oprange[r];
                        oprange[AORR] = oprange[r];
                        oprange[ABIC] = oprange[r];
                        break;
                case ACMP:
                        oprange[ATEQ] = oprange[r];
                        oprange[ACMN] = oprange[r];
                        break;
                case AMVN:
                        break;
                case ABEQ:
                        oprange[ABNE] = oprange[r];
                        oprange[ABCS] = oprange[r];
                        oprange[ABHS] = oprange[r];
                        oprange[ABCC] = oprange[r];
                        oprange[ABLO] = oprange[r];
                        oprange[ABMI] = oprange[r];
                        oprange[ABPL] = oprange[r];
                        oprange[ABVS] = oprange[r];
                        oprange[ABVC] = oprange[r];
                        oprange[ABHI] = oprange[r];
                        oprange[ABLS] = oprange[r];
                        oprange[ABGE] = oprange[r];
                        oprange[ABLT] = oprange[r];
                        oprange[ABGT] = oprange[r];
                        oprange[ABLE] = oprange[r];
                        break;
                case ASLL:
                        oprange[ASRL] = oprange[r];
                        oprange[ASRA] = oprange[r];
                        break;
                case AMUL:
                        oprange[AMULU] = oprange[r];
                        break;
                case ADIV:
                        oprange[AMOD] = oprange[r];
                        oprange[AMODU] = oprange[r];
                        oprange[ADIVU] = oprange[r];
                        break;
                case AMOVW:
                case AMOVB:
                case AMOVBS:
                case AMOVBU:
                case AMOVH:
                case AMOVHS:
                case AMOVHU:
                        break;
                case ASWPW:
                        oprange[ASWPBU] = oprange[r];
                        break;
                case AB:
                case ABL:
                case ABX:
                case ABXRET:
                case ADUFFZERO:
                case ADUFFCOPY:
                case ASWI:
                case AWORD:
                case AMOVM:
                case ARFE:
                case ATEXT:
                case AUSEFIELD:
                case ACASE:
                case ABCASE:
                case ATYPE:
                        break;
                case AADDF:
                        oprange[AADDD] = oprange[r];
                        oprange[ASUBF] = oprange[r];
                        oprange[ASUBD] = oprange[r];
                        oprange[AMULF] = oprange[r];
                        oprange[AMULD] = oprange[r];
                        oprange[ADIVF] = oprange[r];
                        oprange[ADIVD] = oprange[r];
                        oprange[ASQRTF] = oprange[r];
                        oprange[ASQRTD] = oprange[r];
                        oprange[AMOVFD] = oprange[r];
                        oprange[AMOVDF] = oprange[r];
                        oprange[AABSF] = oprange[r];
                        oprange[AABSD] = oprange[r];
                        break;

                case ACMPF:
                        oprange[ACMPD] = oprange[r];
                        break;

                case AMOVF:
                        oprange[AMOVD] = oprange[r];
                        break;

                case AMOVFW:
                        oprange[AMOVDW] = oprange[r];
                        break;

                case AMOVWF:
                        oprange[AMOVWD] = oprange[r];
                        break;

                case AMULL:
                        oprange[AMULAL] = oprange[r];
                        oprange[AMULLU] = oprange[r];
                        oprange[AMULALU] = oprange[r];
                        break;

                case AMULWT:
                        oprange[AMULWB] = oprange[r];
                        break;

                case AMULAWT:
                        oprange[AMULAWB] = oprange[r];
                        break;

                case AMULA:
                case ALDREX:
                case ASTREX:
                case ALDREXD:
                case ASTREXD:
                case ATST:
                case APLD:
                case AUNDEF:
                case ACLZ:
                case AFUNCDATA:
                case APCDATA:
                        break;
                }
        }
}

static void
asmout(Link *ctxt, Prog *p, Optab *o, int32 *out)
{
        int32 o1, o2, o3, o4, o5, o6, v;
        int r, rf, rt, rt2;
        Reloc *rel;

ctxt->printp = p;
        o1 = 0;
        o2 = 0;
        o3 = 0;
        o4 = 0;
        o5 = 0;
        o6 = 0;
        ctxt->armsize += o->size;
if(0 /*debug['P']*/) print("%ux: %P     type %d\n", (uint32)(p->pc), p, o->type);
        switch(o->type) {
        default:
                ctxt->diag("unknown asm %d", o->type);
                prasm(p);
                break;

        case 0:         /* pseudo ops */
if(0 /*debug['G']*/) print("%ux: %s: arm %d\n", (uint32)(p->pc), p->from.sym->name, p->from.sym->fnptr);
                break;

        case 1:         /* op R,[R],R */
                o1 = oprrr(ctxt, p->as, p->scond);
                rf = p->from.reg;
                rt = p->to.reg;
                r = p->reg;
                if(p->to.type == D_NONE)
                        rt = 0;
                if(p->as == AMOVB || p->as == AMOVH || p->as == AMOVW || p->as == AMVN)
                        r = 0;
                else
                if(r == NREG)
                        r = rt;
                o1 |= rf | (r<<16) | (rt<<12);
                break;

        case 2:         /* movbu $I,[R],R */
                aclass(ctxt, &p->from);
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= immrot(ctxt->instoffset);
                rt = p->to.reg;
                r = p->reg;
                if(p->to.type == D_NONE)
                        rt = 0;
                if(p->as == AMOVW || p->as == AMVN)
                        r = 0;
                else if(r == NREG)
                        r = rt;
                o1 |= (r<<16) | (rt<<12);
                break;

        case 3:         /* add R<<[IR],[R],R */
        mov:
                aclass(ctxt, &p->from);
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= p->from.offset;
                rt = p->to.reg;
                r = p->reg;
                if(p->to.type == D_NONE)
                        rt = 0;
                if(p->as == AMOVW || p->as == AMVN)
                        r = 0;
                else if(r == NREG)
                        r = rt;
                o1 |= (r<<16) | (rt<<12);
                break;

        case 4:         /* add $I,[R],R */
                aclass(ctxt, &p->from);
                o1 = oprrr(ctxt, AADD, p->scond);
                o1 |= immrot(ctxt->instoffset);
                r = p->from.reg;
                if(r == NREG)
                        r = o->param;
                o1 |= r << 16;
                o1 |= p->to.reg << 12;
                break;

        case 5:         /* bra s */
                o1 = opbra(ctxt, p->as, p->scond);
                v = -8;
                if(p->to.sym != nil) {
                        rel = addrel(ctxt->cursym);
                        rel->off = ctxt->pc;
                        rel->siz = 4;
                        rel->sym = p->to.sym;
                        v += p->to.offset;
                        rel->add = o1 | ((v >> 2) & 0xffffff);
                        rel->type = R_CALLARM;
                        break;
                }
                if(p->pcond != nil)
                        v = (p->pcond->pc - ctxt->pc) - 8;
                o1 |= (v >> 2) & 0xffffff;
                break;

        case 6:         /* b ,O(R) -> add $O,R,PC */
                aclass(ctxt, &p->to);
                o1 = oprrr(ctxt, AADD, p->scond);
                o1 |= immrot(ctxt->instoffset);
                o1 |= p->to.reg << 16;
                o1 |= REGPC << 12;
                break;

        case 7:         /* bl (R) -> blx R */
                aclass(ctxt, &p->to);
                if(ctxt->instoffset != 0)
                        ctxt->diag("%P: doesn't support BL offset(REG) where offset != 0", p);
                o1 = oprrr(ctxt, ABL, p->scond);
                o1 |= p->to.reg;
                rel = addrel(ctxt->cursym);
                rel->off = ctxt->pc;
                rel->siz = 0;
                rel->type = R_CALLIND;
                break;

        case 8:         /* sll $c,[R],R -> mov (R<<$c),R */
                aclass(ctxt, &p->from);
                o1 = oprrr(ctxt, p->as, p->scond);
                r = p->reg;
                if(r == NREG)
                        r = p->to.reg;
                o1 |= r;
                o1 |= (ctxt->instoffset&31) << 7;
                o1 |= p->to.reg << 12;
                break;

        case 9:         /* sll R,[R],R -> mov (R<<R),R */
                o1 = oprrr(ctxt, p->as, p->scond);
                r = p->reg;
                if(r == NREG)
                        r = p->to.reg;
                o1 |= r;
                o1 |= (p->from.reg << 8) | (1<<4);
                o1 |= p->to.reg << 12;
                break;

        case 10:        /* swi [$con] */
                o1 = oprrr(ctxt, p->as, p->scond);
                if(p->to.type != D_NONE) {
                        aclass(ctxt, &p->to);
                        o1 |= ctxt->instoffset & 0xffffff;
                }
                break;

        case 11:        /* word */
                aclass(ctxt, &p->to);
                o1 = ctxt->instoffset;
                if(p->to.sym != nil) {
                        // This case happens with words generated
                        // in the PC stream as part of the literal pool.
                        rel = addrel(ctxt->cursym);
                        rel->off = ctxt->pc;
                        rel->siz = 4;
                        rel->sym = p->to.sym;
                        rel->add = p->to.offset;
                        
                        // runtime.tlsgm (aka gmsym) is special.
                        // Its "address" is the offset from the TLS thread pointer
                        // to the thread-local g and m pointers.
                        // Emit a TLS relocation instead of a standard one.
                        if(rel->sym == ctxt->gmsym) {
                                rel->type = R_TLS;
                                if(ctxt->flag_shared)
                                        rel->add += ctxt->pc - p->pcrel->pc - 8 - rel->siz;
                                rel->xadd = rel->add;
                                rel->xsym = rel->sym;
                        } else if(ctxt->flag_shared) {
                                rel->type = R_PCREL;
                                rel->add += ctxt->pc - p->pcrel->pc - 8;
                        } else
                                rel->type = R_ADDR;
                        o1 = 0;
                }
                break;

        case 12:        /* movw $lcon, reg */
                o1 = omvl(ctxt, p, &p->from, p->to.reg);
                if(o->flag & LPCREL) {
                        o2 = oprrr(ctxt, AADD, p->scond) | p->to.reg | REGPC << 16 | p->to.reg << 12;
                }
                break;

        case 13:        /* op $lcon, [R], R */
                o1 = omvl(ctxt, p, &p->from, REGTMP);
                if(!o1)
                        break;
                o2 = oprrr(ctxt, p->as, p->scond);
                o2 |= REGTMP;
                r = p->reg;
                if(p->as == AMOVW || p->as == AMVN)
                        r = 0;
                else if(r == NREG)
                        r = p->to.reg;
                o2 |= r << 16;
                if(p->to.type != D_NONE)
                        o2 |= p->to.reg << 12;
                break;

        case 14:        /* movb/movbu/movh/movhu R,R */
                o1 = oprrr(ctxt, ASLL, p->scond);

                if(p->as == AMOVBU || p->as == AMOVHU)
                        o2 = oprrr(ctxt, ASRL, p->scond);
                else
                        o2 = oprrr(ctxt, ASRA, p->scond);

                r = p->to.reg;
                o1 |= (p->from.reg)|(r<<12);
                o2 |= (r)|(r<<12);
                if(p->as == AMOVB || p->as == AMOVBS || p->as == AMOVBU) {
                        o1 |= (24<<7);
                        o2 |= (24<<7);
                } else {
                        o1 |= (16<<7);
                        o2 |= (16<<7);
                }
                break;

        case 15:        /* mul r,[r,]r */
                o1 = oprrr(ctxt, p->as, p->scond);
                rf = p->from.reg;
                rt = p->to.reg;
                r = p->reg;
                if(r == NREG)
                        r = rt;
                if(rt == r) {
                        r = rf;
                        rf = rt;
                }
                if(0)
                if(rt == r || rf == REGPC || r == REGPC || rt == REGPC) {
                        ctxt->diag("bad registers in MUL");
                        prasm(p);
                }
                o1 |= (rf<<8) | r | (rt<<16);
                break;


        case 16:        /* div r,[r,]r */
                o1 = 0xf << 28;
                o2 = 0;
                break;

        case 17:
                o1 = oprrr(ctxt, p->as, p->scond);
                rf = p->from.reg;
                rt = p->to.reg;
                rt2 = p->to.offset;
                r = p->reg;
                o1 |= (rf<<8) | r | (rt<<16) | (rt2<<12);
                break;

        case 20:        /* mov/movb/movbu R,O(R) */
                aclass(ctxt, &p->to);
                r = p->to.reg;
                if(r == NREG)
                        r = o->param;
                o1 = osr(ctxt, p->as, p->from.reg, ctxt->instoffset, r, p->scond);
                break;

        case 21:        /* mov/movbu O(R),R -> lr */
                aclass(ctxt, &p->from);
                r = p->from.reg;
                if(r == NREG)
                        r = o->param;
                o1 = olr(ctxt, ctxt->instoffset, r, p->to.reg, p->scond);
                if(p->as != AMOVW)
                        o1 |= 1<<22;
                break;

        case 30:        /* mov/movb/movbu R,L(R) */
                o1 = omvl(ctxt, p, &p->to, REGTMP);
                if(!o1)
                        break;
                r = p->to.reg;
                if(r == NREG)
                        r = o->param;
                o2 = osrr(ctxt, p->from.reg, REGTMP,r, p->scond);
                if(p->as != AMOVW)
                        o2 |= 1<<22;
                break;

        case 31:        /* mov/movbu L(R),R -> lr[b] */
                o1 = omvl(ctxt, p, &p->from, REGTMP);
                if(!o1)
                        break;
                r = p->from.reg;
                if(r == NREG)
                        r = o->param;
                o2 = olrr(ctxt, REGTMP,r, p->to.reg, p->scond);
                if(p->as == AMOVBU || p->as == AMOVBS || p->as == AMOVB)
                        o2 |= 1<<22;
                break;

        case 34:        /* mov $lacon,R */
                o1 = omvl(ctxt, p, &p->from, REGTMP);
                if(!o1)
                        break;

                o2 = oprrr(ctxt, AADD, p->scond);
                o2 |= REGTMP;
                r = p->from.reg;
                if(r == NREG)
                        r = o->param;
                o2 |= r << 16;
                if(p->to.type != D_NONE)
                        o2 |= p->to.reg << 12;
                break;

        case 35:        /* mov PSR,R */
                o1 = (2<<23) | (0xf<<16) | (0<<0);
                o1 |= (p->scond & C_SCOND) << 28;
                o1 |= (p->from.reg & 1) << 22;
                o1 |= p->to.reg << 12;
                break;

        case 36:        /* mov R,PSR */
                o1 = (2<<23) | (0x29f<<12) | (0<<4);
                if(p->scond & C_FBIT)
                        o1 ^= 0x010 << 12;
                o1 |= (p->scond & C_SCOND) << 28;
                o1 |= (p->to.reg & 1) << 22;
                o1 |= p->from.reg << 0;
                break;

        case 37:        /* mov $con,PSR */
                aclass(ctxt, &p->from);
                o1 = (2<<23) | (0x29f<<12) | (0<<4);
                if(p->scond & C_FBIT)
                        o1 ^= 0x010 << 12;
                o1 |= (p->scond & C_SCOND) << 28;
                o1 |= immrot(ctxt->instoffset);
                o1 |= (p->to.reg & 1) << 22;
                o1 |= p->from.reg << 0;
                break;

        case 38:        /* movm $con,oreg -> stm */
                o1 = (0x4 << 25);
                o1 |= p->from.offset & 0xffff;
                o1 |= p->to.reg << 16;
                aclass(ctxt, &p->to);
                goto movm;

        case 39:        /* movm oreg,$con -> ldm */
                o1 = (0x4 << 25) | (1 << 20);
                o1 |= p->to.offset & 0xffff;
                o1 |= p->from.reg << 16;
                aclass(ctxt, &p->from);
        movm:
                if(ctxt->instoffset != 0)
                        ctxt->diag("offset must be zero in MOVM; %P", p);
                o1 |= (p->scond & C_SCOND) << 28;
                if(p->scond & C_PBIT)
                        o1 |= 1 << 24;
                if(p->scond & C_UBIT)
                        o1 |= 1 << 23;
                if(p->scond & C_SBIT)
                        o1 |= 1 << 22;
                if(p->scond & C_WBIT)
                        o1 |= 1 << 21;
                break;

        case 40:        /* swp oreg,reg,reg */
                aclass(ctxt, &p->from);
                if(ctxt->instoffset != 0)
                        ctxt->diag("offset must be zero in SWP");
                o1 = (0x2<<23) | (0x9<<4);
                if(p->as != ASWPW)
                        o1 |= 1 << 22;
                o1 |= p->from.reg << 16;
                o1 |= p->reg << 0;
                o1 |= p->to.reg << 12;
                o1 |= (p->scond & C_SCOND) << 28;
                break;

        case 41:        /* rfe -> movm.s.w.u 0(r13),[r15] */
                o1 = 0xe8fd8000;
                break;

        case 50:        /* floating point store */
                v = regoff(ctxt, &p->to);
                r = p->to.reg;
                if(r == NREG)
                        r = o->param;
                o1 = ofsr(ctxt, p->as, p->from.reg, v, r, p->scond, p);
                break;

        case 51:        /* floating point load */
                v = regoff(ctxt, &p->from);
                r = p->from.reg;
                if(r == NREG)
                        r = o->param;
                o1 = ofsr(ctxt, p->as, p->to.reg, v, r, p->scond, p) | (1<<20);
                break;

        case 52:        /* floating point store, int32 offset UGLY */
                o1 = omvl(ctxt, p, &p->to, REGTMP);
                if(!o1)
                        break;
                r = p->to.reg;
                if(r == NREG)
                        r = o->param;
                o2 = oprrr(ctxt, AADD, p->scond) | (REGTMP << 12) | (REGTMP << 16) | r;
                o3 = ofsr(ctxt, p->as, p->from.reg, 0, REGTMP, p->scond, p);
                break;

        case 53:        /* floating point load, int32 offset UGLY */
                o1 = omvl(ctxt, p, &p->from, REGTMP);
                if(!o1)
                        break;
                r = p->from.reg;
                if(r == NREG)
                        r = o->param;
                o2 = oprrr(ctxt, AADD, p->scond) | (REGTMP << 12) | (REGTMP << 16) | r;
                o3 = ofsr(ctxt, p->as, p->to.reg, 0, REGTMP, p->scond, p) | (1<<20);
                break;

        case 54:        /* floating point arith */
                o1 = oprrr(ctxt, p->as, p->scond);
                rf = p->from.reg;
                rt = p->to.reg;
                r = p->reg;
                if(r == NREG) {
                        r = rt;
                        if(p->as == AMOVF || p->as == AMOVD || p->as == AMOVFD || p->as == AMOVDF ||
                                p->as == ASQRTF || p->as == ASQRTD || p->as == AABSF || p->as == AABSD)
                                r = 0;
                }
                o1 |= rf | (r<<16) | (rt<<12);
                break;

        case 56:        /* move to FP[CS]R */
                o1 = ((p->scond & C_SCOND) << 28) | (0xe << 24) | (1<<8) | (1<<4);
                o1 |= ((p->to.reg+1)<<21) | (p->from.reg << 12);
                break;

        case 57:        /* move from FP[CS]R */
                o1 = ((p->scond & C_SCOND) << 28) | (0xe << 24) | (1<<8) | (1<<4);
                o1 |= ((p->from.reg+1)<<21) | (p->to.reg<<12) | (1<<20);
                break;
        case 58:        /* movbu R,R */
                o1 = oprrr(ctxt, AAND, p->scond);
                o1 |= immrot(0xff);
                rt = p->to.reg;
                r = p->from.reg;
                if(p->to.type == D_NONE)
                        rt = 0;
                if(r == NREG)
                        r = rt;
                o1 |= (r<<16) | (rt<<12);
                break;

        case 59:        /* movw/bu R<<I(R),R -> ldr indexed */
                if(p->from.reg == NREG) {
                        if(p->as != AMOVW)
                                ctxt->diag("byte MOV from shifter operand");
                        goto mov;
                }
                if(p->from.offset&(1<<4))
                        ctxt->diag("bad shift in LDR");
                o1 = olrr(ctxt, p->from.offset, p->from.reg, p->to.reg, p->scond);
                if(p->as == AMOVBU)
                        o1 |= 1<<22;
                break;

        case 60:        /* movb R(R),R -> ldrsb indexed */
                if(p->from.reg == NREG) {
                        ctxt->diag("byte MOV from shifter operand");
                        goto mov;
                }
                if(p->from.offset&(~0xf))
                        ctxt->diag("bad shift in LDRSB");
                o1 = olhrr(ctxt, p->from.offset, p->from.reg, p->to.reg, p->scond);
                o1 ^= (1<<5)|(1<<6);
                break;

        case 61:        /* movw/b/bu R,R<<[IR](R) -> str indexed */
                if(p->to.reg == NREG)
                        ctxt->diag("MOV to shifter operand");
                o1 = osrr(ctxt, p->from.reg, p->to.offset, p->to.reg, p->scond);
                if(p->as == AMOVB || p->as == AMOVBS || p->as == AMOVBU)
                        o1 |= 1<<22;
                break;

        case 62:        /* case R -> movw       R<<2(PC),PC */
                if(o->flag & LPCREL) {
                        o1 = oprrr(ctxt, AADD, p->scond) | immrot(1) | p->from.reg << 16 | REGTMP << 12;
                        o2 = olrr(ctxt, REGTMP, REGPC, REGTMP, p->scond);
                        o2 |= 2<<7;
                        o3 = oprrr(ctxt, AADD, p->scond) | REGTMP | REGPC << 16 | REGPC << 12;
                } else {
                        o1 = olrr(ctxt, p->from.reg, REGPC, REGPC, p->scond);
                        o1 |= 2<<7;
                }
                break;

        case 63:        /* bcase */
                if(p->pcond != nil) {
                        rel = addrel(ctxt->cursym);
                        rel->off = ctxt->pc;
                        rel->siz = 4;
                        if(p->to.sym != nil && p->to.sym->type != 0) {
                                rel->sym = p->to.sym;
                                rel->add = p->to.offset;
                        } else {
                                rel->sym = ctxt->cursym;
                                rel->add = p->pcond->pc;
                        }
                        if(o->flag & LPCREL) {
                                rel->type = R_PCREL;
                                rel->add += ctxt->pc - p->pcrel->pc - 16 + rel->siz;
                        } else
                                rel->type = R_ADDR;
                        o1 = 0;
                }
                break;

        /* reloc ops */
        case 64:        /* mov/movb/movbu R,addr */
                o1 = omvl(ctxt, p, &p->to, REGTMP);
                if(!o1)
                        break;
                o2 = osr(ctxt, p->as, p->from.reg, 0, REGTMP, p->scond);
                if(o->flag & LPCREL) {
                        o3 = o2;
                        o2 = oprrr(ctxt, AADD, p->scond) | REGTMP | REGPC << 16 | REGTMP << 12;
                }
                break;

        case 65:        /* mov/movbu addr,R */
                o1 = omvl(ctxt, p, &p->from, REGTMP);
                if(!o1)
                        break;
                o2 = olr(ctxt, 0, REGTMP, p->to.reg, p->scond);
                if(p->as == AMOVBU || p->as == AMOVBS || p->as == AMOVB)
                        o2 |= 1<<22;
                if(o->flag & LPCREL) {
                        o3 = o2;
                        o2 = oprrr(ctxt, AADD, p->scond) | REGTMP | REGPC << 16 | REGTMP << 12;
                }
                break;

        case 68:        /* floating point store -> ADDR */
                o1 = omvl(ctxt, p, &p->to, REGTMP);
                if(!o1)
                        break;
                o2 = ofsr(ctxt, p->as, p->from.reg, 0, REGTMP, p->scond, p);
                if(o->flag & LPCREL) {
                        o3 = o2;
                        o2 = oprrr(ctxt, AADD, p->scond) | REGTMP | REGPC << 16 | REGTMP << 12;
                }
                break;

        case 69:        /* floating point load <- ADDR */
                o1 = omvl(ctxt, p, &p->from, REGTMP);
                if(!o1)
                        break;
                o2 = ofsr(ctxt, p->as, p->to.reg, 0, REGTMP, p->scond, p) | (1<<20);
                if(o->flag & LPCREL) {
                        o3 = o2;
                        o2 = oprrr(ctxt, AADD, p->scond) | REGTMP | REGPC << 16 | REGTMP << 12;
                }
                break;

        /* ArmV4 ops: */
        case 70:        /* movh/movhu R,O(R) -> strh */
                aclass(ctxt, &p->to);
                r = p->to.reg;
                if(r == NREG)
                        r = o->param;
                o1 = oshr(ctxt, p->from.reg, ctxt->instoffset, r, p->scond);
                break;
        case 71:        /* movb/movh/movhu O(R),R -> ldrsb/ldrsh/ldrh */
                aclass(ctxt, &p->from);
                r = p->from.reg;
                if(r == NREG)
                        r = o->param;
                o1 = olhr(ctxt, ctxt->instoffset, r, p->to.reg, p->scond);
                if(p->as == AMOVB || p->as == AMOVBS)
                        o1 ^= (1<<5)|(1<<6);
                else if(p->as == AMOVH || p->as == AMOVHS)
                        o1 ^= (1<<6);
                break;
        case 72:        /* movh/movhu R,L(R) -> strh */
                o1 = omvl(ctxt, p, &p->to, REGTMP);
                if(!o1)
                        break;
                r = p->to.reg;
                if(r == NREG)
                        r = o->param;
                o2 = oshrr(ctxt, p->from.reg, REGTMP,r, p->scond);
                break;
        case 73:        /* movb/movh/movhu L(R),R -> ldrsb/ldrsh/ldrh */
                o1 = omvl(ctxt, p, &p->from, REGTMP);
                if(!o1)
                        break;
                r = p->from.reg;
                if(r == NREG)
                        r = o->param;
                o2 = olhrr(ctxt, REGTMP, r, p->to.reg, p->scond);
                if(p->as == AMOVB || p->as == AMOVBS)
                        o2 ^= (1<<5)|(1<<6);
                else if(p->as == AMOVH || p->as == AMOVHS)
                        o2 ^= (1<<6);
                break;
        case 74:        /* bx $I */
                ctxt->diag("ABX $I");
                break;
        case 75:        /* bx O(R) */
                aclass(ctxt, &p->to);
                if(ctxt->instoffset != 0)
                        ctxt->diag("non-zero offset in ABX");
/*
                o1 =    oprrr(ctxt, AADD, p->scond) | immrot(0) | (REGPC<<16) | (REGLINK<<12);  // mov PC, LR
                o2 = ((p->scond&C_SCOND)<<28) | (0x12fff<<8) | (1<<4) | p->to.reg;              // BX R
*/
                // p->to.reg may be REGLINK
                o1 = oprrr(ctxt, AADD, p->scond);
                o1 |= immrot(ctxt->instoffset);
                o1 |= p->to.reg << 16;
                o1 |= REGTMP << 12;
                o2 = oprrr(ctxt, AADD, p->scond) | immrot(0) | (REGPC<<16) | (REGLINK<<12);     // mov PC, LR
                o3 = ((p->scond&C_SCOND)<<28) | (0x12fff<<8) | (1<<4) | REGTMP;         // BX Rtmp
                break;
        case 76:        /* bx O(R) when returning from fn*/
                ctxt->diag("ABXRET");
                break;
        case 77:        /* ldrex oreg,reg */
                aclass(ctxt, &p->from);
                if(ctxt->instoffset != 0)
                        ctxt->diag("offset must be zero in LDREX");
                o1 = (0x19<<20) | (0xf9f);
                o1 |= p->from.reg << 16;
                o1 |= p->to.reg << 12;
                o1 |= (p->scond & C_SCOND) << 28;
                break;
        case 78:        /* strex reg,oreg,reg */
                aclass(ctxt, &p->from);
                if(ctxt->instoffset != 0)
                        ctxt->diag("offset must be zero in STREX");
                o1 = (0x18<<20) | (0xf90);
                o1 |= p->from.reg << 16;
                o1 |= p->reg << 0;
                o1 |= p->to.reg << 12;
                o1 |= (p->scond & C_SCOND) << 28;
                break;
        case 80:        /* fmov zfcon,freg */
                if(p->as == AMOVD) {
                        o1 = 0xeeb00b00;        // VMOV imm 64
                        o2 = oprrr(ctxt, ASUBD, p->scond);
                } else {
                        o1 = 0x0eb00a00;        // VMOV imm 32
                        o2 = oprrr(ctxt, ASUBF, p->scond);
                }
                v = 0x70;       // 1.0
                r = p->to.reg;

                // movf $1.0, r
                o1 |= (p->scond & C_SCOND) << 28;
                o1 |= r << 12;
                o1 |= (v&0xf) << 0;
                o1 |= (v&0xf0) << 12;

                // subf r,r,r
                o2 |= r | (r<<16) | (r<<12);
                break;
        case 81:        /* fmov sfcon,freg */
                o1 = 0x0eb00a00;                // VMOV imm 32
                if(p->as == AMOVD)
                        o1 = 0xeeb00b00;        // VMOV imm 64
                o1 |= (p->scond & C_SCOND) << 28;
                o1 |= p->to.reg << 12;
                v = chipfloat5(ctxt, p->from.u.dval);
                o1 |= (v&0xf) << 0;
                o1 |= (v&0xf0) << 12;
                break;
        case 82:        /* fcmp freg,freg, */
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= (p->reg<<12) | (p->from.reg<<0);
                o2 = 0x0ef1fa10;        // VMRS R15
                o2 |= (p->scond & C_SCOND) << 28;
                break;
        case 83:        /* fcmp freg,, */
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= (p->from.reg<<12) | (1<<16);
                o2 = 0x0ef1fa10;        // VMRS R15
                o2 |= (p->scond & C_SCOND) << 28;
                break;
        case 84:        /* movfw freg,freg - truncate float-to-fix */
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= (p->from.reg<<0);
                o1 |= (p->to.reg<<12);
                break;
        case 85:        /* movwf freg,freg - fix-to-float */
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= (p->from.reg<<0);
                o1 |= (p->to.reg<<12);
                break;
        case 86:        /* movfw freg,reg - truncate float-to-fix */
                // macro for movfw freg,FTMP; movw FTMP,reg
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= (p->from.reg<<0);
                o1 |= (FREGTMP<<12);
                o2 = oprrr(ctxt, AMOVFW+AEND, p->scond);
                o2 |= (FREGTMP<<16);
                o2 |= (p->to.reg<<12);
                break;
        case 87:        /* movwf reg,freg - fix-to-float */
                // macro for movw reg,FTMP; movwf FTMP,freg
                o1 = oprrr(ctxt, AMOVWF+AEND, p->scond);
                o1 |= (p->from.reg<<12);
                o1 |= (FREGTMP<<16);
                o2 = oprrr(ctxt, p->as, p->scond);
                o2 |= (FREGTMP<<0);
                o2 |= (p->to.reg<<12);
                break;
        case 88:        /* movw reg,freg  */
                o1 = oprrr(ctxt, AMOVWF+AEND, p->scond);
                o1 |= (p->from.reg<<12);
                o1 |= (p->to.reg<<16);
                break;
        case 89:        /* movw freg,reg  */
                o1 = oprrr(ctxt, AMOVFW+AEND, p->scond);
                o1 |= (p->from.reg<<16);
                o1 |= (p->to.reg<<12);
                break;
        case 90:        /* tst reg  */
                o1 = oprrr(ctxt, ACMP+AEND, p->scond);
                o1 |= p->from.reg<<16;
                break;
        case 91:        /* ldrexd oreg,reg */
                aclass(ctxt, &p->from);
                if(ctxt->instoffset != 0)
                        ctxt->diag("offset must be zero in LDREX");
                o1 = (0x1b<<20) | (0xf9f);
                o1 |= p->from.reg << 16;
                o1 |= p->to.reg << 12;
                o1 |= (p->scond & C_SCOND) << 28;
                break;
        case 92:        /* strexd reg,oreg,reg */
                aclass(ctxt, &p->from);
                if(ctxt->instoffset != 0)
                        ctxt->diag("offset must be zero in STREX");
                o1 = (0x1a<<20) | (0xf90);
                o1 |= p->from.reg << 16;
                o1 |= p->reg << 0;
                o1 |= p->to.reg << 12;
                o1 |= (p->scond & C_SCOND) << 28;
                break;
        case 93:        /* movb/movh/movhu addr,R -> ldrsb/ldrsh/ldrh */
                o1 = omvl(ctxt, p, &p->from, REGTMP);
                if(!o1)
                        break;
                o2 = olhr(ctxt, 0, REGTMP, p->to.reg, p->scond);
                if(p->as == AMOVB || p->as == AMOVBS)
                        o2 ^= (1<<5)|(1<<6);
                else if(p->as == AMOVH || p->as == AMOVHS)
                        o2 ^= (1<<6);
                if(o->flag & LPCREL) {
                        o3 = o2;
                        o2 = oprrr(ctxt, AADD, p->scond) | REGTMP | REGPC << 16 | REGTMP << 12;
                }
                break;
        case 94:        /* movh/movhu R,addr -> strh */
                o1 = omvl(ctxt, p, &p->to, REGTMP);
                if(!o1)
                        break;
                o2 = oshr(ctxt, p->from.reg, 0, REGTMP, p->scond);
                if(o->flag & LPCREL) {
                        o3 = o2;
                        o2 = oprrr(ctxt, AADD, p->scond) | REGTMP | REGPC << 16 | REGTMP << 12;
                }
                break;
        case 95:        /* PLD off(reg) */
                o1 = 0xf5d0f000;
                o1 |= p->from.reg << 16;
                if(p->from.offset < 0) {
                        o1 &= ~(1 << 23);
                        o1 |= (-p->from.offset) & 0xfff;
                } else
                        o1 |= p->from.offset & 0xfff;
                break;
        case 96:        /* UNDEF */
                // This is supposed to be something that stops execution.
                // It's not supposed to be reached, ever, but if it is, we'd
                // like to be able to tell how we got there.  Assemble as
                // 0xf7fabcfd which is guaranteed to raise undefined instruction
                // exception.
                o1 = 0xf7fabcfd;
                break;
        case 97:        /* CLZ Rm, Rd */
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= p->to.reg << 12;
                o1 |= p->from.reg;
                break;
        case 98:        /* MULW{T,B} Rs, Rm, Rd */
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= p->to.reg << 16;
                o1 |= p->from.reg << 8;
                o1 |= p->reg;
                break;
        case 99:        /* MULAW{T,B} Rs, Rm, Rn, Rd */
                o1 = oprrr(ctxt, p->as, p->scond);
                o1 |= p->to.reg << 12;
                o1 |= p->from.reg << 8;
                o1 |= p->reg;
                o1 |= p->to.offset << 16;
                break;
        }
        
        out[0] = o1;
        out[1] = o2;
        out[2] = o3;
        out[3] = o4;
        out[4] = o5;
        out[5] = o6;
        return;

#ifdef NOTDEF
        v = p->pc;
        switch(o->size) {
        default:
                if(debug['a'])
                        Bprint(&bso, " %.8ux:\t\t%P\n", v, p);
                break;
        case 4:
                if(debug['a'])
                        Bprint(&bso, " %.8ux: %.8ux\t%P\n", v, o1, p);
                lputl(o1);
                break;
        case 8:
                if(debug['a'])
                        Bprint(&bso, " %.8ux: %.8ux %.8ux%P\n", v, o1, o2, p);
                lputl(o1);
                lputl(o2);
                break;
        case 12:
                if(debug['a'])
                        Bprint(&bso, " %.8ux: %.8ux %.8ux %.8ux%P\n", v, o1, o2, o3, p);
                lputl(o1);
                lputl(o2);
                lputl(o3);
                break;
        case 16:
                if(debug['a'])
                        Bprint(&bso, " %.8ux: %.8ux %.8ux %.8ux %.8ux%P\n",
                                v, o1, o2, o3, o4, p);
                lputl(o1);
                lputl(o2);
                lputl(o3);
                lputl(o4);
                break;
        case 20:
                if(debug['a'])
                        Bprint(&bso, " %.8ux: %.8ux %.8ux %.8ux %.8ux %.8ux%P\n",
                                v, o1, o2, o3, o4, o5, p);
                lputl(o1);
                lputl(o2);
                lputl(o3);
                lputl(o4);
                lputl(o5);
                break;
        case 24:
                if(debug['a'])
                        Bprint(&bso, " %.8ux: %.8ux %.8ux %.8ux %.8ux %.8ux %.8ux%P\n",
                                v, o1, o2, o3, o4, o5, o6, p);
                lputl(o1);
                lputl(o2);
                lputl(o3);
                lputl(o4);
                lputl(o5);
                lputl(o6);
                break;
        }
#endif
}

static int32
oprrr(Link *ctxt, int a, int sc)
{
        int32 o;

        o = (sc & C_SCOND) << 28;
        if(sc & C_SBIT)
                o |= 1 << 20;
        if(sc & (C_PBIT|C_WBIT))
                ctxt->diag(".nil/.W on dp instruction");
        switch(a) {
        case AMULU:
        case AMUL:      return o | (0x0<<21) | (0x9<<4);
        case AMULA:     return o | (0x1<<21) | (0x9<<4);
        case AMULLU:    return o | (0x4<<21) | (0x9<<4);
        case AMULL:     return o | (0x6<<21) | (0x9<<4);
        case AMULALU:   return o | (0x5<<21) | (0x9<<4);
        case AMULAL:    return o | (0x7<<21) | (0x9<<4);
        case AAND:      return o | (0x0<<21);
        case AEOR:      return o | (0x1<<21);
        case ASUB:      return o | (0x2<<21);
        case ARSB:      return o | (0x3<<21);
        case AADD:      return o | (0x4<<21);
        case AADC:      return o | (0x5<<21);
        case ASBC:      return o | (0x6<<21);
        case ARSC:      return o | (0x7<<21);
        case ATST:      return o | (0x8<<21) | (1<<20);
        case ATEQ:      return o | (0x9<<21) | (1<<20);
        case ACMP:      return o | (0xa<<21) | (1<<20);
        case ACMN:      return o | (0xb<<21) | (1<<20);
        case AORR:      return o | (0xc<<21);
        case AMOVB:
        case AMOVH:
        case AMOVW:     return o | (0xd<<21);
        case ABIC:      return o | (0xe<<21);
        case AMVN:      return o | (0xf<<21);
        case ASLL:      return o | (0xd<<21) | (0<<5);
        case ASRL:      return o | (0xd<<21) | (1<<5);
        case ASRA:      return o | (0xd<<21) | (2<<5);
        case ASWI:      return o | (0xf<<24);

        case AADDD:     return o | (0xe<<24) | (0x3<<20) | (0xb<<8) | (0<<4);
        case AADDF:     return o | (0xe<<24) | (0x3<<20) | (0xa<<8) | (0<<4);
        case ASUBD:     return o | (0xe<<24) | (0x3<<20) | (0xb<<8) | (4<<4);
        case ASUBF:     return o | (0xe<<24) | (0x3<<20) | (0xa<<8) | (4<<4);
        case AMULD:     return o | (0xe<<24) | (0x2<<20) | (0xb<<8) | (0<<4);
        case AMULF:     return o | (0xe<<24) | (0x2<<20) | (0xa<<8) | (0<<4);
        case ADIVD:     return o | (0xe<<24) | (0x8<<20) | (0xb<<8) | (0<<4);
        case ADIVF:     return o | (0xe<<24) | (0x8<<20) | (0xa<<8) | (0<<4);
        case ASQRTD:    return o | (0xe<<24) | (0xb<<20) | (1<<16) | (0xb<<8) | (0xc<<4);
        case ASQRTF:    return o | (0xe<<24) | (0xb<<20) | (1<<16) | (0xa<<8) | (0xc<<4);
        case AABSD:     return o | (0xe<<24) | (0xb<<20) | (0<<16) | (0xb<<8) | (0xc<<4);
        case AABSF:     return o | (0xe<<24) | (0xb<<20) | (0<<16) | (0xa<<8) | (0xc<<4);
        case ACMPD:     return o | (0xe<<24) | (0xb<<20) | (4<<16) | (0xb<<8) | (0xc<<4);
        case ACMPF:     return o | (0xe<<24) | (0xb<<20) | (4<<16) | (0xa<<8) | (0xc<<4);

        case AMOVF:     return o | (0xe<<24) | (0xb<<20) | (0<<16) | (0xa<<8) | (4<<4);
        case AMOVD:     return o | (0xe<<24) | (0xb<<20) | (0<<16) | (0xb<<8) | (4<<4);

        case AMOVDF:    return o | (0xe<<24) | (0xb<<20) | (7<<16) | (0xa<<8) | (0xc<<4) |
                        (1<<8); // dtof
        case AMOVFD:    return o | (0xe<<24) | (0xb<<20) | (7<<16) | (0xa<<8) | (0xc<<4) |
                        (0<<8); // dtof

        case AMOVWF:
                        if((sc & C_UBIT) == 0)
                                o |= 1<<7;      /* signed */
                        return o | (0xe<<24) | (0xb<<20) | (8<<16) | (0xa<<8) | (4<<4) |
                                (0<<18) | (0<<8);       // toint, double
        case AMOVWD:
                        if((sc & C_UBIT) == 0)
                                o |= 1<<7;      /* signed */
                        return o | (0xe<<24) | (0xb<<20) | (8<<16) | (0xa<<8) | (4<<4) |
                                (0<<18) | (1<<8);       // toint, double

        case AMOVFW:
                        if((sc & C_UBIT) == 0)
                                o |= 1<<16;     /* signed */
                        return o | (0xe<<24) | (0xb<<20) | (8<<16) | (0xa<<8) | (4<<4) |
                                (1<<18) | (0<<8) | (1<<7);      // toint, double, trunc
        case AMOVDW:
                        if((sc & C_UBIT) == 0)
                                o |= 1<<16;     /* signed */
                        return o | (0xe<<24) | (0xb<<20) | (8<<16) | (0xa<<8) | (4<<4) |
                                (1<<18) | (1<<8) | (1<<7);      // toint, double, trunc

        case AMOVWF+AEND:       // copy WtoF
                return o | (0xe<<24) | (0x0<<20) | (0xb<<8) | (1<<4);
        case AMOVFW+AEND:       // copy FtoW
                return o | (0xe<<24) | (0x1<<20) | (0xb<<8) | (1<<4);
        case ACMP+AEND: // cmp imm
                return o | (0x3<<24) | (0x5<<20);

        case ACLZ:
                // CLZ doesn't support .nil
                return (o & (0xf<<28)) | (0x16f<<16) | (0xf1<<4);

        case AMULWT:
                return (o & (0xf<<28)) | (0x12 << 20) | (0xe<<4);
        case AMULWB:
                return (o & (0xf<<28)) | (0x12 << 20) | (0xa<<4);
        case AMULAWT:
                return (o & (0xf<<28)) | (0x12 << 20) | (0xc<<4);
        case AMULAWB:
                return (o & (0xf<<28)) | (0x12 << 20) | (0x8<<4);

        case ABL: // BLX REG
                return (o & (0xf<<28)) | (0x12fff3 << 4);
        }
        ctxt->diag("bad rrr %d", a);
        prasm(ctxt->curp);
        return 0;
}

static int32
opbra(Link *ctxt, int a, int sc)
{

        if(sc & (C_SBIT|C_PBIT|C_WBIT))
                ctxt->diag(".nil/.nil/.W on bra instruction");
        sc &= C_SCOND;
        if(a == ABL || a == ADUFFZERO || a == ADUFFCOPY)
                return (sc<<28)|(0x5<<25)|(0x1<<24);
        if(sc != 0xe)
                ctxt->diag(".COND on bcond instruction");
        switch(a) {
        case ABEQ:      return (0x0<<28)|(0x5<<25);
        case ABNE:      return (0x1<<28)|(0x5<<25);
        case ABCS:      return (0x2<<28)|(0x5<<25);
        case ABHS:      return (0x2<<28)|(0x5<<25);
        case ABCC:      return (0x3<<28)|(0x5<<25);
        case ABLO:      return (0x3<<28)|(0x5<<25);
        case ABMI:      return (0x4<<28)|(0x5<<25);
        case ABPL:      return (0x5<<28)|(0x5<<25);
        case ABVS:      return (0x6<<28)|(0x5<<25);
        case ABVC:      return (0x7<<28)|(0x5<<25);
        case ABHI:      return (0x8<<28)|(0x5<<25);
        case ABLS:      return (0x9<<28)|(0x5<<25);
        case ABGE:      return (0xa<<28)|(0x5<<25);
        case ABLT:      return (0xb<<28)|(0x5<<25);
        case ABGT:      return (0xc<<28)|(0x5<<25);
        case ABLE:      return (0xd<<28)|(0x5<<25);
        case AB:        return (0xe<<28)|(0x5<<25);
        }
        ctxt->diag("bad bra %A", a);
        prasm(ctxt->curp);
        return 0;
}

static int32
olr(Link *ctxt, int32 v, int b, int r, int sc)
{
        int32 o;

        if(sc & C_SBIT)
                ctxt->diag(".nil on LDR/STR instruction");
        o = (sc & C_SCOND) << 28;
        if(!(sc & C_PBIT))
                o |= 1 << 24;
        if(!(sc & C_UBIT))
                o |= 1 << 23;
        if(sc & C_WBIT)
                o |= 1 << 21;
        o |= (1<<26) | (1<<20);
        if(v < 0) {
                if(sc & C_UBIT)
                        ctxt->diag(".U on neg offset");
                v = -v;
                o ^= 1 << 23;
        }
        if(v >= (1<<12) || v < 0)
                ctxt->diag("literal span too large: %d (R%d)\n%P", v, b, ctxt->printp);
        o |= v;
        o |= b << 16;
        o |= r << 12;
        return o;
}

static int32
olhr(Link *ctxt, int32 v, int b, int r, int sc)
{
        int32 o;

        if(sc & C_SBIT)
                ctxt->diag(".nil on LDRH/STRH instruction");
        o = (sc & C_SCOND) << 28;
        if(!(sc & C_PBIT))
                o |= 1 << 24;
        if(sc & C_WBIT)
                o |= 1 << 21;
        o |= (1<<23) | (1<<20)|(0xb<<4);
        if(v < 0) {
                v = -v;
                o ^= 1 << 23;
        }
        if(v >= (1<<8) || v < 0)
                ctxt->diag("literal span too large: %d (R%d)\n%P", v, b, ctxt->printp);
        o |= (v&0xf)|((v>>4)<<8)|(1<<22);
        o |= b << 16;
        o |= r << 12;
        return o;
}

static int32
osr(Link *ctxt, int a, int r, int32 v, int b, int sc)
{
        int32 o;

        o = olr(ctxt, v, b, r, sc) ^ (1<<20);
        if(a != AMOVW)
                o |= 1<<22;
        return o;
}

static int32
oshr(Link *ctxt, int r, int32 v, int b, int sc)
{
        int32 o;

        o = olhr(ctxt, v, b, r, sc) ^ (1<<20);
        return o;
}


static int32
osrr(Link *ctxt, int r, int i, int b, int sc)
{

        return olr(ctxt, i, b, r, sc) ^ ((1<<25) | (1<<20));
}

static int32
oshrr(Link *ctxt, int r, int i, int b, int sc)
{
        return olhr(ctxt, i, b, r, sc) ^ ((1<<22) | (1<<20));
}

static int32
olrr(Link *ctxt, int i, int b, int r, int sc)
{

        return olr(ctxt, i, b, r, sc) ^ (1<<25);
}

static int32
olhrr(Link *ctxt, int i, int b, int r, int sc)
{
        return olhr(ctxt, i, b, r, sc) ^ (1<<22);
}

static int32
ofsr(Link *ctxt, int a, int r, int32 v, int b, int sc, Prog *p)
{
        int32 o;

        if(sc & C_SBIT)
                ctxt->diag(".nil on FLDR/FSTR instruction");
        o = (sc & C_SCOND) << 28;
        if(!(sc & C_PBIT))
                o |= 1 << 24;
        if(sc & C_WBIT)
                o |= 1 << 21;
        o |= (6<<25) | (1<<24) | (1<<23) | (10<<8);
        if(v < 0) {
                v = -v;
                o ^= 1 << 23;
        }
        if(v & 3)
                ctxt->diag("odd offset for floating point op: %d\n%P", v, p);
        else
        if(v >= (1<<10) || v < 0)
                ctxt->diag("literal span too large: %d\n%P", v, p);
        o |= (v>>2) & 0xFF;
        o |= b << 16;
        o |= r << 12;

        switch(a) {
        default:
                ctxt->diag("bad fst %A", a);
        case AMOVD:
                o |= 1 << 8;
        case AMOVF:
                break;
        }
        return o;
}

static int32
omvl(Link *ctxt, Prog *p, Addr *a, int dr)
{
        int32 v, o1;
        if(!p->pcond) {
                aclass(ctxt, a);
                v = immrot(~ctxt->instoffset);
                if(v == 0) {
                        ctxt->diag("missing literal");
                        prasm(p);
                        return 0;
                }
                o1 = oprrr(ctxt, AMVN, p->scond&C_SCOND);
                o1 |= v;
                o1 |= dr << 12;
        } else {
                v = p->pcond->pc - p->pc - 8;
                o1 = olr(ctxt, v, REGPC, dr, p->scond&C_SCOND);
        }
        return o1;
}

int
chipzero5(Link *ctxt, float64 e)
{
        // We use GOARM=7 to gate the use of VFPv3 vmov (imm) instructions.
        if(ctxt->goarm < 7 || e != 0)
                return -1;
        return 0;
}

int
chipfloat5(Link *ctxt, float64 e)
{
        int n;
        ulong h1;
        int32 l, h;
        uint64 ei;

        // We use GOARM=7 to gate the use of VFPv3 vmov (imm) instructions.
        if(ctxt->goarm < 7)
                goto no;

        memmove(&ei, &e, 8);
        l = (int32)ei;
        h = (int32)(ei>>32);

        if(l != 0 || (h&0xffff) != 0)
                goto no;
        h1 = h & 0x7fc00000;
        if(h1 != 0x40000000 && h1 != 0x3fc00000)
                goto no;
        n = 0;

        // sign bit (a)
        if(h & 0x80000000)
                n |= 1<<7;

        // exp sign bit (b)
        if(h1 == 0x3fc00000)
                n |= 1<<6;

        // rest of exp and mantissa (cd-efgh)
        n |= (h >> 16) & 0x3f;

//print("match %.8lux %.8lux %d\n", l, h, n);
        return n;

no:
        return -1;
}

/* [<][>][^][v][top][bottom][index][help] */