/*
 * Copyright (C) 2012-2017,2019-2020  Free Software Foundation, Inc.
 *
 * This file is part of GNU lightning.
 *
 * GNU lightning is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * GNU lightning is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * Authors:
 *      Paulo Cesar Pereira de Andrade
 */

#define _s20P(d)                      ((d) >= -(int)0x80000 && d <= 0x7ffff)
#define _s24P(d)                      ((d) >= -(int)0x800000 && d <= 0x7fffff)
#define _u3(v)                        ((v) & 0x7)
#define _u4(v)                        ((v) & 0xf)
#define _u5(v)                        ((v) & 0x1f)
#define _u8(v)                        ((v) & 0xff)
#define _u12(v)                       ((v) & 0xfff)
#define _u13(v)                       ((v) & 0x1fff)
#define _u16(v)                       ((v) & 0xffff)
#define _u24(v)                       ((v) & 0xffffff)

#define ARM_CC_EQ                     0x00000000      /* Z=1 */
#define ARM_CC_NE                     0x10000000      /* Z=0 */
#define ARM_CC_HS                     0x20000000      /* C=1 */
#define ARM_CC_LO                     0x30000000      /* C=0 */
#define ARM_CC_MI                     0x40000000      /* N=1 */
#define ARM_CC_VS                     0x60000000      /* V=1 */
#define ARM_CC_VC                     0x70000000      /* V=0 */
#define ARM_CC_HI                     0x80000000      /* C=1 && Z=0 */
#define ARM_CC_LS                     0x90000000      /* C=0 || Z=1 */
#define ARM_CC_GE                     0xa0000000      /* N=V */
#define ARM_CC_LT                     0xb0000000      /* N!=V */
#define ARM_CC_GT                     0xc0000000      /* Z=0 && N=V */
#define ARM_CC_LE                     0xd0000000      /* Z=1 || N!=V */
#define ARM_CC_AL                     0xe0000000      /* always */
#define ARM_CC_NV                     0xf0000000      /* reserved */
#define THUMB_MOV                         0x4600
#define THUMB_MOVI                        0x2000
#define THUMB2_MOVI                   0xf0400000
#define THUMB2_MOVWI                  0xf2400000
#define THUMB2_MOVTI                  0xf2c00000
#define THUMB_MVN                         0x43c0
#define THUMB2_MVN                    0xea600000
#define THUMB2_MVNI                   0xf0600000
#define ARM_S                         0x00100000 /* set flags */
#define THUMB_ADD                         0x1800
#define THUMB_ADDX                        0x4400
#define THUMB2_ADD                    0xeb000000
#define THUMB_ADDI3                       0x1c00
#define THUMB_ADDI8                       0x3000
#define THUMB2_ADDI                   0xf1000000
#define THUMB2_ADDWI                  0xf2000000
#define THUMB_ADC                         0x4140
#define THUMB2_ADC                    0xeb400000
#define THUMB2_ADCI                   0xf1400000
#define THUMB_SUB                         0x1a00
#define THUMB2_SUB                    0xeba00000
#define THUMB_SUBI3                       0x1e00
#define THUMB_SUBI8                       0x3800
#define THUMB2_SUBI                   0xf1a00000
#define THUMB2_SUBWI                  0xf2a00000
#define THUMB_SBC                         0x4180
#define THUMB2_SBC                    0xeb600000
#define THUMB2_SBCI                   0xf1600000
#define THUMB_RSBI                        0x4240
#define THUMB2_RSBI                   0xf1c00000
#define THUMB_MUL                         0x4340
#define THUMB2_MUL                    0xfb00f000
#define THUMB2_UMULL                  0xfba00000
#define THUMB2_SMULL                  0xfb800000
#define THUMB_MLS                     0xfb000010
#define THUMB2_SDIV                   0xfb90f0f0
#define THUMB2_UDIV                   0xfbb0f0f0
#define THUMB_AND                         0x4000
#define THUMB2_AND                    0xea000000
#define THUMB2_ANDI                   0xf0000000
#define THUMB2_BIC                    0xea200000
#define THUMB2_BICI                   0xf0200000
#define THUMB_ORR                         0x4300
#define THUMB2_ORR                    0xea400000
#define THUMB2_ORRI                   0xf0400000
#define THUMB_EOR                         0x4040
#define THUMB2_EOR                    0xea800000
#define THUMB2_EORI                   0xf0800000
#define THUMB_REV                         0xba00
#define THUMB2_REV                    0xfa90f080
#define THUMB_SXTB                        0xb240
#define THUMB2_SXTB                   0xfa40f080
#define THUMB_UXTB                        0xb2c0
#define THUMB2_UXTB                   0xfa50f080
#define THUMB_SXTH                        0xb200
#define THUMB2_SXTH                   0xfa00f080
#define THUMB_UXTH                        0xb280
#define THUMB2_UXTH                   0xfa10f080
#define ARM_LSL                       0x00000000
#define THUMB_LSL                         0x4080
#define THUMB2_LSL                    0xfa00f000
#define THUMB_LSLI                        0x0000
#define THUMB2_LSLI                   0xea4f0000
#define ARM_LSR                       0x00000020
#define THUMB_LSR                         0x40c0
#define THUMB2_LSR                    0xfa20f000
#define THUMB_LSRI                        0x0800
#define THUMB2_LSRI                   0xea4f0010
#define ARM_ASR                       0x00000040
#define THUMB_ASR                         0x4100
#define THUMB2_ASR                    0xfa40f000
#define THUMB_ASRI                        0x1000
#define THUMB2_ASRI                   0xea4f0020
#define THUMB_CMP                         0x4280
#define THUMB_CMPX                        0x4500
#define THUMB2_CMP                    0xebb00000
#define THUMB_CMPI                        0x2800
#define THUMB2_CMPI                   0xf1b00000
#define THUMB2_CMN                    0xeb100000
#define THUMB2_CMNI                   0xf1100000
#define THUMB_TST                         0x4200
#define THUMB2_TST                    0xea100000
#define THUMB2_TSTI                   0xf0100000
#define THUMB_BLX                         0x4780
#define THUMB_BX                          0x4700
#define THUMB_CC_B                        0xd000
#define THUMB_B                           0xe000
#define THUMB2_CC_B                   0xf0008000
#define THUMB2_B                      0xf0009000
#define THUMB2_BLI                    0xf000d000
#define THUMB2_BLXI                   0xf000c000
#define THUMB2_P                      0x00000400
#define THUMB2_U                      0x00000200
#define THUMB_LDRSB                       0x5600
#define THUMB2_LDRSB                  0xf9100000
#define THUMB2_LDRSBI                 0xf9100c00
#define THUMB2_LDRSBWI                0xf9900000
#define THUMB_LDRB                        0x5c00
#define THUMB2_LDRB                   0xf8100000
#define THUMB_LDRBI                       0x7800
#define THUMB2_LDRBI                  0xf8100c00
#define THUMB2_LDRBWI                 0xf8900000
#define THUMB_LDRSH                       0x5e00
#define THUMB2_LDRSH                  0xf9300000
#define THUMB2_LDRSHI                 0xf9300c00
#define THUMB2_LDRSHWI                0xf9b00000
#define THUMB_LDRH                        0x5a00
#define THUMB2_LDRH                   0xf8300000
#define THUMB_LDRHI                       0x8800
#define THUMB2_LDRHI                  0xf8300c00
#define THUMB2_LDRHWI                 0xf8b00000
#define THUMB_LDR                         0x5800
#define THUMB2_LDR                    0xf8500000
#define THUMB2_LDRP                   0xf85f0000
#define THUMB_LDRI                        0x6800
#define THUMB_LDRISP                      0x9800
#define THUMB2_LDRI                   0xf8500c00
#define THUMB2_LDRWI                  0xf8d00000
#define THUMB_STRB                        0x5400
#define THUMB2_STRB                   0xf8000000
#define THUMB_STRBI                       0x7000
#define THUMB2_STRBI                  0xf8000c00
#define THUMB2_STRBWI                 0xf8800000
#define THUMB_STRH                        0x5200
#define THUMB2_STRH                   0xf8200000
#define THUMB_STRHI                       0x8000
#define THUMB2_STRHI                  0xf8200c00
#define THUMB2_STRHWI                 0xf8a00000
#define THUMB_STR                         0x5000
#define THUMB2_STR                    0xf8400000
#define THUMB_STRI                        0x6000
#define THUMB2_STRWI                   0xf8c00000
#define THUMB_STRISP                      0x9000
#define THUMB2_STRI                   0xf8400c00
#define THUMB2_LDM_W                  0x00200000
#define THUMB2_PUSH                   0xe92d0000
#define THUMB_DMB                     0xf3bf8f50
#define THUMB_LDREX                   0xe8500f00
#define THUMB_STREX                   0xe8400000
#define THUMB_BRK                         0xbe00

#define _NOREG (jit_gpr_regno(_PC))

#define JIT_RELOC_B JIT_RELOC_FLAG_0

static void
emit_wide_thumb(jit_state_t *_jit, uint32_t inst)
{
  emit_u16(_jit, inst >> 16);
  emit_u16_with_pool(_jit, inst & 0xffff);
}

static uint32_t
rotate_left(uint32_t v, uint32_t n) {
  if (n == 0) {
    return v;
  }
  ASSERT(n < 32);
  return (v << n | v >> (32 - n));
}

static int
encode_arm_immediate(unsigned int v)
{
  unsigned int        a, i;

  for (i = 0; i < 32; i += 2)
    if ((a = rotate_left(v, i)) <= 0xff)
      return (a | (i << 7));

  return (-1);
}

static int
encode_thumb_immediate(unsigned int v)
{
  int                 i;
  unsigned int        m;
  unsigned int        n;
  /* 00000000 00000000 00000000 abcdefgh */
  if ((v & 0xff) == v)
    return (v);
  /* 00000000 abcdefgh 00000000 abcdefgh */
  if ((v & 0xff00ff) == v && ((v & 0xff0000) >> 16) == (v & 0xff))
    return ((v & 0xff) | (1 << 12));
  /* abcdefgh 00000000 abcdefgh 00000000 */
  if (((v & 0xffff0000) >> 16) == (v & 0xffff) && (v & 0xff) == 0)
    return (((v & 0x0000ff00) >> 8) | (2 << 12));
  /* abcdefgh abcdefgh abcdefgh abcdefgh */
  if ( (v &    0xff)        == ((v &     0xff00) >>  8) &&
       ((v &   0xff00) >> 8) == ((v &   0xff0000) >> 16) &&
       ((v & 0xff0000) << 8) ==  (v & 0xff000000))
    return ((v & 0xff) | (3 << 12));
  /* 1bcdefgh << 24 ... 1bcdefgh << 1 */
  for (i = 8, m = 0xff000000, n = 0x80000000;
       i < 23; i++, m >>= 1,  n >>= 1) {
    if ((v & m) == v && (v & n)) {
      v >>= 32 - i;
      if (!(i & 1))
        v &= 0x7f;
      i >>= 1;
      return (((i & 7) << 12) | ((i & 8) << 23) | v);
    }
  }
  return (-1);
}

static int
encode_thumb_word_immediate(unsigned int v)
{
  if ((v & 0xfffff000) == 0)
    return (((v & 0x800) << 15) | ((v & 0x700) << 4) | (v & 0xff));
  return (-1);
}

static uint32_t
read_wide_thumb(uint32_t *loc)
{
  uint16_t *sloc = (uint16_t*)loc;
  return (sloc[0] << 16) | sloc[1];
}

static void
write_wide_thumb(uint32_t *loc, uint32_t v)
{
  uint16_t *sloc = (uint16_t *)loc;
  sloc[0] = v >> 16;
  sloc[1] = v & 0xffff;
}

static int
offset_in_jmp_range(int32_t offset, int flags)
{
  if (!(offset & 1) && flags | JIT_RELOC_B)
    return 0;
  else
    return -0x1000000 <= offset && offset <= 0xffffff;
}

static int32_t
decode_thumb_jump(uint32_t v)
{
  uint32_t s  = (v >> 26) & 1;
  uint32_t j1 = (v >> 13) & 1;
  uint32_t j2 = (v >> 11) & 1;
  uint32_t i1 = s ? j1 : !j1;
  uint32_t i2 = s ? j2 : !j2;
  uint32_t hi = (v >> 16) & 0x3ff;
  uint32_t lo = v & 0x7ff;

  int32_t ret = s << 31;
  ret >>= 8;
  ret |= i1 << 22;
  ret |= i2 << 21;
  ret |= hi << 11;
  ret |= lo;
  return ret << 1;
}

static const uint32_t thumb_jump_mask = 0xf800d000;

static uint32_t
encode_thumb_jump(int32_t v)
{
  ASSERT(offset_in_jmp_range(v, 0));
  v >>= 1;
  uint32_t s  = !!(v & 0x800000);
  uint32_t i1 = !!(v & 0x400000);
  uint32_t i2 = !!(v & 0x200000);
  uint32_t j1 = s ? i1 : !i1;
  uint32_t j2 = s ? i2 : !i2;
  uint32_t ret = (s<<26)|((v&0x1ff800)<<5)|(j1<<13)|(j2<<11)|(v&0x7ff);
  ASSERT(decode_thumb_jump(ret) == v << 1);
  ASSERT((ret & thumb_jump_mask) == 0);
  return ret;
}

static uint32_t
patch_thumb_jump(uint32_t inst, int32_t v)
{
  inst &= thumb_jump_mask;
  if (!(v & 1)) {
    ASSERT(inst == THUMB2_BLI || inst == THUMB2_BLXI);
    v = (v + 2) & ~2;
    inst = THUMB2_BLXI;
  }
  return inst | encode_thumb_jump(v);
}

static int32_t
read_jmp_offset(uint32_t *loc)
{
  return decode_thumb_jump(read_wide_thumb(loc));
}

static void
patch_jmp_offset(uint32_t *loc, int32_t v)
{
  write_wide_thumb(loc, patch_thumb_jump(read_wide_thumb(loc), v));
}

static void
patch_veneer_jmp_offset(uint32_t *loc, int32_t v)
{
  ASSERT(!(v & 1));
  patch_jmp_offset(loc, v | 1);
}

static jit_reloc_t
emit_thumb_jump(jit_state_t *_jit, uint32_t inst)
{
  while (1) {
    uint8_t *pc_base = _jit->pc.uc + 4;
    int32_t off = (uint8_t*)jit_address(_jit) - pc_base;
    enum jit_reloc_kind kind = JIT_RELOC_JMP_WITH_VENEER;
    if (inst == THUMB2_B)
      kind |= JIT_RELOC_B;
    jit_reloc_t ret = jit_reloc (_jit, kind, 0, _jit->pc.uc, pc_base, 0);
    uint8_t thumb_jump_width = 24;
    if (add_pending_literal(_jit, ret, thumb_jump_width - 1)) {
      emit_wide_thumb(_jit, patch_thumb_jump(inst, off));
      return ret;
    }
  }
}

static int
offset_in_jcc_range(int32_t v, int flags)
{
  if (!(v & 1))
    return 0;
  else
    return -0x100000 <= v && v <= 0xfffff;
}

static int32_t
decode_thumb_cc_jump(uint32_t v)
{
  uint32_t s  = (v >> 26) & 1;
  uint32_t j1 = (v >> 13) & 1;
  uint32_t j2 = (v >> 11) & 1;
  uint32_t hi = (v >> 16) & 0x3f;
  uint32_t lo = v & 0x7ff;

  int32_t ret = s << 31;
  ret >>= 12;
  ret |= j2 << 18;
  ret |= j1 << 17;
  ret |= hi << 11;
  ret |= lo;
  return ret << 1;
}

static const uint32_t thumb_cc_jump_mask = 0xfbc0d000;

static uint32_t
encode_thumb_cc_jump(int32_t v)
{
  ASSERT(offset_in_jcc_range(v, 0));
  v >>= 1;
  uint32_t s  = !!(v & 0x80000);
  uint32_t j2 = !!(v & 0x40000);
  uint32_t j1 = !!(v & 0x20000);
  uint32_t hi = (v >> 11) & 0x3f;
  uint32_t lo = v & 0x7ff;
  uint32_t ret = (s<<26)|(hi << 16)|(j1<<13)|(j2<<11)|lo;
  ASSERT(decode_thumb_cc_jump(ret) == v << 1);
  ASSERT((ret & thumb_cc_jump_mask) == 0);
  return ret;
}

static uint32_t
patch_thumb_cc_jump(uint32_t inst, int32_t v)
{
  return (inst & thumb_cc_jump_mask) | encode_thumb_cc_jump(v);
}

static int32_t
read_jcc_offset(uint32_t *loc)
{
  return decode_thumb_cc_jump(read_wide_thumb(loc));
}

static void
patch_jcc_offset(uint32_t *loc, int32_t v)
{
  write_wide_thumb(loc, patch_thumb_cc_jump(read_wide_thumb(loc), v));
}

static void
patch_veneer_jcc_offset(uint32_t *loc, int32_t v)
{
  ASSERT(!(v & 1));
  patch_jcc_offset(loc, v | 1);
}

static jit_reloc_t
emit_thumb_cc_jump(jit_state_t *_jit, uint32_t inst)
{
  while (1) {
    uint8_t *pc_base = _jit->pc.uc + 4;
    int32_t off = (uint8_t*)jit_address(_jit) - pc_base;
    jit_reloc_t ret =
      jit_reloc (_jit, JIT_RELOC_JCC_WITH_VENEER, 0, _jit->pc.uc, pc_base, 0);
    uint8_t thumb_cc_jump_width = 20;
    if (add_pending_literal(_jit, ret, thumb_cc_jump_width - 1)) {
      emit_wide_thumb(_jit, patch_thumb_cc_jump(inst, off));
      return ret;
    }
  }
}

static void
torrr(jit_state_t *_jit, int o, int rn, int rd, int rm)
{
  ASSERT(!(o & 0xf0f0f));
  emit_wide_thumb(_jit, o|(_u4(rn)<<16)|(_u4(rd)<<8)|_u4(rm));
}

static void
torxr(jit_state_t *_jit, int o, int rn, int rt, int rm)
{
  ASSERT(!(o & 0xf0f0f));
  emit_wide_thumb(_jit, o|(_u4(rn)<<16)|(_u4(rt)<<12)|_u4(rm));
}

static void
torrrr(jit_state_t *_jit, int o, int rn, int rl, int rh, int rm)
{
  ASSERT(!(o & 0x000fff0f));
  emit_wide_thumb(_jit, o|(_u4(rn)<<16)|(_u4(rl)<<12)|(_u4(rh)<<8)|_u4(rm));
}

static void
torri(jit_state_t *_jit, int o, int rn, int rd, int im)
{
  ASSERT(!(o  & 0x0c0f7fff));
  ASSERT(!(im & 0xfbff8f00));
  emit_wide_thumb(_jit, o|(_u4(rn)<<16)|(_u4(rd)<<8)|im);
}

static void
torri8(jit_state_t *_jit, int o, int rn, int rt, int im)
{
  ASSERT(!(o  & 0x000ff0ff));
  ASSERT(!(im & 0xffffff00));
  emit_wide_thumb(_jit, o|(_u4(rn)<<16)|(_u4(rt)<<12)|im);
}

static void
torri12(jit_state_t *_jit, int o, int rn, int rt, int im)
{
  ASSERT(!(o  & 0x000fffff));
  ASSERT(!(im & 0xfffff000));
  emit_wide_thumb(_jit, o|(_u4(rn)<<16)|(_u4(rt)<<12)|im);
}

static void
tshift(jit_state_t *_jit, int o, int rd, int rm, int im)
{
  ASSERT(!(o & 0x7fcf));
  ASSERT(im >= 0 && im < 32);
  emit_wide_thumb(_jit, o|((im&0x1c)<<10)|(_u4(rd)<<8)|((im&3)<<6)|_u4(rm));
}

static void
toriw(jit_state_t *_jit, int o, int rd, int im)
{
  ASSERT(!(im & 0xffff0000));
  emit_wide_thumb(_jit, o|((im&0xf000)<<4)|((im&0x800)<<15)|((im&0x700)<<4)|(_u4(rd)<<8)|(im&0xff));
}

static jit_reloc_t
tcb(jit_state_t *_jit, int cc)
{
  ASSERT(!(cc & 0xfffffff));
  ASSERT(cc != ARM_CC_AL && cc != ARM_CC_NV);
  cc = ((uint32_t)cc) >> 6;
  return emit_thumb_cc_jump(_jit, THUMB2_CC_B|cc);
}

static jit_reloc_t
tb(jit_state_t *_jit, int o)
{
  ASSERT(!(o & 0x07ff2fff));
  return emit_thumb_jump(_jit, o);
}

static void
T1_ORR(jit_state_t *_jit, int32_t rdn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_ORR|(_u3(rm)<<3)|_u3(rdn));
}

static void
T2_ORR(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_ORR,rn,rd,rm);
}

static void
T2_ORRI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_ORRI,rn,rd,im);
}

static void
T1_EOR(jit_state_t *_jit, int32_t rdn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_EOR|(_u3(rm)<<3)|_u3(rdn));
}

static void
T2_EOR(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_EOR,rn,rd,rm);
}

static void
T2_EORI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_EORI,rn,rd,im);
}

static void
T1_MOV(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_MOV|((_u4(rd)&8)<<4)|(_u4(rm)<<3)|(rd&7));
}

static void
T1_MOVI(jit_state_t *_jit, int32_t rd, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_MOVI|(_u3(rd)<<8)|_u8(im));
}

static void
T2_MOVI(jit_state_t *_jit, int32_t rd, int32_t im)
{
  return torri(_jit, THUMB2_MOVI,_NOREG,rd,im);
}

static void
T2_MOVWI(jit_state_t *_jit, int32_t rd, int32_t im)
{
  return toriw(_jit, THUMB2_MOVWI,rd,im);
}

static void
T2_MOVTI(jit_state_t *_jit, int32_t rd, int32_t im)
{
  return toriw(_jit, THUMB2_MOVTI,rd,im);
}

static void
T1_MVN(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_MVN|(_u3(rm)<<3)|_u3(rd));
}

static void
T2_MVN(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  return torrr(_jit, THUMB2_MVN,_NOREG,rd,rm);
}

static void
T2_MVNI(jit_state_t *_jit, int32_t rd, int32_t im)
{
  return torri(_jit, THUMB2_MVNI,_NOREG,rd,im);
}

static void
T1_NOT(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  return T1_MVN(_jit, rd,rm);
}

static void
T2_NOT(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  return T2_MVN(_jit, rd,rm);
}

static void
T1_NOP(jit_state_t *_jit)
{
  emit_u16_with_pool(_jit, 0xbf00);
}

static void
T1_ADD(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_ADD|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rd));
}

static void
T1_ADDX(jit_state_t *_jit, int32_t rdn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_ADDX|((_u4(rdn)&8)<<4)|(_u4(rm)<<3)|(rdn&7));
}

static void
T2_ADD(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_ADD,rn,rd,rm);
}

static void
T1_ADDI3(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_ADDI3|(_u3(im)<<6)|(_u3(rn)<<3)|_u3(rd));
}

static void
T1_ADDI8(jit_state_t *_jit, int32_t rdn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_ADDI8|(_u3(rdn)<<8)|_u8(im));
}

static void
T2_ADDI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_ADDI,rn,rd,im);
}

static void
T2_ADDWI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_ADDWI,rn,rd,im);
}

static void
T2_ADDS(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_ADD|ARM_S,rn,rd,rm);
}

static void
T2_ADDSI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_ADDI|ARM_S,rn,rd,im);
}

static void
T1_ADC(jit_state_t *_jit, int32_t rdn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_ADC|(_u3(rm)<<3)|_u3(rdn));
}

static void
T2_ADCS(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_ADC|ARM_S,rn,rd,rm);
}

static void
T2_ADCSI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_ADCI|ARM_S,rn,rd,im);
}

static void
T1_SUB(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_SUB|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rd));
}

static void
T2_SUB(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_SUB,rn,rd,rm);
}

static void
T1_SUBI3(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_SUBI3|(_u3(im)<<6)|(_u3(rn)<<3)|_u3(rd));
}

static void
T1_SUBI8(jit_state_t *_jit, int32_t rdn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_SUBI8|(_u3(rdn)<<8)|_u8(im));
}

static void
T2_SUBI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_SUBI,rn,rd,im);
}

static void
T2_SUBWI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_SUBWI,rn,rd,im);
}

static void
T2_SUBS(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_SUB|ARM_S,rn,rd,rm);
}

static void
T2_SUBSI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_SUBI|ARM_S,rn,rd,im);
}

static void
T1_SBC(jit_state_t *_jit, int32_t rdn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_SBC|(_u3(rm)<<3)|_u3(rdn));
}

static void
T2_SBCS(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_SBC|ARM_S,rn,rd,rm);
}

static void
T2_SBCSI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_SBCI|ARM_S,rn,rd,im);
}

static void
T1_RSBI(jit_state_t *_jit, int32_t rd, int32_t rn)
{
  emit_u16_with_pool(_jit, THUMB_RSBI|(_u3(rn)<<3)|_u3(rd));
}

static void
T2_RSBI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_RSBI,rn,rd,im);
}

static void
T1_MUL(jit_state_t *_jit, int32_t rdm, int32_t rn)
{
  emit_u16_with_pool(_jit, THUMB_MUL|(_u3(rn)<<3)|_u3(rdm));
}

static void
T2_MUL(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_MUL,rn,rd,rm);
}

static void
T2_SMULL(jit_state_t *_jit, int32_t rl, int32_t rh, int32_t rn, int32_t rm)
{
  return torrrr(_jit, THUMB2_SMULL,rn,rl,rh,rm);
}

static void
T2_UMULL(jit_state_t *_jit, int32_t rl, int32_t rh, int32_t rn, int32_t rm)
{
  return torrrr(_jit, THUMB2_UMULL,rn,rl,rh,rm);
}

static void
T2_SDIV(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_SDIV,rn,rd,rm);
}

static void
T2_UDIV(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_UDIV,rn,rd,rm);
}

static void
T1_MLS(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm, int32_t ra)
{
  return torrrr(_jit, THUMB_MLS, rn, ra, rd, rm);
}

static void
T1_AND(jit_state_t *_jit, int32_t rdn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_AND|(_u3(rm)<<3)|_u3(rdn));
}

static void
T2_AND(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_AND,rn,rd,rm);
}

static void
T2_ANDI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_ANDI,rn,rd,im);
}

static void
T2_BICI(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_BICI,rn,rd,im);
}

static void
T1_REV(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_REV|(_u3(rm)<<3)|_u3(rd));
}

static void
T2_REV(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  return torrr(_jit, THUMB2_REV,rm,rd,rm);
}

static void
T1_SXTB(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_SXTB|(_u3(rm)<<3)|_u3(rd));
}

static void
T2_SXTB(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  return torrr(_jit, THUMB2_SXTB,_NOREG,rd,rm);
}

static void
T1_UXTB(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_UXTB|(_u3(rm)<<3)|_u3(rd));
}

static void
T2_UXTB(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  return torrr(_jit, THUMB2_UXTB,_NOREG,rd,rm);
}

static void
T1_SXTH(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_SXTH|(_u3(rm)<<3)|_u3(rd));
}

static void
T2_SXTH(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  return torrr(_jit, THUMB2_SXTH,_NOREG,rd,rm);
}

static void
T1_UXTH(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_UXTH|(_u3(rm)<<3)|_u3(rd));
}

static void
T2_UXTH(jit_state_t *_jit, int32_t rd, int32_t rm)
{
  return torrr(_jit, THUMB2_UXTH,_NOREG,rd,rm);
}

static void
T1_LSL(jit_state_t *_jit, int32_t rdn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_LSL|(_u3(rm)<<3)|_u3(rdn));
}

static void
T2_LSL(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_LSL,rn,rd,rm);
}

static void
T1_LSLI(jit_state_t *_jit, int32_t rd, int32_t rm, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_LSLI|(_u5(im)<<6)|(_u3(rm)<<3)|_u3(rd));
}

static void
T2_LSLI(jit_state_t *_jit, int32_t rd, int32_t rm, int32_t im)
{
  return tshift(_jit, THUMB2_LSLI,rd,rm,im);
}

static void
T1_LSR(jit_state_t *_jit, int32_t rdn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_LSR|(_u3(rm)<<3)|_u3(rdn));
}

static void
T2_LSR(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_LSR,rn,rd,rm);
}

static void
T1_LSRI(jit_state_t *_jit, int32_t rd, int32_t rm, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_LSRI|(_u5(im)<<6)|(_u3(rm)<<3)|_u3(rd));
}

static void
T2_LSRI(jit_state_t *_jit, int32_t rd, int32_t rm, int32_t im)
{
  return tshift(_jit, THUMB2_LSRI,rd,rm,im);
}

static void
T1_ASR(jit_state_t *_jit, int32_t rdn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_ASR|(_u3(rm)<<3)|_u3(rdn));
}

static void
T2_ASR(jit_state_t *_jit, int32_t rd, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_ASR,rn,rd,rm);
}

static void
T1_ASRI(jit_state_t *_jit, int32_t rd, int32_t rm, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_ASRI|(_u5(im)<<6)|(_u3(rm)<<3)|_u3(rd));
}

static void
T2_ASRI(jit_state_t *_jit, int32_t rd, int32_t rm, int32_t im)
{
  return tshift(_jit, THUMB2_ASRI,rd,rm,im);
}

static void
T1_CMP(jit_state_t *_jit, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_CMP|(_u3(rm)<<3)|_u3(rn));
}

static void
T1_CMPX(jit_state_t *_jit, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_CMPX|((_u4(rn)&8)<<4)|(_u4(rm)<<3)|(rn&7));
}

static void
T2_CMP(jit_state_t *_jit, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_CMP,rn,_NOREG,rm);
}

static void
T1_CMPI(jit_state_t *_jit, int32_t rn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_CMPI|(_u3(rn)<<8)|_u8(im));
}

static void
T2_CMPI(jit_state_t *_jit, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_CMPI,rn,_NOREG,im);
}

static void
T2_CMNI(jit_state_t *_jit, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_CMNI,rn,_NOREG,im);
}

static void
T1_TST(jit_state_t *_jit, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_TST|(_u3(rm)<<3)|_u3(rn));
}

static void
T2_TST(jit_state_t *_jit, int32_t rn, int32_t rm)
{
  return torrr(_jit, THUMB2_TST,rn,_NOREG,rm);
}

static void
T2_TSTI(jit_state_t *_jit, int32_t rn, int32_t im)
{
  return torri(_jit, THUMB2_TSTI,rn,_NOREG,im);
}

static void
T1_BLX(jit_state_t *_jit, int32_t r0)
{
  emit_u16_with_pool(_jit, THUMB_BLX|(_u4(r0)<<3));
}

static void
T1_BX(jit_state_t *_jit, int32_t r0)
{
  emit_u16_with_pool(_jit, THUMB_BX|(_u4(r0)<<3));
}

static jit_reloc_t
T2_CC_B(jit_state_t *_jit, uint32_t cc)
{
  return tcb(_jit, cc);
}

static jit_reloc_t
T2_B(jit_state_t *_jit)
{
  return tb(_jit, THUMB2_B);
}

static jit_reloc_t
T2_BLI(jit_state_t *_jit)
{
  return tb(_jit, THUMB2_BLI);
}

enum dmb_option { DMB_ISH = 0xb };
static void
T1_DMB(jit_state_t *_jit, enum dmb_option option)
{
  emit_wide_thumb(_jit, THUMB_DMB|_u4(option));
}

static void
T1_LDREX(jit_state_t *_jit, int32_t rt, int32_t rn, int8_t offset)
{
  emit_wide_thumb(_jit, THUMB_LDREX|(_u4(rn)<<16)|(_u4(rt)<<12)|_u8(offset));
}

static void
T1_STREX(jit_state_t *_jit, int32_t rd, int32_t rt, int32_t rn, int8_t offset)
{
  emit_wide_thumb
    (_jit, THUMB_STREX|(_u4(rn)<<16)|(_u4(rt)<<12)|(_u4(rd)<<8)|_u8(offset));
}

static void
T1_LDRSB(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_LDRSB|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_LDRSB(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  return torxr(_jit, THUMB2_LDRSB,rn,rt,rm);
}

static void
T2_LDRSBI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRSBI|THUMB2_U,rn,rt,im);
}

static void
T2_LDRSBWI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri12(_jit, THUMB2_LDRSBWI,rn,rt,im);
}

static void
T2_LDRSBIN(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRSBI,rn,rt,im);
}

static void
T1_LDRB(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_LDRB|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_LDRB(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  return torxr(_jit, THUMB2_LDRB,rn,rt,rm);
}

static void
T1_LDRBI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_LDRBI|(_u5(im)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_LDRBI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRBI|THUMB2_U,rn,rt,im);
}

static void
T2_LDRBWI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri12(_jit, THUMB2_LDRBWI,rn,rt,im);
}

static void
T2_LDRBIN(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRBI,rn,rt,im);
}

static void
T1_LDRSH(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_LDRSH|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_LDRSH(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  return torxr(_jit, THUMB2_LDRSH,rn,rt,rm);
}

static void
T2_LDRSHI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRSHI|THUMB2_U,rn,rt,im);
}

static void
T2_LDRSHWI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri12(_jit, THUMB2_LDRSHWI,rn,rt,im);
}

static void
T2_LDRSHIN(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRSHI,rn,rt,im);
}

static void
T1_LDRH(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_LDRH|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_LDRH(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  return torxr(_jit, THUMB2_LDRH,rn,rt,rm);
}

static void
T1_LDRHI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_LDRHI|(_u5(im)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_LDRHI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRHI|THUMB2_U,rn,rt,im);
}

static void
T2_LDRHWI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri12(_jit, THUMB2_LDRHWI,rn,rt,im);
}

static void
T2_LDRHIN(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRHI,rn,rt,im);
}

static void
T1_LDR(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_LDR|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_LDR(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  return torxr(_jit, THUMB2_LDR,rn,rt,rm);
}

static void
T1_LDRI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_LDRI|(_u5(im)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T1_LDRISP(jit_state_t *_jit, int32_t rt, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_LDRISP|(_u3(rt)<<8)|_u8(im));
}

static void
T2_LDRI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRI|THUMB2_U,rn,rt,im);
}

static void
T2_LDRWI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri12(_jit, THUMB2_LDRWI,rn,rt,im);
}

static void
T2_LDRIN(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_LDRI,rn,rt,im);
}

static void
T1_STRB(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_STRB|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_STRB(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  return torxr(_jit, THUMB2_STRB,rn,rt,rm);
}

static void
T1_STRBI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_STRBI | (_u5(im) << 6) | (_u3(rn) << 3) | _u3(rt));
}

static void
T2_STRBI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_STRBI|THUMB2_U,rn,rt,im);
}

static void
T2_STRBWI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri12(_jit, THUMB2_STRBWI,rn,rt,im);
}

static void
T2_STRBIN(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_STRBI,rn,rt,im);
}

static void
T1_STRH(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_STRH|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_STRH(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  return torxr(_jit, THUMB2_STRH,rn,rt,rm);
}

static void
T1_STRHI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_STRHI|(_u5(im)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_STRHI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_STRHI|THUMB2_U,rn,rt,im);
}

static void
T2_STRHWI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri12(_jit, THUMB2_STRHWI,rn,rt,im);
}

static void
T2_STRHIN(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_STRHI,rn,rt,im);
}

static void
T1_STR(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  emit_u16_with_pool(_jit, THUMB_STR|(_u3(rm)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T2_STR(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t rm)
{
  return torxr(_jit, THUMB2_STR,rn,rt,rm);
}

static void
T1_STRI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_STRI|(_u5(im)<<6)|(_u3(rn)<<3)|_u3(rt));
}

static void
T1_STRISP(jit_state_t *_jit, int32_t rt, int32_t im)
{
  emit_u16_with_pool(_jit, THUMB_STRISP|(_u3(rt)<<8)|(_u8(im)));
}

static void
T2_STRI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_STRI|THUMB2_U,rn,rt,im);
}

static void
T2_STRWI(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri12(_jit, THUMB2_STRWI,rn,rt,im);
}

static void
T2_STRIN(jit_state_t *_jit, int32_t rt, int32_t rn, int32_t im)
{
  return torri8(_jit, THUMB2_STRI,rn,rt,im);
}

static void
T1_BRK(jit_state_t *_jit)
{
  emit_u16_with_pool(_jit, THUMB_BRK);
}

static void
nop(jit_state_t *_jit, int32_t i0)
{
  for (; i0 > 0; i0 -= 2)
    T1_NOP(_jit);
    
  ASSERT(i0 == 0);
}

static void
movr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  if (r0 != r1) {
    T1_MOV(_jit, r0, r1);
  }
}

enum preserve_flags { PRESERVE_FLAGS, FLAGS_UNIMPORTANT };

static void
_movi(jit_state_t *_jit, int32_t r0, jit_word_t i0, enum preserve_flags flags)
{
  int                 i;
    
  if (flags == PRESERVE_FLAGS && r0 < 8 && !(i0 & 0xffffff80))
    T1_MOVI(_jit, r0, i0);
  else if (r0 < 8 && !(i0 & 0xffffff80))
    T1_MOVI(_jit, r0, i0);
  else if ((i = encode_thumb_immediate(i0)) != -1)
    T2_MOVI(_jit, r0, i);
  else if ((i = encode_thumb_immediate(~i0)) != -1)
    T2_MVNI(_jit, r0, i);
  else {
    T2_MOVWI(_jit, r0, (uint16_t)i0);
    if (i0 & 0xffff0000)
      T2_MOVTI(_jit, r0, (uint16_t)((unsigned)i0 >> 16));
  }
}

static void
movi(jit_state_t *_jit, int32_t r0, jit_word_t i0)
{
  return _movi(_jit, r0, i0, FLAGS_UNIMPORTANT);
}

static int
offset_in_load_from_pool_range(int32_t offset)
{
  return -0xfff <= offset && offset <= 0xfff;
}

static int32_t
decode_load_from_pool_offset(uint32_t inst)
{
  int32_t ret = inst & 0xfff;
  return ((inst >> 23) & 1) ? ret : -ret;
}

static uint32_t
encode_load_from_pool_offset(int32_t off)
{
  ASSERT(offset_in_load_from_pool_range(off));
  uint32_t u = off >= 0;
  uint32_t ret = ((u ? off : -off) & 0xfff) | (u << 23);
  ASSERT(decode_load_from_pool_offset(ret) == off);
  return ret;
}

static uint32_t
patch_load_from_pool(uint32_t inst, int32_t off)
{
  uint32_t load_from_pool_mask = THUMB2_LDRP | (0xf << 12);
  return (inst & load_from_pool_mask) | encode_load_from_pool_offset(off);
}

static int32_t
read_load_from_pool_offset(uint32_t *loc)
{
  return decode_load_from_pool_offset(read_wide_thumb(loc));
}

static void
patch_load_from_pool_offset(uint32_t *loc, int32_t v)
{
  write_wide_thumb(loc, patch_load_from_pool(read_wide_thumb(loc), v));
}

static jit_reloc_t
emit_load_from_pool(jit_state_t *_jit, uint32_t inst)
{
  while (1) {
    uint8_t *pc_base = (uint8_t *)((_jit->pc.w + 4) & ~3);
    uint8_t rsh = 0;
    int32_t off = (_jit->pc.uc - pc_base) >> rsh;
    jit_reloc_t ret =
      jit_reloc (_jit, JIT_RELOC_LOAD_FROM_POOL, 0, _jit->pc.uc, pc_base, rsh);
    uint8_t load_from_pool_width = 12;
    if (add_pending_literal(_jit, ret, load_from_pool_width)) {
      emit_wide_thumb(_jit, patch_load_from_pool(inst, off));
      return ret;
    }
  }
}  

static jit_reloc_t
movi_from_pool(jit_state_t *_jit, int32_t Rt)
{
  return emit_load_from_pool(_jit, THUMB2_LDRP | (_u4(Rt) << 12));
}

static jit_reloc_t
mov_addr(jit_state_t *_jit, int32_t r0)
{
  return movi_from_pool(_jit, r0);
}

static void
comr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_NOT(_jit, r0, r1);
  else
    T2_NOT(_jit, r0, r1);
}

static void
negr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_RSBI(_jit, r0, r1);
  else
    T2_RSBI(_jit, r0, r1, 0);
}

static void
addr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8)
    T1_ADD(_jit, r0, r1, r2);
  else if (r0 == r1 || r0 == r2)
    T1_ADDX(_jit, r0, r0 == r1 ? r2 : r1);
  else
    T2_ADD(_jit, r0, r1, r2);
}

static void
addi(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  int                 i;
    
  if ((r0|r1) < 8 && !(i0 & ~7))
    T1_ADDI3(_jit, r0, r1, i0);
  else if ((r0|r1) < 8 && !(-i0 & ~7))
    T1_SUBI3(_jit, r0, r1, -i0);
  else if (r0 < 8 && r0 == r1 && !(i0 & ~0xff))
    T1_ADDI8(_jit, r0, i0);
  else if (r0 < 8 && r0 == r1 && !(-i0 & ~0xff))
    T1_SUBI8(_jit, r0, -i0);
  else if ((i = encode_thumb_immediate(i0)) != -1)
    T2_ADDI(_jit, r0, r1, i);
  else if ((i = encode_thumb_immediate(-i0)) != -1)
    T2_SUBI(_jit, r0, r1, i);
  else if ((i = encode_thumb_word_immediate(i0)) != -1)
    T2_ADDWI(_jit, r0, r1, i);
  else if ((i = encode_thumb_word_immediate(-i0)) != -1)
    T2_SUBWI(_jit, r0, r1, i);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    T2_ADD(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
addcr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  /* thumb auto set carry if not inside IT block */
  if ((r0|r1|r2) < 8)
    T1_ADD(_jit, r0, r1, r2);
  else
    T2_ADDS(_jit, r0, r1, r2);
}

static void
addci(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  int                 i;
    
  if ((r0|r1) < 8 && !(i0 & ~7))
    T1_ADDI3(_jit, r0, r1, i0);
  else if ((r0|r1) < 8 && !(-i0 & ~7))
    T1_SUBI3(_jit, r0, r1, -i0);
  else if (r0 < 8 && r0 == r1 && !(i0 & ~0xff))
    T1_ADDI8(_jit, r0, i0);
  else if (r0 < 8 && r0 == r1 && !(-i0 & ~0xff))
    T1_SUBI8(_jit, r0, -i0);
  else if ((i = encode_thumb_immediate(i0)) != -1)
    T2_ADDSI(_jit, r0, r1, i);
  else if ((i = encode_thumb_immediate(-i0)) != -1)
    T2_SUBSI(_jit, r0, r1, i);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    T2_ADDS(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
addxr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  /* keep setting carry because don't know last ADC */
  
  /* thumb auto set carry if not inside IT block */
  if ((r0|r1|r2) < 8 && (r0 == r1 || r0 == r2))
    T1_ADC(_jit, r0, r0 == r1 ? r2 : r1);
  else
    T2_ADCS(_jit, r0, r1, r2);
}

static void
addxi(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  int                 i;
  if ((i = encode_thumb_immediate(i0)) != -1)
    T2_ADCSI(_jit, r0, r1, i);
  else if ((i = encode_thumb_immediate(-i0)) != -1)
    T2_SBCSI(_jit, r0, r1, i);
  else if (r0 != r1) {
    _movi(_jit, r0, i0, PRESERVE_FLAGS);
    T2_ADCS(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    _movi(_jit, jit_gpr_regno(reg), i0, PRESERVE_FLAGS);
    T2_ADCS(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
subr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8)
    T1_SUB(_jit, r0, r1, r2);
  else
    T2_SUB(_jit, r0, r1, r2);
}

static void
subi(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  int                 i;
    
  if ((r0|r1) < 8 && !(i0 & ~7))
    T1_SUBI3(_jit, r0, r1, i0);
  else if ((r0|r1) < 8 && !(-i0 & ~7))
    T1_ADDI3(_jit, r0, r1, -i0);
  else if (r0 < 8 && r0 == r1 && !(i0 & ~0xff))
    T1_SUBI8(_jit, r0, i0);
  else if (r0 < 8 && r0 == r1 && !(-i0 & ~0xff))
    T1_ADDI8(_jit, r0, -i0);
  else if ((i = encode_thumb_immediate(i0)) != -1)
    T2_SUBI(_jit, r0, r1, i);
  else if ((i = encode_thumb_immediate(-i0)) != -1)
    T2_ADDI(_jit, r0, r1, i);
  else if ((i = encode_thumb_word_immediate(i0)) != -1)
    T2_SUBWI(_jit, r0, r1, i);
  else if ((i = encode_thumb_word_immediate(-i0)) != -1)
    T2_ADDWI(_jit, r0, r1, i);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    T2_SUB(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
subcr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  /* thumb auto set carry if not inside IT block */
  if ((r0|r1|r2) < 8)
    T1_SUB(_jit, r0, r1, r2);
  else
    T2_SUBS(_jit, r0, r1, r2);
}

static void
subci(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  int                 i;
    
  if ((r0|r1) < 8 && !(i0 & ~7))
    T1_SUBI3(_jit, r0, r1, i0);
  else if ((r0|r1) < 8 && !(-i0 & ~7))
    T1_ADDI3(_jit, r0, r1, -i0);
  else if (r0 < 8 && r0 == r1 && !(i0 & ~0xff))
    T1_SUBI8(_jit, r0, i0);
  else if (r0 < 8 && r0 == r1 && !(-i0 & ~0xff))
    T1_ADDI8(_jit, r0, -i0);
  else if ((i = encode_thumb_immediate(i0)) != -1)
    T2_SUBSI(_jit, r0, r1, i);
  else if ((i = encode_thumb_immediate(-i0)) != -1)
    T2_ADDSI(_jit, r0, r1, i);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    T2_SUBS(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
subxr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  /* keep setting carry because don't know last SBC */
  
  /* thumb auto set carry if not inside IT block */
  if ((r0|r1|r2) < 8 && r0 == r1)
    T1_SBC(_jit, r0, r2);
  else
    T2_SBCS(_jit, r0, r1, r2);
}

static void
subxi(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  int                 i;
  if ((i = encode_arm_immediate(i0)) != -1)
    T2_SBCSI(_jit, r0, r1, i);
  else if ((i = encode_arm_immediate(-i0)) != -1)
    T2_ADCSI(_jit, r0, r1, i);
  else if (r0 != r1) {
    _movi(_jit, r0, i0, PRESERVE_FLAGS);
    T2_SBCS(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    _movi(_jit, jit_gpr_regno(reg), i0, PRESERVE_FLAGS);
    T2_SBCS(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
mulr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if (r0 == r2 && (r0|r1) < 8)
    T1_MUL(_jit, r0, r1);
  else if (r0 == r1 && (r0|r2) < 8)
    T1_MUL(_jit, r0, r2);
  else
    T2_MUL(_jit, r0, r1, r2);
}

static void
muli(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  mulr(_jit, r0, r1, jit_gpr_regno(reg));
  unget_temp_gpr(_jit);
}

static void
iqmulr(jit_state_t *_jit, int32_t r0, int32_t r1,
        int32_t r2, int32_t r3, jit_bool_t sign)
{
  if (r2 == r3) {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movr(_jit, jit_gpr_regno(reg), r2);
    if (sign)
      T2_SMULL(_jit, r0, r1, jit_gpr_regno(reg), r2);
    else
      T2_UMULL(_jit, r0, r1, jit_gpr_regno(reg), r2);
    unget_temp_gpr(_jit);
  } else if (r0 != r2 && r1 != r2) {
    if (sign)
      T2_SMULL(_jit, r0, r1, r2, r3);
    else
      T2_UMULL(_jit, r0, r1, r2, r3);
  } else {
    if (sign)
      T2_SMULL(_jit, r0, r1, r3, r2);
    else
      T2_UMULL(_jit, r0, r1, r3, r2);
  }
}

static void
iqmuli(jit_state_t *_jit, int32_t r0, int32_t r1,
        int32_t r2, jit_word_t i0, jit_bool_t sign)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  iqmulr(_jit, r0, r1, r2, jit_gpr_regno(reg), sign);
  unget_temp_gpr(_jit);
}

static void
qmulr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2, int32_t r3)
{
  return iqmulr(_jit, r0,r1,r2,r3,1);
}

static void
qmulr_u(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2, int32_t r3)
{
  return iqmulr(_jit, r0,r1,r2,r3,0);
}

static void
qmuli(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2, int32_t i0)
{
  return iqmuli(_jit, r0,r1,r2,i0,1);
}

static void
qmuli_u(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2, int32_t i0)
{
  return iqmuli(_jit, r0,r1,r2,i0,0);
}

static void
divr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  T2_SDIV(_jit, r0, r1, r2);
}

static void
divi(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  divr(_jit, r0, r1, jit_gpr_regno(reg));
  unget_temp_gpr(_jit);
}

static void
divr_u(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  T2_UDIV(_jit, r0, r1, r2);
}

static void
divi_u(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  divr_u(_jit, r0, r1, jit_gpr_regno(reg));
  unget_temp_gpr(_jit);
}

static void
iqdivr(jit_state_t *_jit, int32_t r0, int32_t r1,
        int32_t r2, int32_t r3, jit_bool_t sign)
{
  int need_tmp = r0 == r2 || r0 == r3;
  if (need_tmp) {
    int32_t tmp = jit_gpr_regno(get_temp_gpr(_jit));
    if (r0 == r2) {
      movr(_jit, tmp, r2);
      r2 = tmp;
    }
    if (r0 == r3) {
      if (r2 != r3)
        movr(_jit, tmp, r3);
      r3 = tmp;
    }
  }
  if (sign)
    divr(_jit, r0, r2, r3);
  else
    divr_u(_jit, r0, r2, r3);
  T1_MLS(_jit, r1, r3, r0, r2);
  if (need_tmp)
    unget_temp_gpr(_jit);
}

static void
iqdivi(jit_state_t *_jit, int32_t r0, int32_t r1,
        int32_t r2, jit_word_t i0, jit_bool_t sign)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  iqdivr(_jit, r0, r1, r2, jit_gpr_regno(reg), sign);
  unget_temp_gpr(_jit);
}

static void
qdivr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2, int32_t r3)
{
  return iqdivr(_jit, r0,r1,r2,r3,1);
}

static void
qdivr_u(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2, int32_t r3)
{
  return iqdivr(_jit, r0,r1,r2,r3,0);
}

static void
qdivi(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2, int32_t i0)
{
  return iqdivi(_jit, r0,r1,r2,i0,1);
}

static void
qdivi_u(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2, int32_t i0)
{
  return iqdivi(_jit, r0,r1,r2,i0,0);
}

static void
iremr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2, jit_bool_t sign)
{
  return iqdivr(_jit, r0, r0, r1, r2, sign);
}

static void
remr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  return iremr(_jit, r0, r1, r2, 1);
}

static void
remi(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  remr(_jit, r0, r1, jit_gpr_regno(reg));
  unget_temp_gpr(_jit);
}

static void
remr_u(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  return iremr(_jit, r0, r1, r2, 0);
}

static void
remi_u(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  remr_u(_jit, r0, r1,jit_gpr_regno(reg));
  unget_temp_gpr(_jit);
}

static void
andr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8 && (r0 == r1 || r0 == r2))
    T1_AND(_jit, r0, r0 == r1 ? r2 : r1);
  else
    T2_AND(_jit, r0, r1, r2);
}

static void
andi(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  int                 i;
    
  if ((i = encode_thumb_immediate(i0)) != -1)
    T2_ANDI(_jit, r0, r1, i);
  else if ((i = encode_thumb_immediate(~i0)) != -1)
    T2_BICI(_jit, r0, r1, i);
  else if (r0 != r1) {
    movi(_jit, r0, i0);
    T2_AND(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    T2_AND(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
orr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8 && (r0 == r1 || r0 == r2))
    T1_ORR(_jit, r0, r0 == r1 ? r2 : r1);
  else
    T2_ORR(_jit, r0, r1, r2);
}

static void
ori(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  int                 i;
    
  if ((i = encode_thumb_immediate(i0)) != -1)
    T2_ORRI(_jit, r0, r1, i);
  else if (r0 != r1) {
    movi(_jit, r0, i0);
    T2_ORR(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    T2_ORR(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
xorr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8 && (r0 == r1 || r0 == r2))
    T1_EOR(_jit, r0, r0 == r1 ? r2 : r1);
  else
    T2_EOR(_jit, r0, r1, r2);
}

static void
xori(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  int                 i;
    
  if ((i = encode_thumb_immediate(i0)) != -1)
    T2_EORI(_jit, r0, r1, i);
  else if (r0 != r1) {
    movi(_jit, r0, i0);
    T2_EOR(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    T2_EOR(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
lshr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8 && r0 == r1)
    T1_LSL(_jit, r0, r2);
  else
    T2_LSL(_jit, r0, r1, r2);
}

static void
lshi(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  ASSERT(i0 >= 0 && i0 <= 31);
  if (i0 == 0)
    movr(_jit, r0, r1);
  else {
    if ((r0|r1) < 8)
      T1_LSLI(_jit, r0, r1, i0);
    else
      T2_LSLI(_jit, r0, r1, i0);
  }
}

static void
rshr(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8 && r0 == r1)
    T1_ASR(_jit, r0, r2);
  else
    T2_ASR(_jit, r0, r1, r2);
}

static void
rshi(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  ASSERT(i0 >= 0 && i0 <= 31);
  if (i0 == 0)
    movr(_jit, r0, r1);
  else {
    if ((r0|r1) < 8)
      T1_ASRI(_jit, r0, r1, i0);
    else
      T2_ASRI(_jit, r0, r1, i0);
  }
}

static void
rshr_u(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8 && r0 == r1)
    T1_LSR(_jit, r0, r2);
  else
    T2_LSR(_jit, r0, r1, r2);
}

static void
rshi_u(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  ASSERT(i0 >= 0 && i0 <= 31);
  if (i0 == 0)
    movr(_jit, r0, r1);
  else {
    if ((r0|r1) < 8)
      T1_LSRI(_jit, r0, r1, i0);
    else
      T2_LSRI(_jit, r0, r1, i0);
  }
}

static void
jmpr(jit_state_t *_jit, int32_t r0)
{
  T1_BX(_jit, r0);
}

static jit_reloc_t
jmp(jit_state_t *_jit)
{
  return T2_B(_jit);
}

static void
jmpi(jit_state_t *_jit, jit_word_t i0)
{
  return jit_patch_there(_jit, jmp(_jit), (void*)i0);
}

static jit_reloc_t
bccr(jit_state_t *_jit, int cc, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_CMP(_jit, r0, r1);
  else if ((r0&r1) & 8)
    T1_CMPX(_jit, r0, r1);
  else
    T2_CMP(_jit, r0, r1);
  return T2_CC_B(_jit, cc);
}

static jit_reloc_t
bcci(jit_state_t *_jit, int cc, int32_t r0, jit_word_t i1)
{
  int i;
  if (r0 < 7 && !(i1 & 0xffffff00))
    T1_CMPI(_jit, r0, i1);
  else if ((i = encode_thumb_immediate(i1)) != -1)
    T2_CMPI(_jit, r0, i);
  else if ((i = encode_thumb_immediate(-i1)) != -1)
    T2_CMNI(_jit, r0, i);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i1);
    T2_CMP(_jit, r0, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
  return T2_CC_B(_jit, cc);
}

static jit_reloc_t
bltr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_LT, r0, r1);
}

static jit_reloc_t
blti(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_LT, r0, i1);
}

static jit_reloc_t
bltr_u(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_LO, r0, r1);
}

static jit_reloc_t
blti_u(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_LO, r0, i1);
}

static jit_reloc_t
bler(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_LE, r0, r1);
}

static jit_reloc_t
blei(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_LE, r0, i1);
}

static jit_reloc_t
bler_u(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_LS, r0, r1);
}

static jit_reloc_t
blei_u(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_LS, r0, i1);
}

static jit_reloc_t
beqr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_EQ, r0, r1);
}

static jit_reloc_t
beqi(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_EQ, r0, i1);
}

static jit_reloc_t
bger(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_GE, r0, r1);
}

static jit_reloc_t
bgei(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_GE, r0, i1);
}

static jit_reloc_t
bger_u(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_HS, r0, r1);
}

static jit_reloc_t
bgei_u(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_HS, r0, i1);
}

static jit_reloc_t
bgtr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_GT, r0, r1);
}

static jit_reloc_t
bgti(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_GT, r0, i1);
}

static jit_reloc_t
bgtr_u(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_HI, r0, r1);
}

static jit_reloc_t
bgti_u(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_HI, r0, i1);
}

static jit_reloc_t
bner(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bccr(_jit, ARM_CC_NE, r0, r1);
}

static jit_reloc_t
bnei(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bcci(_jit, ARM_CC_NE, r0, i1);
}

static jit_reloc_t
baddr(jit_state_t *_jit, int cc, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_ADD(_jit, r0, r0, r1);
  else
    T2_ADDS(_jit, r0, r0, r1);
  return T2_CC_B(_jit, cc);
}

static jit_reloc_t
baddi(jit_state_t *_jit, int cc, int32_t r0, int i1)
{
  int i;
  if (r0 < 8 && !(i1 & ~7))
    T1_ADDI3(_jit, r0, r0, i1);
  else if (r0 < 8 && !(-i1 & ~7))
    T1_SUBI3(_jit, r0, r0, -i1);
  else if (r0 < 8 && !(i1 & ~0xff))
    T1_ADDI8(_jit, r0, i1);
  else if (r0 < 8 && !(-i1 & ~0xff))
    T1_SUBI8(_jit, r0, -i1);
  else if ((i = encode_thumb_immediate(i1)) != -1)
    T2_ADDSI(_jit, r0, r0, i);
  else if ((i = encode_thumb_immediate(-i1)) != -1)
    T2_SUBSI(_jit, r0, r0, i);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i1);
    T2_ADDS(_jit, r0, r0, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
  return T2_CC_B(_jit, cc);
}

static jit_reloc_t
boaddr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return baddr(_jit, ARM_CC_VS, r0, r1);
}

static jit_reloc_t
boaddi(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return baddi(_jit, ARM_CC_VS, r0, i1);
}

static jit_reloc_t
boaddr_u(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return baddr(_jit, ARM_CC_HS, r0, r1);
}

static jit_reloc_t
boaddi_u(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return baddi(_jit, ARM_CC_HS, r0, i1);
}

static jit_reloc_t
bxaddr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return baddr(_jit, ARM_CC_VC, r0, r1);
}

static jit_reloc_t
bxaddi(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return baddi(_jit, ARM_CC_VC, r0, i1);
}

static jit_reloc_t
bxaddr_u(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return baddr(_jit, ARM_CC_LO, r0, r1);
}

static jit_reloc_t
bxaddi_u(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return baddi(_jit, ARM_CC_LO, r0, i1);
}

static jit_reloc_t
bsubr(jit_state_t *_jit, int cc, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_SUB(_jit, r0, r0, r1);
  else
    T2_SUBS(_jit, r0, r0, r1);
  return T2_CC_B(_jit, cc);
}

static jit_reloc_t
bsubi(jit_state_t *_jit, int cc, int32_t r0, int i1)
{
  int i;
  if (r0 < 8 && !(i1 & ~7))
    T1_SUBI3(_jit, r0, r0, i1);
  else if (r0 < 8 && !(-i1 & ~7))
    T1_ADDI3(_jit, r0, r0, -i1);
  else if (r0 < 8 && !(i1 & ~0xff))
    T1_SUBI8(_jit, r0, i1);
  else if (r0 < 8 && !(-i1 & ~0xff))
    T1_ADDI8(_jit, r0, -i1);
  else if ((i = encode_thumb_immediate(i1)) != -1)
    T2_SUBSI(_jit, r0, r0, i);
  else if ((i = encode_thumb_immediate(-i1)) != -1)
    T2_SUBSI(_jit, r0, r0, i);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i1);
    T2_SUBS(_jit, r0, r0, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
  return T2_CC_B(_jit, cc);
}

static jit_reloc_t
bosubr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bsubr(_jit, ARM_CC_VS, r0, r1);
}

static jit_reloc_t
bosubi(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bsubi(_jit, ARM_CC_VS, r0, i1);
}

static jit_reloc_t
bosubr_u(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bsubr(_jit, ARM_CC_LO, r0, r1);
}

static jit_reloc_t
bosubi_u(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bsubi(_jit, ARM_CC_LO, r0, i1);
}

static jit_reloc_t
bxsubr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bsubr(_jit, ARM_CC_VC, r0, r1);
}

static jit_reloc_t
bxsubi(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bsubi(_jit, ARM_CC_VC, r0, i1);
}

static jit_reloc_t
bxsubr_u(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bsubr(_jit, ARM_CC_HS, r0, r1);
}

static jit_reloc_t
bxsubi_u(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bsubi(_jit, ARM_CC_HS, r0, i1);
}

static jit_reloc_t
bmxr(jit_state_t *_jit, int cc, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_TST(_jit, r0, r1);
  else
    T2_TST(_jit, r0, r1);
  return T2_CC_B(_jit, cc);
}

static jit_reloc_t
bmxi(jit_state_t *_jit, int cc, int32_t r0, jit_word_t i1)
{
  int i;
  if ((i = encode_thumb_immediate(i1)) != -1)
    T2_TSTI(_jit, r0, i);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i1);
    T2_TST(_jit, r0, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
  return T2_CC_B(_jit, cc);
}

static jit_reloc_t
bmsr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bmxr(_jit, ARM_CC_NE, r0, r1);
}

static jit_reloc_t
bmsi(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bmxi(_jit, ARM_CC_NE, r0, i1);
}

static jit_reloc_t
bmcr(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  return bmxr(_jit, ARM_CC_EQ, r0, r1);
}

static jit_reloc_t
bmci(jit_state_t *_jit, int32_t r0, int32_t i1)
{
  return bmxi(_jit, ARM_CC_EQ, r0, i1);
}

static void
ldr_c(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  T2_LDRSBI(_jit, r0, r1, 0);
}

static void
ldi_c(jit_state_t *_jit, int32_t r0, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  T2_LDRSBI(_jit, r0, jit_gpr_regno(reg), 0);
  unget_temp_gpr(_jit);
}

static void
ldxr_c(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8)
    T1_LDRSB(_jit, r0, r1, r2);
  else
    T2_LDRSB(_jit, r0, r1, r2);
}

#define jit_ldrt_strt_p() 0

static void
ldxi_c(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
    
  if (jit_ldrt_strt_p() && i0 >= 0 && i0 <= 255)
    T2_LDRSBI(_jit, r0, r1, i0);
  else if (i0 < 0 && i0 >= -255)
    T2_LDRSBIN(_jit, r0, r1, -i0);
  else if (i0 >= 0 && i0 <= 4095)
    T2_LDRSBWI(_jit, r0, r1, i0);
  else if (r0 != r1) {
    movi(_jit, r0, i0);
    if ((r0|r1) < 8)
      T1_LDRSB(_jit, r0, r1, r0);
    else
      T2_LDRSB(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    if ((r0|r1|jit_gpr_regno(reg)) < 8)
      T1_LDRSB(_jit, r0, r1, jit_gpr_regno(reg));
    else
      T2_LDRSB(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
ldr_uc(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  T2_LDRBI(_jit, r0, r1, 0);
}

static void
ldi_uc(jit_state_t *_jit, int32_t r0, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  T2_LDRBI(_jit, r0, jit_gpr_regno(reg), 0);
  unget_temp_gpr(_jit);
}

static void
ldxr_uc(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8)
    T1_LDRB(_jit, r0, r1, r2);
  else
    T2_LDRB(_jit, r0, r1, r2);
}

static void
ldxi_uc(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  if ((r0|r1) < 8 && i0 >= 0 && i0 < 0x20)
    T1_LDRBI(_jit, r0, r1, i0);
  else if (jit_ldrt_strt_p() && i0 >= 0 && i0 <= 255)
    T2_LDRBI(_jit, r0, r1, i0);
  else if (i0 < 0 && i0 >= -255)
    T2_LDRBIN(_jit, r0, r1, -i0);
  else if (i0 >= 0 && i0 <= 4095)
    T2_LDRBWI(_jit, r0, r1, i0);
  else if (r0 != r1) {
    movi(_jit, r0, i0);
    if ((r0|r1) < 8)
      T1_LDRB(_jit, r0, r1, r0);
    else
      T2_LDRB(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    if ((r0|r1|jit_gpr_regno(reg)) < 8)
      T1_LDRB(_jit, r0, r1, jit_gpr_regno(reg));
    else
      T2_LDRB(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
ldr_s(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  T2_LDRSHI(_jit, r0, r1, 0);
}

static void
ldi_s(jit_state_t *_jit, int32_t r0, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  T2_LDRSHI(_jit, r0, jit_gpr_regno(reg), 0);
  unget_temp_gpr(_jit);
}

static void
ldxr_s(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8)
    T1_LDRSH(_jit, r0, r1, r2);
  else
    T2_LDRSH(_jit, r0, r1, r2);
}

static void
ldxi_s(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  if (jit_ldrt_strt_p() && i0 >= 0 && i0 <= 255)
    T2_LDRSHI(_jit, r0, r1, i0);
  else if (i0 < 0 && i0 >= -255)
    T2_LDRSHIN(_jit, r0, r1, -i0);
  else if (i0 >= 0 && i0 <= 4095)
    T2_LDRSHWI(_jit, r0, r1, i0);
  else if (r0 != r1) {
    movi(_jit, r0, i0);
    if ((r0|r1) < 8)
      T1_LDRSH(_jit, r0, r1, r0);
    else
      T2_LDRSH(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    if ((r0|r1|jit_gpr_regno(reg)) < 8)
      T1_LDRSH(_jit, r0, r1, jit_gpr_regno(reg));
    else
      T2_LDRSH(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
ldr_us(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  T2_LDRHI(_jit, r0, r1, 0);
}

static void
ldi_us(jit_state_t *_jit, int32_t r0, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  T2_LDRHI(_jit, r0, jit_gpr_regno(reg), 0);
  unget_temp_gpr(_jit);
}

static void
ldxr_us(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  
  if ((r0|r1|r2) < 8)
    T1_LDRH(_jit, r0, r1, r2);
  else
    T2_LDRH(_jit, r0, r1, r2);
}

static void
ldxi_us(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  if ((r0|r1) < 8 && i0 >= 0 && !(i0 & 1) && (i0 >> 1) < 0x20)
    T1_LDRHI(_jit, r0, r1, i0 >> 1);
  else if (jit_ldrt_strt_p() && i0 >= 0 && i0 <= 255)
    T2_LDRHI(_jit, r0, r1, i0);
  else if (i0 < 0 && i0 >= -255)
    T2_LDRHIN(_jit, r0, r1, -i0);
  else if (i0 >= 0 && i0 <= 4095)
    T2_LDRHWI(_jit, r0, r1, i0);
  else if (r0 != r1) {
    movi(_jit, r0, i0);
    if ((r0|r1) < 8)
      T1_LDRH(_jit, r0, r1, r0);
    else
      T2_LDRH(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    if ((r0|r1|jit_gpr_regno(reg)) < 8)
      T1_LDRH(_jit, r0, r1, jit_gpr_regno(reg));
    else
      T2_LDRH(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
ldr_i(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  T2_LDRI(_jit, r0, r1, 0);
}

static void
ldi_i(jit_state_t *_jit, int32_t r0, jit_word_t i0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  T2_LDRI(_jit, r0, jit_gpr_regno(reg), 0);
  unget_temp_gpr(_jit);
}

static void
ldxr_i(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8)
    T1_LDR(_jit, r0, r1, r2);
  else
    T2_LDR(_jit, r0, r1, r2);
}

static void
ldxi_i(jit_state_t *_jit, int32_t r0, int32_t r1, jit_word_t i0)
{
  if ((r0|r1) < 8 && i0 >= 0 && !(i0 & 3) && (i0 >> 2) < 0x20)
    T1_LDRI(_jit, r0, r1, i0 >> 2);
  else if (r1 == jit_gpr_regno(JIT_SP) && r0 < 8 &&
           i0 >= 0 && !(i0 & 3) && (i0 >> 2) <= 255)
    T1_LDRISP(_jit, r0, i0 >> 2);
  else if (jit_ldrt_strt_p() && i0 >= 0 && i0 <= 255)
    T2_LDRI(_jit, r0, r1, i0);
  else if (i0 < 0 && i0 > -255)
    T2_LDRIN(_jit, r0, r1, -i0);
  else if (i0 >= 0 && i0 <= 4095)
    T2_LDRWI(_jit, r0, r1, i0);
  else if (r0 != r1) {
    movi(_jit, r0, i0);
    if ((r0|r1) < 8)
      T1_LDR(_jit, r0, r1, r0);
    else
      T2_LDR(_jit, r0, r1, r0);
  } else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    if ((r0|r1|jit_gpr_regno(reg)) < 8)
      T1_LDR(_jit, r0, r1, jit_gpr_regno(reg));
    else
      T2_LDR(_jit, r0, r1, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
str_c(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  T2_STRBI(_jit, r1, r0, 0);
}

static void
sti_c(jit_state_t *_jit, jit_word_t i0, int32_t r0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  T2_STRBI(_jit, r0, jit_gpr_regno(reg), 0);
  unget_temp_gpr(_jit);
}

static void
stxr_c(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8)
    T1_STRB(_jit, r2, r1, r0);
  else
    T2_STRB(_jit, r2, r1, r0);
}

static void
stxi_c(jit_state_t *_jit, jit_word_t i0, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8 && i0 >= 0 && i0 < 0x20)
    T1_STRBI(_jit, r1, r0, i0);
  else if (jit_ldrt_strt_p() && i0 >= 0 && i0 <= 255)
    T2_STRBI(_jit, r1, r0, i0);
  else if (i0 < 0 && i0 >= -255)
    T2_STRBIN(_jit, r1, r0, -i0);
  else if (i0 >= 0 && i0 <= 4095)
    T2_STRBWI(_jit, r1, r0, i0);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    if ((r0|r1|jit_gpr_regno(reg)) < 8)
      T1_STRB(_jit, r1, r0, jit_gpr_regno(reg));
    else
      T2_STRB(_jit, r1, r0, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
str_s(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  T2_STRHI(_jit, r1, r0, 0);
}

static void
sti_s(jit_state_t *_jit, jit_word_t i0, int32_t r0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  T2_STRHI(_jit, r0, jit_gpr_regno(reg), 0);
  unget_temp_gpr(_jit);
}

static void
stxr_s(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8)
    T1_STRH(_jit, r2, r1, r0);
  else
    T2_STRH(_jit, r2, r1, r0);
}

static void
stxi_s(jit_state_t *_jit, jit_word_t i0, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8 && i0 >= 0 && !(i0 & 1) && (i0 >> 1) < 0x20)
    T1_STRHI(_jit, r1, r0, i0 >> 1);
  else if (jit_ldrt_strt_p() && i0 >= 0 && i0 <= 255)
    T2_STRHI(_jit, r1, r0, i0);
  else if (i0 < 0 && i0 >= -255)
    T2_STRHIN(_jit, r1, r0, -i0);
  else if (i0 >= 0 && i0 <= 4095)
    T2_STRHWI(_jit, r1, r0, i0);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    if ((r0|r1|jit_gpr_regno(reg)) < 8)
      T1_STRH(_jit, r1, r0, jit_gpr_regno(reg));
    else
      T2_STRH(_jit, r1, r0, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
str_i(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  T2_STRI(_jit, r1, r0, 0);
}

static void
sti_i(jit_state_t *_jit, jit_word_t i0, int32_t r0)
{
  jit_gpr_t reg = get_temp_gpr(_jit);
  movi(_jit, jit_gpr_regno(reg), i0);
  T2_STRI(_jit, r0, jit_gpr_regno(reg), 0);
  unget_temp_gpr(_jit);
}

static void
stxr_i(jit_state_t *_jit, int32_t r0, int32_t r1, int32_t r2)
{
  if ((r0|r1|r2) < 8)
    T1_STR(_jit, r2, r1, r0);
  else
    T2_STR(_jit, r2, r1, r0);
}

static void
stxi_i(jit_state_t *_jit, jit_word_t i0, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8 && i0 >= 0 && !(i0 & 3) && (i0 >> 2) < 0x20)
    T1_STRI(_jit, r1, r0, i0 >> 2);
  else if (r0 == jit_gpr_regno(JIT_SP) && r1 < 8 &&
           i0 >= 0 && !(i0 & 3) && (i0 >> 2) <= 255)
    T1_STRISP(_jit, r1, i0 >> 2);
  else if (jit_ldrt_strt_p() && i0 >= 0 && i0 <= 255)
    T2_STRI(_jit, r1, r0, i0);
  else if (i0 < 0 && i0 >= -255)
    T2_STRIN(_jit, r1, r0, -i0);
  else if (i0 >= 0 && i0 <= 4095)
    T2_STRWI(_jit, r1, r0, i0);
  else {
    jit_gpr_t reg = get_temp_gpr(_jit);
    movi(_jit, jit_gpr_regno(reg), i0);
    if ((r0|r1|jit_gpr_regno(reg)) < 8)
      T1_STR(_jit, r1, r0, jit_gpr_regno(reg));
    else
      T2_STR(_jit, r1, r0, jit_gpr_regno(reg));
    unget_temp_gpr(_jit);
  }
}

static void
bswapr_us(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_REV(_jit, r0, r1);
  else
    T2_REV(_jit, r0, r1);
  rshi_u(_jit, r0, r0, 16);
}

/* inline glibc htonl (without register clobber) */
static void
bswapr_ui(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_REV(_jit, r0, r1);
  else
    T2_REV(_jit, r0, r1);
}

static void
extr_c(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  
  if ((r0|r1) < 8)
    T1_SXTB(_jit, r0, r1);
  else
    T2_SXTB(_jit, r0, r1);
}

static void
extr_uc(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_UXTB(_jit, r0, r1);
  else
    T2_UXTB(_jit, r0, r1);
}

static void
extr_s(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_SXTH(_jit, r0, r1);
  else
    T2_SXTH(_jit, r0, r1);
}

static void
extr_us(jit_state_t *_jit, int32_t r0, int32_t r1)
{
  if ((r0|r1) < 8)
    T1_UXTH(_jit, r0, r1);
  else
    T2_UXTH(_jit, r0, r1);
}

static void
callr(jit_state_t *_jit, int32_t r0)
{
  T1_BLX(_jit, r0);
}

static void
calli(jit_state_t *_jit, jit_word_t i0)
{
  jit_patch_there(_jit, T2_BLI(_jit), (void*)i0);
}

static void
jmpi_with_link(jit_state_t *_jit, jit_word_t i0)
{
  jit_patch_there(_jit, T2_BLI(_jit), (void*)i0);
}

static void
push_link_register(jit_state_t *_jit)
{
}

static void
pop_link_register(jit_state_t *_jit)
{
}

static void
ret(jit_state_t *_jit)
{
  T1_BX(_jit, jit_gpr_regno(_LR));
}

static void
reti(jit_state_t *_jit, int32_t i0)
{
  movi(_jit, jit_gpr_regno(_R0), i0);
  ret(_jit);
}

static void
retr(jit_state_t *_jit, int32_t r0)
{
  movr(_jit, jit_gpr_regno(_R0), r0);
  ret(_jit);
}

static void
retval_c(jit_state_t *_jit, int32_t r0)
{
  extr_c(_jit, r0, jit_gpr_regno(_R0));
}

static void
retval_uc(jit_state_t *_jit, int32_t r0)
{
  extr_uc(_jit, r0, jit_gpr_regno(_R0));
}

static void
retval_s(jit_state_t *_jit, int32_t r0)
{
  extr_s(_jit, r0, jit_gpr_regno(_R0));
}

static void
retval_us(jit_state_t *_jit, int32_t r0)
{
  extr_us(_jit, r0, jit_gpr_regno(_R0));
}

static void
retval_i(jit_state_t *_jit, int32_t r0)
{
  movr(_jit, r0, jit_gpr_regno(_R0));
}

static uint32_t*
jmp_without_veneer(jit_state_t *_jit)
{
  uint32_t *loc = _jit->pc.ui;
  emit_u16(_jit, 0);
  emit_u16(_jit, 0);
  return loc;
}

static void
patch_jmp_without_veneer(jit_state_t *_jit, uint32_t *loc)
{
  uint8_t *pc_base = ((uint8_t *)loc) + 4;
  int32_t off = (uint8_t*)jit_address(_jit) - pc_base;
  write_wide_thumb(loc, THUMB2_B | encode_thumb_jump(off));
}

struct veneer
{
  uint16_t ldr;
  uint16_t br;
  uint32_t addr;
};

static void
patch_veneer(uint32_t *loc, jit_pointer_t addr)
{
  struct veneer *v = (struct veneer*) loc;
  v->addr = (uintptr_t) addr;
}

static void
emit_veneer(jit_state_t *_jit, jit_pointer_t target)
{
  uint16_t thumb1_ldr = 0x4800;
  int32_t tmp = jit_gpr_regno(JIT_TMP1);
  ASSERT(tmp < 8);
  // Loaded addr is 4 bytes after the LDR, which is aligned, so offset is 0.
  emit_u16(_jit, thumb1_ldr | (tmp << 8));
  emit_u16(_jit, THUMB_BX|(_u4(tmp)<<3));
  emit_u32(_jit, (uint32_t) target);
}

static void
ldr_atomic(jit_state_t *_jit, int32_t dst, int32_t loc)
{
  T1_DMB(_jit, DMB_ISH);
  ldr_i(_jit, dst, loc);
  T1_DMB(_jit, DMB_ISH);
}

static void
str_atomic(jit_state_t *_jit, int32_t loc, int32_t val)
{
  T1_DMB(_jit, DMB_ISH);
  str_i(_jit, loc, val);
  T1_DMB(_jit, DMB_ISH);
}

static void
swap_atomic(jit_state_t *_jit, int32_t dst, int32_t loc, int32_t val)
{
  int32_t result = jit_gpr_regno(get_temp_gpr(_jit));
  int32_t val_or_tmp = dst == val ? jit_gpr_regno(get_temp_gpr(_jit)) : val;
  movr(_jit, val_or_tmp, val);
  T1_DMB(_jit, DMB_ISH);
  void *retry = jit_address(_jit);
  T1_LDREX(_jit, dst, loc, 0);
  T1_STREX(_jit, result, val_or_tmp, loc, 0);
  jit_patch_there(_jit, bnei(_jit, result, 0), retry);
  T1_DMB(_jit, DMB_ISH);
  if (dst == val) unget_temp_gpr(_jit);
  unget_temp_gpr(_jit);
}

static void
cas_atomic(jit_state_t *_jit, int32_t dst, int32_t loc, int32_t expected,
           int32_t desired)
{
  int32_t dst_or_tmp;
  if (dst == loc || dst == expected || dst == expected)
    dst_or_tmp = jit_gpr_regno(get_temp_gpr(_jit));
  else
    dst_or_tmp = dst;
  T1_DMB(_jit, DMB_ISH);
  void *retry = jit_address(_jit);
  T1_LDREX(_jit, dst_or_tmp, loc, 0);
  jit_reloc_t bad = bner(_jit, dst_or_tmp, expected);
  int result = jit_gpr_regno(get_temp_gpr(_jit));
  T1_STREX(_jit, result, desired, loc, 0);
  jit_patch_there(_jit, bnei(_jit, result, 0), retry);
  unget_temp_gpr(_jit);
  jit_patch_here(_jit, bad);
  T1_DMB(_jit, DMB_ISH);
  movr(_jit, dst, dst_or_tmp);
  unget_temp_gpr(_jit);
}

static void
breakpoint(jit_state_t *_jit)
{
  T1_BRK(_jit);
}
