//===-- gen/asm-x86.h - x86/x86_64 inline assembler handling ----*- C++ -*-===//
//
//                         LDC – the LLVM D compiler
//
// This file originates from work by David Friedman for GDC released under
// the GPL 2 and Artistic licenses. See the LICENSE file for details.
//
//===----------------------------------------------------------------------===//
//
// Parses "DMD-style" x86/x86_64 inline assembly blocks and converts them to
// GDC/LLVM inline assembler syntax.
//
// This file is designed to be included twice, once with the ASM_X86_64 define
// set to get the 64 bit asm parser, and once without to get the 32 bit one.
// This is a direct result of merging two disparate but largely identical
// implementations, and should be refactored to just use a runtime parameter
// for choosing the architecture.
//
//===----------------------------------------------------------------------===//

#include "dmd/expression.h"
#include "dmd/id.h"
#include "dmd/identifier.h"
#include "dmd/ldcbindings.h"
#include "dmd/mangle.h"
#include "gen/llvmhelpers.h" // printLabelName
#include <cctype>
#include "llvm/ADT/StringRef.h"

#ifndef ASM_X86_64
namespace AsmParserx8632 {
#else
namespace AsmParserx8664 {
#endif

typedef enum {
  Reg_Invalid = -1,
  Reg_EAX = 0,
  Reg_EBX,
  Reg_ECX,
  Reg_EDX,
  Reg_ESI,
  Reg_EDI,
  Reg_EBP,
  Reg_ESP,
  Reg_EIP,
  Reg_ST,
  Reg_ST1,
  Reg_ST2,
  Reg_ST3,
  Reg_ST4,
  Reg_ST5,
  Reg_ST6,
  Reg_ST7,
  Reg_MM0,
  Reg_MM1,
  Reg_MM2,
  Reg_MM3,
  Reg_MM4,
  Reg_MM5,
  Reg_MM6,
  Reg_MM7,
  Reg_XMM0,
  Reg_XMM1,
  Reg_XMM2,
  Reg_XMM3,
  Reg_XMM4,
  Reg_XMM5,
  Reg_XMM6,
  Reg_XMM7,

#ifdef ASM_X86_64
  Reg_RAX,
  Reg_RBX,
  Reg_RCX,
  Reg_RDX,
  Reg_RSI,
  Reg_RDI,
  Reg_RBP,
  Reg_RSP,
  Reg_R8,
  Reg_R9,
  Reg_R10,
  Reg_R11,
  Reg_R12,
  Reg_R13,
  Reg_R14,
  Reg_R15,
  Reg_R8B,
  Reg_R9B,
  Reg_R10B,
  Reg_R11B,
  Reg_R12B,
  Reg_R13B,
  Reg_R14B,
  Reg_R15B,
  Reg_R8W,
  Reg_R9W,
  Reg_R10W,
  Reg_R11W,
  Reg_R12W,
  Reg_R13W,
  Reg_R14W,
  Reg_R15W,
  Reg_R8D,
  Reg_R9D,
  Reg_R10D,
  Reg_R11D,
  Reg_R12D,
  Reg_R13D,
  Reg_R14D,
  Reg_R15D,
  Reg_XMM8,
  Reg_XMM9,
  Reg_XMM10,
  Reg_XMM11,
  Reg_XMM12,
  Reg_XMM13,
  Reg_XMM14,
  Reg_XMM15,
  Reg_RIP,
  Reg_SIL,
  Reg_DIL,
  Reg_BPL,
  Reg_SPL,
#endif

  Reg_EFLAGS,
  Reg_CS,
  Reg_DS,
  Reg_SS,
  Reg_ES,
  Reg_FS,
  Reg_GS,
  Reg_AX,
  Reg_BX,
  Reg_CX,
  Reg_DX,
  Reg_SI,
  Reg_DI,
  Reg_BP,
  Reg_SP,
  Reg_AL,
  Reg_AH,
  Reg_BL,
  Reg_BH,
  Reg_CL,
  Reg_CH,
  Reg_DL,
  Reg_DH,
  Reg_CR0,
  Reg_CR2,
  Reg_CR3,
  Reg_CR4,
  Reg_DR0,
  Reg_DR1,
  Reg_DR2,
  Reg_DR3,
  Reg_DR6,
  Reg_DR7,
  Reg_TR3,
  Reg_TR4,
  Reg_TR5,
  Reg_TR6,
  Reg_TR7
} Reg;

static const int N_Regs = /*gp*/ 8 + /*EIP*/ 1 + /*fp*/ 8 + /*mmx*/ 8 + /*sse*/ 8 +
                          /*seg*/ 6 + /*16bit*/ 8 + /*8bit*/ 8 + /*sys*/ 4 + 6 +
                          5 + /*flags*/ 1
#ifdef ASM_X86_64
                          + 8 /*RAX, etc*/
                          + 8 /*R8-15*/
                          + 4 /*SIL, etc. 8-bit*/
                          + 8 /*R8-15B*/
                          + 8 /*R8-15W*/
                          + 8 /*R8-15D*/
                          + 8 /*XMM8-15*/
                          + 1 /*RIP*/
#endif
    ;

#define NULL_TREE ""

static struct {
  llvm::StringRef name;
  std::string gccName; // GAS will take upper case, but GCC won't (needed for
                       // the clobber list)
  Identifier *ident;
  char size;
  signed char baseReg; // %% todo: Reg, Reg_XX
} regInfo[N_Regs] = {
    {"EAX", NULL_TREE, nullptr, 4, Reg_EAX},
    {"EBX", NULL_TREE, nullptr, 4, Reg_EBX},
    {"ECX", NULL_TREE, nullptr, 4, Reg_ECX},
    {"EDX", NULL_TREE, nullptr, 4, Reg_EDX},
    {"ESI", NULL_TREE, nullptr, 4, Reg_ESI},
    {"EDI", NULL_TREE, nullptr, 4, Reg_EDI},
    {"EBP", NULL_TREE, nullptr, 4, Reg_EBP},
    {"ESP", NULL_TREE, nullptr, 4, Reg_ESP},
    {"EIP", NULL_TREE, nullptr, 4, Reg_EIP},
    {"ST", NULL_TREE, nullptr, 10, Reg_ST},
    {"ST(1)", NULL_TREE, nullptr, 10, Reg_ST1},
    {"ST(2)", NULL_TREE, nullptr, 10, Reg_ST2},
    {"ST(3)", NULL_TREE, nullptr, 10, Reg_ST3},
    {"ST(4)", NULL_TREE, nullptr, 10, Reg_ST4},
    {"ST(5)", NULL_TREE, nullptr, 10, Reg_ST5},
    {"ST(6)", NULL_TREE, nullptr, 10, Reg_ST6},
    {"ST(7)", NULL_TREE, nullptr, 10, Reg_ST7},
    {"MM0", NULL_TREE, nullptr, 8, Reg_MM0},
    {"MM1", NULL_TREE, nullptr, 8, Reg_MM1},
    {"MM2", NULL_TREE, nullptr, 8, Reg_MM2},
    {"MM3", NULL_TREE, nullptr, 8, Reg_MM3},
    {"MM4", NULL_TREE, nullptr, 8, Reg_MM4},
    {"MM5", NULL_TREE, nullptr, 8, Reg_MM5},
    {"MM6", NULL_TREE, nullptr, 8, Reg_MM6},
    {"MM7", NULL_TREE, nullptr, 8, Reg_MM7},
    {"XMM0", NULL_TREE, nullptr, 16, Reg_XMM0},
    {"XMM1", NULL_TREE, nullptr, 16, Reg_XMM1},
    {"XMM2", NULL_TREE, nullptr, 16, Reg_XMM2},
    {"XMM3", NULL_TREE, nullptr, 16, Reg_XMM3},
    {"XMM4", NULL_TREE, nullptr, 16, Reg_XMM4},
    {"XMM5", NULL_TREE, nullptr, 16, Reg_XMM5},
    {"XMM6", NULL_TREE, nullptr, 16, Reg_XMM6},
    {"XMM7", NULL_TREE, nullptr, 16, Reg_XMM7},

#ifdef ASM_X86_64
    {"RAX", NULL_TREE, nullptr, 8, Reg_RAX},
    {"RBX", NULL_TREE, nullptr, 8, Reg_RBX},
    {"RCX", NULL_TREE, nullptr, 8, Reg_RCX},
    {"RDX", NULL_TREE, nullptr, 8, Reg_RDX},
    {"RSI", NULL_TREE, nullptr, 8, Reg_RSI},
    {"RDI", NULL_TREE, nullptr, 8, Reg_RDI},
    {"RBP", NULL_TREE, nullptr, 8, Reg_RBP},
    {"RSP", NULL_TREE, nullptr, 8, Reg_RSP},
    {"R8", NULL_TREE, nullptr, 8, Reg_R8},
    {"R9", NULL_TREE, nullptr, 8, Reg_R9},
    {"R10", NULL_TREE, nullptr, 8, Reg_R10},
    {"R11", NULL_TREE, nullptr, 8, Reg_R11},
    {"R12", NULL_TREE, nullptr, 8, Reg_R12},
    {"R13", NULL_TREE, nullptr, 8, Reg_R13},
    {"R14", NULL_TREE, nullptr, 8, Reg_R14},
    {"R15", NULL_TREE, nullptr, 8, Reg_R15},
    {"R8B", NULL_TREE, nullptr, 1, Reg_R8},
    {"R9B", NULL_TREE, nullptr, 1, Reg_R9},
    {"R10B", NULL_TREE, nullptr, 1, Reg_R10},
    {"R11B", NULL_TREE, nullptr, 1, Reg_R11},
    {"R12B", NULL_TREE, nullptr, 1, Reg_R12},
    {"R13B", NULL_TREE, nullptr, 1, Reg_R13},
    {"R14B", NULL_TREE, nullptr, 1, Reg_R14},
    {"R15B", NULL_TREE, nullptr, 1, Reg_R15},
    {"R8W", NULL_TREE, nullptr, 2, Reg_R8},
    {"R9W", NULL_TREE, nullptr, 2, Reg_R9},
    {"R10W", NULL_TREE, nullptr, 2, Reg_R10},
    {"R11W", NULL_TREE, nullptr, 2, Reg_R11},
    {"R12W", NULL_TREE, nullptr, 2, Reg_R12},
    {"R13W", NULL_TREE, nullptr, 2, Reg_R13},
    {"R14W", NULL_TREE, nullptr, 2, Reg_R14},
    {"R15W", NULL_TREE, nullptr, 2, Reg_R15},
    {"R8D", NULL_TREE, nullptr, 4, Reg_R8},
    {"R9D", NULL_TREE, nullptr, 4, Reg_R9},
    {"R10D", NULL_TREE, nullptr, 4, Reg_R10},
    {"R11D", NULL_TREE, nullptr, 4, Reg_R11},
    {"R12D", NULL_TREE, nullptr, 4, Reg_R12},
    {"R13D", NULL_TREE, nullptr, 4, Reg_R13},
    {"R14D", NULL_TREE, nullptr, 4, Reg_R14},
    {"R15D", NULL_TREE, nullptr, 4, Reg_R15},
    {"XMM8", NULL_TREE, nullptr, 16, Reg_XMM8},
    {"XMM9", NULL_TREE, nullptr, 16, Reg_XMM9},
    {"XMM10", NULL_TREE, nullptr, 16, Reg_XMM10},
    {"XMM11", NULL_TREE, nullptr, 16, Reg_XMM11},
    {"XMM12", NULL_TREE, nullptr, 16, Reg_XMM12},
    {"XMM13", NULL_TREE, nullptr, 16, Reg_XMM13},
    {"XMM14", NULL_TREE, nullptr, 16, Reg_XMM14},
    {"XMM15", NULL_TREE, nullptr, 16, Reg_XMM15},
    {"RIP", NULL_TREE, nullptr, 8, Reg_RIP},
    {"SIL", NULL_TREE, nullptr, 1, Reg_SIL},
    {"DIL", NULL_TREE, nullptr, 1, Reg_DIL},
    {"BPL", NULL_TREE, nullptr, 1, Reg_BPL},
    {"SPL", NULL_TREE, nullptr, 1, Reg_SPL},
#endif

    {"FLAGS", NULL_TREE, nullptr, 0,
     Reg_EFLAGS}, // the gcc name is "flags"; not used in assembler input
    {"CS", NULL_TREE, nullptr, 2, -1},
    {"DS", NULL_TREE, nullptr, 2, -1},
    {"SS", NULL_TREE, nullptr, 2, -1},
    {"ES", NULL_TREE, nullptr, 2, -1},
    {"FS", NULL_TREE, nullptr, 2, -1},
    {"GS", NULL_TREE, nullptr, 2, -1},
    {"AX", NULL_TREE, nullptr, 2, Reg_EAX},
    {"BX", NULL_TREE, nullptr, 2, Reg_EBX},
    {"CX", NULL_TREE, nullptr, 2, Reg_ECX},
    {"DX", NULL_TREE, nullptr, 2, Reg_EDX},
    {"SI", NULL_TREE, nullptr, 2, Reg_ESI},
    {"DI", NULL_TREE, nullptr, 2, Reg_EDI},
    {"BP", NULL_TREE, nullptr, 2, Reg_EBP},
    {"SP", NULL_TREE, nullptr, 2, Reg_ESP},
    {"AL", NULL_TREE, nullptr, 1, Reg_EAX},
    {"AH", NULL_TREE, nullptr, 1, Reg_EAX},
    {"BL", NULL_TREE, nullptr, 1, Reg_EBX},
    {"BH", NULL_TREE, nullptr, 1, Reg_EBX},
    {"CL", NULL_TREE, nullptr, 1, Reg_ECX},
    {"CH", NULL_TREE, nullptr, 1, Reg_ECX},
    {"DL", NULL_TREE, nullptr, 1, Reg_EDX},
    {"DH", NULL_TREE, nullptr, 1, Reg_EDX},
    {"CR0", NULL_TREE, nullptr, 0, -1},
    {"CR2", NULL_TREE, nullptr, 0, -1},
    {"CR3", NULL_TREE, nullptr, 0, -1},
    {"CR4", NULL_TREE, nullptr, 0, -1},
    {"DR0", NULL_TREE, nullptr, 0, -1},
    {"DR1", NULL_TREE, nullptr, 0, -1},
    {"DR2", NULL_TREE, nullptr, 0, -1},
    {"DR3", NULL_TREE, nullptr, 0, -1},
    {"DR6", NULL_TREE, nullptr, 0, -1},
    {"DR7", NULL_TREE, nullptr, 0, -1},
    {"TR3", NULL_TREE, nullptr, 0, -1},
    {"TR4", NULL_TREE, nullptr, 0, -1},
    {"TR5", NULL_TREE, nullptr, 0, -1},
    {"TR6", NULL_TREE, nullptr, 0, -1},
    {"TR7", NULL_TREE, nullptr, 0, -1}};

typedef enum {
  No_Type_Needed,
  Int_Types,
  Word_Types, // same as Int_Types, but byte is not allowed
  FP_Types,
  FPInt_Types,
  Byte_NoType, // byte only, but no type suffix
} TypeNeeded;

typedef enum { No_Link, Out_Mnemonic, Next_Form } OpLink;

typedef enum {
  Clb_SizeAX = 0x01,
  Clb_SizeDXAX = 0x02,
  Clb_EAX = 0x03,
#ifndef ASM_X86_64
  Clb_DXAX_Mask = 0x03,
#else
  Clb_DXAX_Mask = 0x103,
  Clb_SizeRDXRAX = 0x100,
#endif

  Clb_Flags = 0x04,
  Clb_DI = 0x08,
  Clb_SI = 0x10,
  Clb_CX = 0x20,
  Clb_ST = 0x40,
  Clb_SP = 0x80 // Doesn't actually let GCC know the frame pointer is modified
} ImplicitClober;

// "^ +/..\([A-Za-z_0-9]+\).*" -> "    \1,"
typedef enum {
  Op_Invalid,
  Op_Adjust,
  Op_Dst,
  Op_Upd,
  Op_DstW,
  Op_DstF,
  Op_UpdF,
  Op_DstSrc,
  Op_DstSrcF,
  Op_UpdSrcF,
  Op_DstSrcFW,
  Op_UpdSrcFW,
  Op_DstSrcSSE,
  Op_UpdSrcSSE,
  Op_DstSrcMMX,
  Op_DstSrcImmS,
  Op_DstSrcImmM,
  Op_ExtSrcImmS,
  Op_UpdSrcShft,
  Op_DstSrcNT,
  Op_UpdSrcNT,
  Op_DstMemNT,
  Op_DstRMBNT,
  Op_DstRMWNT,
  Op_UpdUpd,
  Op_UpdUpdF,
  Op_Src,
  Op_SrcRMWNT,
  Op_SrcW,
  Op_SrcImm,
  Op_Src_DXAXF,
  Op_SrcMemNT,
  Op_SrcMemNTF,
  Op_SrcSrc,
  Op_SrcSrcF,
  Op_SrcSrcFW,
  Op_SrcSrcSSEF,
  Op_SrcSrcMMX,
  Op_Shift,
  Op_Branch,
  Op_CBranch,
  Op_0,
  Op_0_AX,
  Op_0_DXAX,
  Op_0_DXCXAX,
  Op_Loop,
  Op_Flags,
  Op_F0_ST,
  Op_F0_P,
  Op_Fs_P,
  Op_Fis,
  Op_Fis_ST,
  Op_Fis_P,
  Op_Fid,
  Op_Fid_P,
  Op_FidR_P,
  Op_Ffd,
  Op_FfdR,
  Op_Ffd_P,
  Op_FfdR_P,
  Op_FfdRR_P,
  Op_Fd_P,
  Op_FdST,
  Op_FMath,
  Op_FMath0,
  Op_FMath2,
  Op_FdSTiSTi,
  Op_FdST0ST1,
  Op_FPMath,
  Op_FCmp,
  Op_FCmp1,
  Op_FCmpP,
  Op_FCmpP1,
  Op_FCmpFlg,
  Op_FCmpFlgP,
  Op_fld,
  Op_fldR,
  Op_fxch,
  Op_fxch1,
  Op_fxch0,
  Op_SizedStack,
  Op_bound,
  Op_bswap,
  Op_cmps,
  Op_cmpsd,
  Op_cmpsX,
  Op_cmpxchg,
#ifdef ASM_X86_64
  Op_cmpxchg16b,
#endif
  Op_cmpxchg8b,
  Op_cpuid,
  Op_enter,
  Op_fdisi,
  Op_feni,
  Op_fsetpm,
  Op_fXstsw,
  Op_imul,
  Op_imul2,
  Op_imul1,
  Op_in,
  Op_ins,
  Op_insX,
  Op_iret,
  Op_iretd,
#ifdef ASM_X86_64
  Op_iretq,
#endif
  Op_lods,
  Op_lodsX,
  Op_movs,
  Op_movsd,
  Op_movsX,
  Op_movsx,
  Op_movzx,
  Op_mul,
  Op_out,
  Op_outs,
  Op_outsX,
  Op_push,
  Op_ret,
  Op_retf,
  Op_scas,
  Op_scasX,
  Op_stos,
  Op_stosX,
  Op_xgetbv,
  Op_xlat,
  N_AsmOpInfo,
  Op_Align,
  Op_Even,
  Op_Naked,
  Op_db,
  Op_ds,
  Op_di,
  Op_dl,
  Op_df,
  Op_dd,
  Op_de
} AsmOp;

typedef enum {
  Opr_None = 0,
  OprC_MRI = 1,
  OprC_MR = 2,
  OprC_Mem = 3,
  OprC_Reg = 4,
  OprC_Imm = 5,
  OprC_SSE = 6,
  OprC_SSE_Mem = 7,
  OprC_R32 = 8,
  OprC_RWord = 9,
  OprC_RFP = 10,
  OprC_AbsRel = 11,
  OprC_Relative = 12,
  OprC_Port = 13, // DX or imm
  OprC_AX = 14,   // AL,AX,EAX
  OprC_DX = 15,   // only DX
  OprC_MMX = 16,
  OprC_MMX_Mem = 17,
  OprC_Shift = 18, // imm or CL

  Opr_ClassMask = 0x1f,

  Opr_Dest = 0x20,
  Opr_Update = 0x60,

  Opr_NoType = 0x80,
} OprVals;

typedef unsigned char Opr;

struct AsmOpInfo {
  Opr operands[3];
#ifndef ASM_X86_64
  unsigned char needsType : 3, implicitClobbers : 8, linkType : 2;
#else
  unsigned short needsType : 3, implicitClobbers : 9, linkType : 2;
#endif
  unsigned link;

  unsigned nOperands() {
    if (!operands[0]) {
      return 0;
    }
    if (!operands[1]) {
      return 1;
    }
    if (!operands[2]) {
      return 2;
    }
    return 3;
  }
};

typedef enum {
  Mn_fdisi,
  Mn_feni,
  Mn_fsetpm,
  Mn_iretw,
  Mn_iret,
#ifdef ASM_X86_64
  Mn_iretq,
#endif
  Mn_lret,
  Mn_cmpxchg8b,
#ifdef ASM_X86_64
  Mn_cmpxchg16b,
#endif
  N_AltMn
} Alternate_Mnemonics;

static const char *alternateMnemonics[N_AltMn] = {
    ".byte 0xdb, 0xe1",
    ".byte 0xdb, 0xe0",
    ".byte 0xdb, 0xe4",
    "iretw",
    "iret",
#ifdef ASM_X86_64
    "iretq",
#endif
    "lret",
    "cmpxchg8b",
#ifdef ASM_X86_64
    "cmpxchg16b",
#endif
};

#define mri OprC_MRI
#define mr OprC_MR
#define mem OprC_Mem
// for now mfp=mem
#define mfp OprC_Mem
#define reg OprC_Reg
#define imm OprC_Imm
#define sse OprC_SSE
#define ssem OprC_SSE_Mem
#define mmx OprC_MMX
#define mmxm OprC_MMX_Mem
#define r32 OprC_R32
#define rw OprC_RWord
#define rfp OprC_RFP
#define port OprC_Port
#define ax OprC_AX
#define dx OprC_DX
#define shft OprC_Shift
#define D Opr_Dest
#define U Opr_Update
#define N Opr_NoType

// D=dest, N=notype
// clang-format off
static AsmOpInfo asmOpInfo[N_AsmOpInfo] = {
    /* Op_Invalid   */ {},
    /* Op_Adjust    */ {{0, 0, 0}, 0, Clb_EAX /*just AX*/},
    /* Op_Dst       */ {{D | mr, 0, 0}, 1},
    /* Op_Upd       */ {{U | mr, 0, 0}, 1},
    /* Op_DstW      */ {{D | mr, 0, 0}, Word_Types},
    /* Op_DstF      */ {{D | mr, 0, 0}, 1, Clb_Flags},
    /* Op_UpdF      */ {{U | mr, 0, 0}, 1, Clb_Flags},
    /* Op_DstSrc    */ {{D | mr, mri, 0}, /**/ 1},
    /* Op_DstSrcF   */ {{D | mr, mri, 0}, /**/ 1, Clb_Flags},
    /* Op_UpdSrcF   */ {{U | mr, mri, 0}, /**/ 1, Clb_Flags},
    /* Op_DstSrcFW  */ {{D | mr, mri, 0}, /**/ Word_Types, Clb_Flags},
    /* Op_UpdSrcFW  */ {{U | mr, mri, 0}, /**/ Word_Types, Clb_Flags},
    /* Op_DstSrcSSE */ {{D | sse, ssem, 0}},
    /* Op_UpdSrcSSE */ {{U | sse, ssem, 0}},       // some may not be update %%
    /* Op_DstSrcMMX */ {{U | mmx, mmxm, 0}},       // some may not be update %%
    /* Op_DstSrcImmS*/ {{U | sse, ssem, N | imm}}, // some may not be update %%
    /* Op_DstSrcImmM*/ {{U | mmx, mmxm, N | imm}}, // some may not be update %%
    /* Op_ExtSrcImmS*/ {{D | mr, sse, N | imm}},   // used for extractps
    /* Op_UpdSrcShft*/ {{U | mr, reg, N | shft}, 1, Clb_Flags}, // 16/32 only
    /* Op_DstSrcNT  */ {{D | mr, mr, 0},
                        0}, // used for movd .. operands can be rm32,sse,mmx
    /* Op_UpdSrcNT  */ {{U | mr, mr, 0},
                        0}, // used for movd .. operands can be rm32,sse,mmx
    /* Op_DstMemNT  */ {{D | mem, 0, 0}},
    /* Op_DstRMBNT  */ {{D | mr, 0, 0}, Byte_NoType},
    /* Op_DstRMWNT  */ {{D | mr, 0, 0}},
    /* Op_UpdUpd    */ {{U | mr, U | mr, 0}, /**/ 1},
    /* Op_UpdUpdF   */ {{U | mr, U | mr, 0}, /**/ 1, Clb_Flags},
    /* Op_Src       */ {{mri, 0, 0}, 1},
    /* Op_SrcRMWNT  */ {{mr, 0, 0}, 0},
    /* Op_SrcW      */ {{mri, 0, 0}, Word_Types},
    /* Op_SrcImm    */ {{imm, 0, 0}},
    /* Op_Src_DXAXF */ {{mr, 0, 0}, 1, Clb_SizeDXAX | Clb_Flags},
    /* Op_SrcMemNT  */ {{mem, 0, 0}},
    /* Op_SrcMemNTF */ {{mem, 0, 0}, 0, Clb_Flags},
    /* Op_SrcSrc    */ {{mr, mri, 0}, 1},
    /* Op_SrcSrcF   */ {{mr, mri, 0}, 1, Clb_Flags},
    /* Op_SrcSrcFW  */ {{mr, mri, 0}, Word_Types, Clb_Flags},
    /* Op_SrcSrcSSEF*/ {{sse, ssem, 0}, 0, Clb_Flags},
    /* Op_SrcSrcMMX */ {{mmx, mmx, 0}},
    /* Op_Shift     */ {{D | mr, N | shft, 0}, /**/ 1, Clb_Flags},
    /* Op_Branch    */ {{mri, 0, 0}},
    /* Op_CBranch   */ {{imm, 0, 0}},
    /* Op_0         */ {{0, 0, 0}},
    /* Op_0_AX      */ {{0, 0, 0}, 0, Clb_SizeAX},
    /* Op_0_DXAX    */ {{0, 0, 0}, 0, Clb_SizeDXAX}, // but for cwd/cdq -- how
                                                     // do know the size..
    /* Op_0_DXCXAX*/   {{0, 0, 0}, 0, Clb_SizeDXAX | Clb_CX},
    /* Op_Loop      */ {{imm, 0, 0}, 0, Clb_CX},
    /* Op_Flags     */ {{0, 0, 0}, 0, Clb_Flags},
    /* Op_F0_ST     */ {{0, 0, 0}, 0, Clb_ST},
    /* Op_F0_P      */ {{0, 0, 0}, 0, Clb_ST}, // push, pops, etc. not sure how
                                               // to inform gcc..
    /* Op_Fs_P      */ {{mem, 0, 0}, 0, Clb_ST},   // "
    /* Op_Fis       */ {{mem, 0, 0}, FPInt_Types}, // only 16bit and 32bit, DMD
                                                   // defaults to 16bit
    /* Op_Fis_ST    */ {{mem, 0, 0}, FPInt_Types, Clb_ST}, // "
    /* Op_Fis_P     */ {{mem, 0, 0},
                        FPInt_Types,
                        Clb_ST}, // push and pop, fild so also 64 bit
    /* Op_Fid       */ {{D | mem, 0, 0}, FPInt_Types}, // only 16bit and 32bit,
                                                       // DMD defaults to 16bit
    /* Op_Fid_P     */ {{D | mem, 0, 0},
                        FPInt_Types,
                        Clb_ST,
                        Next_Form,
                        Op_FidR_P}, // push and pop, fild so also 64 bit
    /* Op_FidR_P    */ {{D | mem, rfp, 0},
                        FPInt_Types,
                        Clb_ST}, // push and pop, fild so also 64 bit
    /* Op_Ffd       */ {{D | mfp, 0, 0},
                        FP_Types,
                        0,
                        Next_Form,
                        Op_FfdR}, // only 16bit and 32bit, DMD defaults to
                                  // 16bit, reg form doesn't need type
    /* Op_FfdR      */ {{D | rfp, 0, 0}},
    /* Op_Ffd_P     */ {{D | mfp, 0, 0},
                        FP_Types,
                        Clb_ST,
                        Next_Form,
                        Op_FfdR_P}, // pop, fld so also 80 bit, "
    /* Op_FfdR_P    */ {{D | rfp, 0, 0}, 0, Clb_ST, Next_Form, Op_FfdRR_P},
    /* Op_FfdRR_P   */ {{D | rfp, rfp, 0}, 0, Clb_ST},
    /* Op_Fd_P      */ {{D | mem, 0, 0}, 0, Clb_ST}, // "
    /* Op_FdST      */ {{D | rfp, 0, 0}},
    /* Op_FMath     */ {{mfp, 0, 0},
                        FP_Types,
                        Clb_ST,
                        Next_Form,
                        Op_FMath0}, // and only single or double prec
    /* Op_FMath0    */ {{0, 0, 0}, 0, Clb_ST, Next_Form, Op_FMath2}, // pops
    /* Op_FMath2    */ {{D | rfp, rfp, 0},
                        0,
                        Clb_ST,
                        Next_Form,
                        Op_FdST0ST1}, // and only single or double prec
    /* Op_FdSTiSTi  */ {{D | rfp, rfp, 0}},
    /* Op_FdST0ST1  */ {{0, 0, 0}},
    /* Op_FPMath    */ {{D | rfp, rfp, 0},
                        0,
                        Clb_ST,
                        Next_Form,
                        Op_F0_P}, // pops
    /* Op_FCmp      */ {{mfp, 0, 0},
                        FP_Types,
                        0,
                        Next_Form,
                        Op_FCmp1}, // DMD defaults to float ptr
    /* Op_FCmp1     */ {{rfp, 0, 0}, 0, 0, Next_Form, Op_0},
    /* Op_FCmpP     */ {{mfp, 0, 0}, FP_Types, 0, Next_Form, Op_FCmpP1}, // pops
    /* Op_FCmpP1    */ {{rfp, 0, 0}, 0, 0, Next_Form, Op_F0_P},          // pops
    /* Op_FCmpFlg   */ {{rfp, 0, 0}, 0, Clb_Flags},
    /* Op_FCmpFlgP  */ {{rfp, 0, 0}, 0, Clb_Flags}, // pops
    /* Op_fld       */ {{mfp, 0, 0}, FP_Types, Clb_ST, Next_Form, Op_fldR},
    /* Op_fldR      */ {{rfp, 0, 0}, 0, Clb_ST},
    /* Op_fxch      */ {{D | rfp, D | rfp, 0},
                        0,
                        Clb_ST,
                        Next_Form,
                        Op_fxch1}, // not in intel manual?, but DMD allows it
                                   // (gas won't), second arg must be ST
    /* Op_fxch1     */ {{D | rfp, 0, 0}, 0, Clb_ST, Next_Form, Op_fxch0},
    /* Op_fxch0     */ {{0, 0, 0}, 0, Clb_ST}, // Also clobbers ST(1)
    /* Op_SizedStack*/ {{0, 0, 0}, 0, Clb_SP}, // type suffix special case
    /* Op_bound     */ {{mr, mri, 0},
                        Word_Types}, // operands *not* reversed for gas
    /* Op_bswap     */ {{D | r32, 0, 0}},
    /* Op_cmps      */ {{mem, mem, 0}, 1, Clb_DI | Clb_SI | Clb_Flags},
    /* Op_cmpsd     */ {{0, 0, 0},
                        0,
                        Clb_DI | Clb_SI | Clb_Flags,
                        Next_Form,
                        Op_DstSrcImmS},
    /* Op_cmpsX     */ {{0, 0, 0}, 0, Clb_DI | Clb_SI | Clb_Flags},
    /* Op_cmpxchg   */ {{D | mr, reg, 0}, 1, Clb_SizeAX | Clb_Flags},
#ifdef ASM_X86_64
    /* Op_cmpxchg16b */ {{D | mem /*128*/, 0, 0},
                         0,
                         Clb_SizeRDXRAX /*64*/ | Clb_Flags,
                         Out_Mnemonic,
                         Mn_cmpxchg16b},
#endif
    /* Op_cmpxchg8b */ {{D | mem /*64*/, 0, 0},
                        0,
                        Clb_SizeDXAX /*32*/ | Clb_Flags,
                        Out_Mnemonic,
                        Mn_cmpxchg8b},
    /* Op_cpuid     */ {{0, 0, 0}}, // Clobbers eax, ebx, ecx, and edx. Handled
                                    // specially below.
    /* Op_enter     */ {{imm, imm, 0}}, // operands *not* reversed for gas, %%
                                        // inform gcc of EBP clobber?,
    /* Op_fdisi     */ {{0, 0, 0}, 0, 0, Out_Mnemonic, Mn_fdisi},
    /* Op_feni      */ {{0, 0, 0}, 0, 0, Out_Mnemonic, Mn_feni},
    /* Op_fsetpm    */ {{0, 0, 0}, 0, 0, Out_Mnemonic, Mn_fsetpm},
    /* Op_fXstsw    */ {{D | mr, 0, 0}}, // ax is the only allowed register
    /* Op_imul      */ {{D | reg, mr, imm},
                        1,
                        Clb_Flags,
                        Next_Form,
                        Op_imul2}, // 16/32 only
    /* Op_imul2     */ {{D | reg, mri, 0},
                        1,
                        Clb_Flags,
                        Next_Form,
                        Op_imul1}, // 16/32 only
    /* Op_imul1     */ {{mr, 0, 0}, 1, Clb_Flags | Clb_SizeDXAX},
    /* Op_in        */ {{D | ax, N | port, 0}, 1},
    /* Op_ins       */ {{mem, N | dx, 0},
                        1,
                        Clb_DI}, // can't override ES segment for this one
    /* Op_insX      */ {{0, 0, 0},
                        0,
                        Clb_DI}, // output segment overrides %% needs work
    /* Op_iret      */ {{0, 0, 0}, 0, 0, Out_Mnemonic, Mn_iretw},
    /* Op_iretd     */ {{0, 0, 0}, 0, 0, Out_Mnemonic, Mn_iret},
#ifdef ASM_X86_64
    /* Op_iretq     */ {{0, 0, 0}, 0, 0, Out_Mnemonic, Mn_iretq},
#endif
    /* Op_lods      */ {{mem, 0, 0}, 1, Clb_SI},
    /* Op_lodsX     */ {{0, 0, 0}, 0, Clb_SI},
    /* Op_movs      */ {{mem, mem, 0},
                        1,
                        Clb_DI | Clb_SI}, // only src/DS can be overridden
    /* Op_movsd     */ {{0, 0, 0},
                        0,
                        Clb_DI | Clb_SI,
                        Next_Form,
                        Op_DstSrcSSE}, // %% gas doesn't accept movsd .. has to
                                       // movsl
    /* Op_movsX     */ {{0, 0, 0}, 0, Clb_DI | Clb_SI},
    /* Op_movsx     */ {{D | reg, mr, 0}, 1}, // type suffix is special case
    /* Op_movzx     */ {{D | reg, mr, 0}, 1}, // type suffix is special case
    /* Op_mul       */ {{U | ax, mr, 0},
                        1,
                        Clb_SizeDXAX | Clb_Flags,
                        Next_Form,
                        Op_Src_DXAXF},
    /* Op_out       */ {{N | port, ax, 0}, 1},
    /* Op_outs      */ {{N | dx, mem, 0}, 1, Clb_SI},
    /* Op_outsX     */ {{0, 0, 0}, 0, Clb_SI},
#ifndef ASM_X86_64
    /* Op_push      */ {{mri, 0, 0}, Word_Types, Clb_SP}, // would be Op_SrcW,
                                                          // but DMD defaults to
                                                          // 32-bit for
                                                          // immediate form
#else
    /* Op_push      */ {{mri, 0, 0}, 0, Clb_SP}, // would be Op_SrcW, but DMD
                                                 // defaults to 32-bit for
                                                 // immediate form
#endif
    /* Op_ret       */ {{imm, 0, 0}, 0, 0, Next_Form, Op_0},
    /* Op_retf      */ {{0, 0, 0}, 0, 0, Out_Mnemonic, Mn_lret},
    /* Op_scas      */ {{mem, 0, 0}, 1, Clb_DI | Clb_Flags},
    /* Op_scasX     */ {{0, 0, 0}, 0, Clb_DI | Clb_Flags},
    /* Op_stos      */ {{mem, 0, 0}, 1, Clb_DI},
    /* Op_stosX     */ {{0, 0, 0}, 0, Clb_DI},
#ifndef ASM_X86_64
    /* Op_xgetbv    */ {{0, 0, 0}, 0, Clb_SizeDXAX},
#else
    /* Op_xgetbv    */ {{0, 0, 0}, 0, Clb_SizeRDXRAX},
#endif
    /* Op_xlat      */ {{mem, 0, 0}, 0, Clb_SizeAX}

    /// * Op_arpl      */  { D|mr,  reg }, // 16 only -> DstSrc
    /// * Op_bsX       */  {   rw,  mrw,  0,    1, Clb_Flags },//->srcsrcf
    /// * Op_bt        */  {   mrw, riw,  0,    1, Clb_Flags },//->srcsrcf
    /// * Op_btX       */  { D|mrw, riw,  0,    1, Clb_Flags },//->dstsrcf ..
    /// immediate does not contribute to size
    /// * Op_cmovCC    */  { D|rw,  mrw,  0,    1 } // ->dstsrc
};
// clang-format on

#undef mri
#undef mr
#undef mem
#undef mfp
#undef reg
#undef imm
#undef sse
#undef ssem
#undef mmx
#undef mmxm
#undef r32
#undef rw
#undef rfp
#undef port
#undef ax
#undef dx
#undef shft
#undef D
#undef U
#undef N

struct AsmOpEnt {
  const char *inMnemonic;
  AsmOp asmOp;
};

/* Some opcodes have data size restrictions, which we don't check

   cmov, l<segreg> ?, lea, lsl, shld

   todo: push <immediate> is always the 32-bit form, even tho push <mem> is
   16-bit
*/

// WARNING: This table is expected to be SORTED ALPHABETICALLY.
static AsmOpEnt opData[] = {
#ifndef ASM_X86_64
    {"aaa", Op_Adjust},
    {"aad", Op_Adjust},
    {"aam", Op_Adjust},
    {"aas", Op_Adjust},
#endif
    {"adc", Op_UpdSrcF},
    {"add", Op_UpdSrcF},
    {"addpd", Op_UpdSrcSSE},
    {"addps", Op_UpdSrcSSE},
#ifdef ASM_X86_64
    {"addq", Op_UpdSrcSSE}, // ?
#endif
    {"addsd", Op_UpdSrcSSE},
    {"addss", Op_UpdSrcSSE},
    {"addsubpd", Op_UpdSrcSSE},
    {"addsubps", Op_UpdSrcSSE},
    {"aesdec", Op_UpdSrcSSE},
    {"aesdeclast", Op_UpdSrcSSE},
    {"aesenc", Op_UpdSrcSSE},
    {"aesenclast", Op_UpdSrcSSE},
    {"aesimc", Op_UpdSrcSSE},
    {"aeskeygenassist", Op_DstSrcImmS},
#ifndef ASM_X86_64
    {"align", Op_Align},
#endif
    {"and", Op_UpdSrcF},
    {"andnpd", Op_UpdSrcSSE},
    {"andnps", Op_UpdSrcSSE},
    {"andpd", Op_UpdSrcSSE},
    {"andps", Op_UpdSrcSSE},
#ifndef ASM_X86_64
    {"arpl", Op_UpdSrcNT},
#endif
    {"blendpd", Op_DstSrcImmS},
    {"blendps", Op_DstSrcImmS},
    {"blendvpd", Op_DstSrcSSE},
    {"blendvps", Op_DstSrcSSE},
    {"bound", Op_bound},
    {"bsf", Op_SrcSrcFW},
    {"bsr", Op_SrcSrcFW},
    {"bswap", Op_bswap},
    {"bt", Op_SrcSrcFW},
    {"btc", Op_UpdSrcFW},
    {"btr", Op_UpdSrcFW},
    {"bts", Op_UpdSrcFW},
    {"call", Op_Branch},
#ifdef ASM_X86_64
    {"callf", Op_Branch}, // ?
#endif
    {"cbw", Op_0_AX},
#ifndef ASM_X86_64
    {"cdq", Op_0_DXAX},
#else
    {"cdqe", Op_0_DXAX},
#endif
    {"clc", Op_Flags},
    {"cld", Op_Flags},
    {"clflush", Op_SrcMemNT},
    {"cli", Op_Flags},
    {"clts", Op_0},
    {"cmc", Op_Flags},
    {"cmova", Op_DstSrc},
    {"cmovae", Op_DstSrc},
    {"cmovb", Op_DstSrc},
    {"cmovbe", Op_DstSrc},
    {"cmovc", Op_DstSrc},
    {"cmove", Op_DstSrc},
    {"cmovg", Op_DstSrc},
    {"cmovge", Op_DstSrc},
    {"cmovl", Op_DstSrc},
    {"cmovle", Op_DstSrc},
    {"cmovna", Op_DstSrc},
    {"cmovnae", Op_DstSrc},
    {"cmovnb", Op_DstSrc},
    {"cmovnbe", Op_DstSrc},
    {"cmovnc", Op_DstSrc},
    {"cmovne", Op_DstSrc},
    {"cmovng", Op_DstSrc},
    {"cmovnge", Op_DstSrc},
    {"cmovnl", Op_DstSrc},
    {"cmovnle", Op_DstSrc},
    {"cmovno", Op_DstSrc},
    {"cmovnp", Op_DstSrc},
    {"cmovns", Op_DstSrc},
    {"cmovnz", Op_DstSrc},
    {"cmovo", Op_DstSrc},
    {"cmovp", Op_DstSrc},
    {"cmovpe", Op_DstSrc},
    {"cmovpo", Op_DstSrc},
    {"cmovs", Op_DstSrc},
    {"cmovz", Op_DstSrc},
    {"cmp", Op_SrcSrcF},
    {"cmppd", Op_DstSrcImmS},
    {"cmpps", Op_DstSrcImmS},
#ifdef ASM_X86_64
    {"cmpq", Op_DstSrcNT}, // ?
#endif
    {"cmps", Op_cmps},
    {"cmpsb", Op_cmpsX},
    {"cmpsd", Op_cmpsd}, // string cmp, and SSE cmp
#ifdef ASM_X86_64
    {"cmpsq", Op_cmpsX},
#endif
    {"cmpss", Op_DstSrcImmS},
    {"cmpsw", Op_cmpsX},
    {"cmpxchg", Op_cmpxchg},
#ifdef ASM_X86_64
    {"cmpxchg16b", Op_cmpxchg16b},
#endif
    {"cmpxchg8b", Op_cmpxchg8b},
    {"comisd", Op_SrcSrcSSEF},
    {"comiss", Op_SrcSrcSSEF},
    {"cpuid", Op_cpuid},
#ifdef ASM_X86_64
    {"cqo", Op_0_DXAX},
#endif
    {"crc32", Op_DstSrc},
    {"cvtdq2pd", Op_DstSrcSSE},
    {"cvtdq2ps", Op_DstSrcSSE},
    {"cvtpd2dq", Op_DstSrcSSE},
    {"cvtpd2pi", Op_DstSrcSSE},
    {"cvtpd2ps", Op_DstSrcSSE},
    {"cvtpi2pd", Op_DstSrcSSE},
    {"cvtpi2ps", Op_DstSrcSSE},
    {"cvtps2dq", Op_DstSrcSSE},
    {"cvtps2pd", Op_DstSrcSSE},
    {"cvtps2pi", Op_DstSrcSSE},
    {"cvtsd2si", Op_DstSrcSSE},
    {"cvtsd2ss", Op_DstSrcSSE},
    {"cvtsi2sd", Op_DstSrcSSE},
    {"cvtsi2ss", Op_DstSrcSSE},
    {"cvtss2sd", Op_DstSrcSSE},
    {"cvtss2si", Op_DstSrcSSE},
    {"cvttpd2dq", Op_DstSrcSSE},
    {"cvttpd2pi", Op_DstSrcSSE},
    {"cvttps2dq", Op_DstSrcSSE},
    {"cvttps2pi", Op_DstSrcSSE},
    {"cvttsd2si", Op_DstSrcSSE},
    {"cvttss2si", Op_DstSrcSSE},
    {"cwd", Op_0_DXAX},
#ifndef ASM_X86_64
    {"cwde", Op_0_AX},
#else
    {"cwde", Op_0_DXAX},
#endif
//  {"da", XXXX}, // dunno what this is -- takes labels?
#ifndef ASM_X86_64
    {"daa", Op_Adjust},
    {"das", Op_Adjust},
#endif
    {"db", Op_db},
    {"dd", Op_dd},
    {"de", Op_de},
    {"dec", Op_UpdF},
    {"df", Op_df},
    {"di", Op_di},
    {"div", Op_Src_DXAXF},
    {"divpd", Op_UpdSrcSSE},
    {"divps", Op_UpdSrcSSE},
    {"divsd", Op_UpdSrcSSE},
    {"divss", Op_UpdSrcSSE},
    {"dl", Op_dl},
    {"dppd", Op_DstSrcImmS},
    {"dpps", Op_DstSrcImmS},
    {"dq", Op_dl},
    {"ds", Op_ds},
    {"dt", Op_de},
    {"dw", Op_ds},
    {"emms", Op_0}, // clobber all mmx/fp?
    {"enter", Op_enter},
    {"even", Op_Even},
    {"extractps", Op_ExtSrcImmS},
    {"f2xm1", Op_F0_ST}, // %% most of these are update...
    {"fabs", Op_F0_ST},
    {"fadd", Op_FMath},
    {"faddp", Op_FPMath},
    {"fbld", Op_Fs_P},
    {"fbstp", Op_Fd_P},
    {"fchs", Op_F0_ST},
    {"fclex", Op_0},
    {"fcmovb", Op_FdSTiSTi}, // but only ST(0) can be the destination -- should
                             // be FdST0STi
    {"fcmovbe", Op_FdSTiSTi},
    {"fcmove", Op_FdSTiSTi},
    {"fcmovnb", Op_FdSTiSTi},
    {"fcmovnbe", Op_FdSTiSTi},
    {"fcmovne", Op_FdSTiSTi},
    {"fcmovnu", Op_FdSTiSTi},
    {"fcmovu", Op_FdSTiSTi},
    {"fcom", Op_FCmp},
    {"fcomi", Op_FCmpFlg},
    {"fcomip", Op_FCmpFlgP},
    {"fcomp", Op_FCmpP},
    {"fcompp", Op_F0_P}, // pops twice
    {"fcos", Op_F0_ST},
    {"fdecstp", Op_F0_P}, // changes stack
    {"fdisi", Op_fdisi},
    {"fdiv", Op_FMath},
    {"fdivp", Op_FPMath},
    {"fdivr", Op_FMath},
    {"fdivrp", Op_FPMath},
    {"feni", Op_feni},
    {"ffree", Op_FdST},
    {"fiadd", Op_Fis_ST},
    {"ficom", Op_Fis},
    {"ficomp", Op_Fis_P},
    {"fidiv", Op_Fis_ST},
    {"fidivr", Op_Fis_ST},
    {"fild", Op_Fis_P},
    {"fimul", Op_Fis_ST},
    {"fincstp", Op_F0_P},
    {"finit", Op_F0_P},
    {"fist", Op_Fid}, // only 16,32bit
    {"fistp", Op_Fid_P},
    {"fisttp", Op_Fid_P},
    {"fisub", Op_Fis_ST},
    {"fisubr", Op_Fis_ST},
    {"fld", Op_fld},
    {"fld1", Op_F0_P},
    {"fldcw", Op_SrcMemNT},
    {"fldenv", Op_SrcMemNT},
    {"fldl2e", Op_F0_P},
    {"fldl2t", Op_F0_P},
    {"fldlg2", Op_F0_P},
    {"fldln2", Op_F0_P},
    {"fldpi", Op_F0_P},
    {"fldz", Op_F0_P},
    {"fmul", Op_FMath},
    {"fmulp", Op_FPMath},
    {"fnclex", Op_0},
    {"fndisi", Op_fdisi}, // ??
    {"fneni", Op_feni},   // ??
    {"fninit", Op_0},
    {"fnop", Op_0},
    {"fnsave", Op_DstMemNT},
    {"fnstcw", Op_DstMemNT},
    {"fnstenv", Op_DstMemNT},
    {"fnstsw", Op_fXstsw},
    {"fpatan", Op_F0_P}, // pop and modify new ST
    {"fprem", Op_F0_ST},
    {"fprem1", Op_F0_ST},
    {"fptan", Op_F0_P}, // modify ST and push 1.0
    {"frndint", Op_F0_ST},
    {"frstor", Op_SrcMemNT}, // but clobbers everything
    {"fsave", Op_DstMemNT},
    {"fscale", Op_F0_ST},
    {"fsetpm", Op_fsetpm},
    {"fsin", Op_F0_ST},
    {"fsincos", Op_F0_P},
    {"fsqrt", Op_F0_ST},
    {"fst", Op_Ffd},
    {"fstcw", Op_DstMemNT},
    {"fstenv", Op_DstMemNT},
    {"fstp", Op_Ffd_P},
    {"fstsw", Op_fXstsw},
    {"fsub", Op_FMath},
    {"fsubp", Op_FPMath},
    {"fsubr", Op_FMath},
    {"fsubrp", Op_FPMath},
    {"ftst", Op_0},
    {"fucom", Op_FCmp},
    {"fucomi", Op_FCmpFlg},
    {"fucomip", Op_FCmpFlgP},
    {"fucomp", Op_FCmpP},
    {"fucompp", Op_F0_P}, // pops twice
    {"fwait", Op_0},
    {"fxam", Op_0},
    {"fxch", Op_fxch},
    {"fxrstor", Op_SrcMemNT}, // clobbers FP,MMX,SSE
    {"fxsave", Op_DstMemNT},
    {"fxtract", Op_F0_P}, // pushes
    {"fyl2x", Op_F0_P},   // pops
    {"fyl2xp1", Op_F0_P}, // pops
    {"haddpd", Op_UpdSrcSSE},
    {"haddps", Op_UpdSrcSSE},
    {"hlt", Op_0},
    {"hsubpd", Op_UpdSrcSSE},
    {"hsubps", Op_UpdSrcSSE},
    {"idiv", Op_Src_DXAXF},
    {"imul", Op_imul},
    {"in", Op_in},
    {"inc", Op_UpdF},
    {"ins", Op_ins},
    {"insb", Op_insX},
    {"insd", Op_insX},
    {"insertps", Op_DstSrcImmS},
    {"insw", Op_insX},
    {"int", Op_SrcImm},
    {"into", Op_0},
    {"invd", Op_0},
    {"invlpg", Op_SrcMemNT},
    {"iret", Op_iret},
    {"iretd", Op_iretd},
#ifdef ASM_X86_64
    {"iretq", Op_iretq}, // ?
#endif
    {"ja", Op_CBranch},
    {"jae", Op_CBranch},
    {"jb", Op_CBranch},
    {"jbe", Op_CBranch},
    {"jc", Op_CBranch},
    {"jcxz", Op_CBranch},
    {"je", Op_CBranch},
    {"jecxz", Op_CBranch},
    {"jg", Op_CBranch},
    {"jge", Op_CBranch},
    {"jl", Op_CBranch},
    {"jle", Op_CBranch},
    {"jmp", Op_Branch},
#ifdef ASM_X86_64
    {"jmpe", Op_Branch}, // ?
    {"jmpf", Op_Branch}, // ?
#endif
    {"jna", Op_CBranch},
    {"jnae", Op_CBranch},
    {"jnb", Op_CBranch},
    {"jnbe", Op_CBranch},
    {"jnc", Op_CBranch},
    {"jne", Op_CBranch},
    {"jng", Op_CBranch},
    {"jnge", Op_CBranch},
    {"jnl", Op_CBranch},
    {"jnle", Op_CBranch},
    {"jno", Op_CBranch},
    {"jnp", Op_CBranch},
    {"jns", Op_CBranch},
    {"jnz", Op_CBranch},
    {"jo", Op_CBranch},
    {"jp", Op_CBranch},
    {"jpe", Op_CBranch},
    {"jpo", Op_CBranch},
#ifdef ASM_X86_64
    {"jrcxz", Op_CBranch}, // Not supported by DMD
#endif
    {"js", Op_CBranch},
    {"jz", Op_CBranch},
    {"lahf", Op_0_AX},
    {"lar", Op_DstSrcFW}, // reg dest only
    {"lddqu", Op_DstSrcSSE},
    {"ldmxcsr", Op_SrcMemNT},
    {"lds", Op_DstSrc}, // reg dest only
    {"lea", Op_DstSrc}, // "
#ifdef ASM_X86_64
    {"leaq", Op_DstSrcSSE}, // ?
#endif
    {"leave", Op_0}, // EBP,ESP clobbers
#ifndef ASM_X86_64
    {"les", Op_DstSrc},
#endif
    {"lfence", Op_0},
    {"lfs", Op_DstSrc},
    {"lgdt", Op_SrcMemNT},
    {"lgs", Op_DstSrc},
    {"lidt", Op_SrcMemNT},
    {"lldt", Op_SrcRMWNT},
    {"lmsw", Op_SrcRMWNT},
    {"lock", Op_0},
    {"lods", Op_lods},
    {"lodsb", Op_lodsX},
    {"lodsd", Op_lodsX},
#ifdef ASM_X86_64
    {"lodsq", Op_lodsX},
#endif
    {"lodsw", Op_lodsX},
    {"loop", Op_Loop},
    {"loope", Op_Loop},
    {"loopne", Op_Loop},
    {"loopnz", Op_Loop},
    {"loopz", Op_Loop},
    {"lsl", Op_DstSrcFW}, // reg dest only
    {"lss", Op_DstSrc},
    {"ltr", Op_DstMemNT},
    {"maskmovdqu", Op_SrcSrcMMX}, // writes to [edi]
    {"maskmovq", Op_SrcSrcMMX},
    {"maxpd", Op_UpdSrcSSE},
    {"maxps", Op_UpdSrcSSE},
    {"maxsd", Op_UpdSrcSSE},
    {"maxss", Op_UpdSrcSSE},
    {"mfence", Op_0},
    {"minpd", Op_UpdSrcSSE},
    {"minps", Op_UpdSrcSSE},
    {"minsd", Op_UpdSrcSSE},
    {"minss", Op_UpdSrcSSE},
    {"monitor", Op_0},
    {"mov", Op_DstSrc},
    {"movapd", Op_DstSrcSSE},
    {"movaps", Op_DstSrcSSE},
#ifdef ASM_X86_64
    {"movb", Op_DstSrcNT}, // ?
#endif
    {"movd", Op_DstSrcNT}, // also mmx and sse
    {"movddup", Op_DstSrcSSE},
    {"movdq2q", Op_DstSrcNT}, // mmx/sse
    {"movdqa", Op_DstSrcSSE},
    {"movdqu", Op_DstSrcSSE},
    {"movhlps", Op_DstSrcSSE},
    {"movhpd", Op_DstSrcSSE},
    {"movhps", Op_DstSrcSSE},
#ifdef ASM_X86_64
    {"movl", Op_DstSrc}, // ?
#endif
    {"movlhps", Op_DstSrcSSE},
    {"movlpd", Op_DstSrcSSE},
    {"movlps", Op_DstSrcSSE},
    {"movmskpd", Op_DstSrcSSE},
    {"movmskps", Op_DstSrcSSE},
    {"movntdq", Op_DstSrcNT}, // limited to sse, but mem dest
    {"movntdqa", Op_DstSrcNT},
    {"movnti", Op_DstSrcNT},  // limited to gpr, but mem dest
    {"movntpd", Op_DstSrcNT}, // limited to sse, but mem dest
    {"movntps", Op_DstSrcNT}, // limited to sse, but mem dest
    {"movntq", Op_DstSrcNT},  // limited to mmx, but mem dest
    {"movq", Op_DstSrcNT},    // limited to sse and mmx
    {"movq2dq", Op_DstSrcNT}, // limited to sse <- mmx regs
    {"movs", Op_movs},
    {"movsb", Op_movsX},
    {"movsd", Op_movsd},
    {"movshdup", Op_DstSrcSSE},
    {"movsldup", Op_DstSrcSSE},
#ifdef ASM_X86_64
    {"movsq", Op_movsd},
#endif
    {"movss", Op_DstSrcSSE},
    {"movsw", Op_movsX},
    {"movsx", Op_movsx}, // word-only, reg dest
#ifdef ASM_X86_64
    {"movsxd", Op_movsx},
#endif
    {"movupd", Op_DstSrcSSE},
    {"movups", Op_DstSrcSSE},
#ifdef ASM_X86_64
    {"movzbl", Op_DstSrcNT},
#endif
    {"movzx", Op_movzx},
    {"mpsadbw", Op_DstSrcImmS},
    {"mul", Op_mul},
    {"mulpd", Op_UpdSrcSSE},
    {"mulps", Op_UpdSrcSSE},
    {"mulsd", Op_UpdSrcSSE},
    {"mulss", Op_UpdSrcSSE},
    {"mwait", Op_0},
    {"naked", Op_Naked},
    {"neg", Op_UpdF},
    {"nop", Op_0},
    {"not", Op_Upd},
    {"or", Op_UpdSrcF},
    {"orpd", Op_UpdSrcSSE},
    {"orps", Op_UpdSrcSSE},
    {"out", Op_out},
    {"outs", Op_outs},
    {"outsb", Op_outsX},
    {"outsd", Op_outsX},
    {"outsw", Op_outsX},
#ifdef ASM_X86_64
    {"pabsb", Op_DstSrcSSE},
    {"pabsq", Op_DstSrcSSE},
    {"pabsw", Op_DstSrcSSE},
#endif
    {"packssdw", Op_DstSrcMMX}, // %% also SSE
    {"packsswb", Op_DstSrcMMX},
    {"packusdw", Op_DstSrcSSE},
    {"packuswb", Op_DstSrcMMX},
    {"paddb", Op_DstSrcMMX},
    {"paddd", Op_DstSrcMMX},
    {"paddq", Op_DstSrcMMX},
    {"paddsb", Op_DstSrcMMX},
    {"paddsw", Op_DstSrcMMX},
    {"paddusb", Op_DstSrcMMX},
    {"paddusw", Op_DstSrcMMX},
    {"paddw", Op_DstSrcMMX},
    {"palignr", Op_DstSrcImmS},
    {"pand", Op_DstSrcMMX},
    {"pandn", Op_DstSrcMMX},
#ifdef ASM_X86_64
    {"pause", Op_DstSrcMMX}, // ?
#endif
    {"pavgb", Op_DstSrcMMX},
    {"pavgusb", Op_DstSrcMMX}, // AMD 3dNow!
    {"pavgw", Op_DstSrcMMX},
    {"pblendvb", Op_UpdSrcSSE}, // implicit xmm0
    {"pblendw", Op_DstSrcImmS},
    {"pcmpeqb", Op_DstSrcMMX},
    {"pcmpeqd", Op_DstSrcMMX},
    {"pcmpeqq", Op_UpdSrcSSE},
    {"pcmpeqw", Op_DstSrcMMX},
    {"pcmpestri", Op_DstSrcImmS},
    {"pcmpestrm", Op_DstSrcImmS},
    {"pcmpgtb", Op_DstSrcMMX},
    {"pcmpgtd", Op_DstSrcMMX},
    {"pcmpgtq", Op_UpdSrcSSE},
    {"pcmpgtw", Op_DstSrcMMX},
    {"pcmpistri", Op_DstSrcImmS},
    {"pcmpistrm", Op_DstSrcImmS},
    {"pextrb", Op_DstSrcImmM}, // gpr32 dest
    {"pextrd", Op_DstSrcImmM}, // gpr32 dest
    {"pextrq", Op_DstSrcImmM}, // gpr32 dest
    {"pextrw", Op_DstSrcImmM}, // gpr32 dest
    {"pf2id", Op_DstSrcMMX},   // %% AMD 3dNow! opcodes
    {"pfacc", Op_DstSrcMMX},
    {"pfadd", Op_DstSrcMMX},
    {"pfcmpeq", Op_DstSrcMMX},
    {"pfcmpge", Op_DstSrcMMX},
    {"pfcmpgt", Op_DstSrcMMX},
    {"pfmax", Op_DstSrcMMX},
    {"pfmin", Op_DstSrcMMX},
    {"pfmul", Op_DstSrcMMX},
    {"pfnacc", Op_DstSrcMMX},  // 3dNow values are returned in MM0 register,
    {"pfpnacc", Op_DstSrcMMX}, // so should be correct to use Op_DstSrcMMX.
    {"pfrcp", Op_DstSrcMMX},
    {"pfrcpit1", Op_DstSrcMMX},
    {"pfrcpit2", Op_DstSrcMMX},
    {"pfrsqit1", Op_DstSrcMMX},
    {"pfrsqrt", Op_DstSrcMMX},
    {"pfsub", Op_DstSrcMMX},
    {"pfsubr", Op_DstSrcMMX},
#ifdef ASM_X86_64
    {"phaddd", Op_UpdSrcSSE},
    {"phaddsw", Op_UpdSrcSSE},
    {"phaddw", Op_UpdSrcSSE},
    {"phminposuw", Op_UpdSrcSSE},
    {"phsubd", Op_UpdSrcSSE},
    {"phsubsw", Op_UpdSrcSSE},
    {"phsubw", Op_UpdSrcSSE},
#endif
    {"pi2fd", Op_DstSrcMMX},   // %%
    {"pinsrb", Op_DstSrcImmM}, // gpr32(16), mem16 src, sse too
    {"pinsrd", Op_DstSrcImmM}, // gpr32(16), mem16 src, sse too
    {"pinsrq", Op_DstSrcImmM}, // gpr32(16), mem16 src, sse too
    {"pinsrw", Op_DstSrcImmM}, // gpr32(16), mem16 src, sse too
#ifdef ASM_X86_64
    {"pmaddubsw", Op_UpdSrcSSE},
#endif
    {"pmaddwd", Op_DstSrcMMX},
    {"pmaxsb", Op_UpdSrcSSE},
    {"pmaxsd", Op_UpdSrcSSE},
    {"pmaxsw", Op_DstSrcMMX},
    {"pmaxub", Op_DstSrcMMX},
    {"pmaxud", Op_UpdSrcSSE},
    {"pmaxuw", Op_UpdSrcSSE},
    {"pminsb", Op_UpdSrcSSE},
    {"pminsd", Op_UpdSrcSSE},
    {"pminsw", Op_DstSrcMMX},
    {"pminub", Op_DstSrcMMX},
    {"pminud", Op_UpdSrcSSE},
    {"pminuw", Op_UpdSrcSSE},
    {"pmovmskb", Op_DstSrcMMX},
    {"pmovsxbd", Op_DstSrcSSE},
    {"pmovsxbq", Op_DstSrcSSE},
    {"pmovsxbw", Op_DstSrcSSE},
    {"pmovsxdq", Op_DstSrcSSE},
    {"pmovsxwd", Op_DstSrcSSE},
    {"pmovsxwq", Op_DstSrcSSE},
    {"pmovzxbd", Op_DstSrcSSE},
    {"pmovzxbq", Op_DstSrcSSE},
    {"pmovzxbw", Op_DstSrcSSE},
    {"pmovzxdq", Op_DstSrcSSE},
    {"pmovzxwd", Op_DstSrcSSE},
    {"pmovzxwq", Op_DstSrcSSE},
    {"pmuldq", Op_DstSrcSSE},
#ifdef ASM_X86_64
    {"pmulhrsw", Op_DstSrcMMX},
#endif
    {"pmulhrw", Op_DstSrcMMX}, // AMD 3dNow!
    {"pmulhuw", Op_DstSrcMMX},
    {"pmulhw", Op_DstSrcMMX},
    {"pmulld", Op_UpdSrcSSE},
    {"pmullw", Op_DstSrcMMX},
    {"pmuludq", Op_DstSrcMMX}, // also sse
    {"pop", Op_DstW},
#ifndef ASM_X86_64
    {"popa", Op_SizedStack},  // For intel this is always 16-bit
    {"popad", Op_SizedStack}, // GAS doesn't accept 'popad' -- these clobber
                              // everything, but supposedly it would be used to
                              // preserve clobbered regs
#endif
    {"popcnt", Op_DstSrc},
    {"popf", Op_0}, // rewrite the insn with a special case
#ifndef ASM_X86_64
    {"popfd", Op_0},
#else
    {"popfq", Op_0}, // Op_SizedStack?
    {"popq", Op_push},
#endif
    {"por", Op_DstSrcMMX},
    {"prefetchnta", Op_SrcMemNT},
    {"prefetcht0", Op_SrcMemNT},
    {"prefetcht1", Op_SrcMemNT},
    {"prefetcht2", Op_SrcMemNT},
    {"psadbw", Op_DstSrcMMX},
    {"pshufb", Op_UpdSrcSSE},
#ifndef ASM_X86_64
    {"pshufd", Op_DstSrcImmM},
    {"pshufhw", Op_DstSrcImmM},
    {"pshuflw", Op_DstSrcImmM},
    {"pshufw", Op_DstSrcImmM},
#else
    {"pshufd", Op_DstSrcImmS},
    {"pshufhw", Op_DstSrcImmS},
    {"pshuflw", Op_DstSrcImmS},
    {"pshufw", Op_DstSrcImmS},
    {"psignb", Op_UpdSrcSSE},
    {"psignd", Op_UpdSrcSSE},
    {"psignw", Op_UpdSrcSSE},
#endif
    {"pslld", Op_DstSrcMMX}, // immediate operands...
    {"pslldq", Op_DstSrcMMX},
    {"psllq", Op_DstSrcMMX},
    {"psllw", Op_DstSrcMMX},
    {"psrad", Op_DstSrcMMX},
    {"psraw", Op_DstSrcMMX},
    {"psrld", Op_DstSrcMMX},
    {"psrldq", Op_DstSrcMMX},
    {"psrlq", Op_DstSrcMMX},
    {"psrlw", Op_DstSrcMMX},
    {"psubb", Op_DstSrcMMX},
    {"psubd", Op_DstSrcMMX},
    {"psubq", Op_DstSrcMMX},
    {"psubsb", Op_DstSrcMMX},
    {"psubsw", Op_DstSrcMMX},
    {"psubusb", Op_DstSrcMMX},
    {"psubusw", Op_DstSrcMMX},
    {"psubw", Op_DstSrcMMX},
    {"pswapd", Op_DstSrcMMX}, // AMD 3dNow!
    {"ptest", Op_SrcSrcSSEF},
    {"punpckhbw", Op_DstSrcMMX},
    {"punpckhdq", Op_DstSrcMMX},
    {"punpckhqdq", Op_DstSrcMMX},
    {"punpckhwd", Op_DstSrcMMX},
    {"punpcklbw", Op_DstSrcMMX},
    {"punpckldq", Op_DstSrcMMX},
    {"punpcklqdq", Op_DstSrcMMX},
    {"punpcklwd", Op_DstSrcMMX},
    {"push", Op_push},
#ifndef ASM_X86_64
    {"pusha", Op_SizedStack},
    {"pushad", Op_SizedStack},
#endif
    {"pushf", Op_0},
#ifndef ASM_X86_64
    {"pushfd", Op_0},
#else
    {"pushfq", Op_0},   // Op_SizedStack?
    {"pushq", Op_push}, // ?
#endif
    {"pxor", Op_DstSrcMMX},
    {"rcl", Op_Shift}, // limited src operands -- change to shift
    {"rcpps", Op_DstSrcSSE},
    {"rcpss", Op_DstSrcSSE},
    {"rcr", Op_Shift},
    //{"rdfsbase", XXXX},
    //{"rdgsbase", XXXX},
    {"rdmsr", Op_0_DXAX},
    {"rdpmc", Op_0_DXAX},
    //{"rdrand", XXXX},
    {"rdtsc", Op_0_DXAX},
    /*
        RDTSCP clobbers EDX:EAX and ECX
        From the Intel manual:
        EDX:EAX ← TimeStampCounter;
        ECX ← IA32_TSC_AUX[31:0];
    */
    {"rdtscp", Op_0_DXCXAX},
    {"rep", Op_0},
    {"repe", Op_0},
    {"repne", Op_0},
    {"repnz", Op_0},
    {"repz", Op_0},
    {"ret", Op_ret},
    {"retf", Op_retf},
#ifdef ASM_X86_64
    {"retn", Op_retf}, // ?
#endif
    {"rol", Op_Shift},
    {"ror", Op_Shift},
    {"roundpd", Op_DstSrcImmS},
    {"roundps", Op_DstSrcImmS},
    {"roundsd", Op_DstSrcImmS},
    {"roundss", Op_DstSrcImmS},
    {"rsm", Op_0},
    {"rsqrtps", Op_DstSrcSSE},
    {"rsqrtss", Op_DstSrcSSE},
    {"sahf", Op_Flags},
    {"sal", Op_Shift},
#ifdef ASM_X86_64
    {"salq", Op_DstSrcNT},
#endif
    {"sar", Op_Shift},
    {"sbb", Op_UpdSrcF},
    {"scas", Op_scas},
    {"scasb", Op_scasX},
    {"scasd", Op_scasX},
#ifdef ASM_X86_64
    {"scasq", Op_scasX},
#endif
    {"scasw", Op_scasX},
    {"seta", Op_DstRMBNT}, // also gpr8
    {"setae", Op_DstRMBNT},
    {"setb", Op_DstRMBNT},
    {"setbe", Op_DstRMBNT},
    {"setc", Op_DstRMBNT},
    {"sete", Op_DstRMBNT},
    {"setg", Op_DstRMBNT},
    {"setge", Op_DstRMBNT},
    {"setl", Op_DstRMBNT},
    {"setle", Op_DstRMBNT},
    {"setna", Op_DstRMBNT},
    {"setnae", Op_DstRMBNT},
    {"setnb", Op_DstRMBNT},
    {"setnbe", Op_DstRMBNT},
    {"setnc", Op_DstRMBNT},
    {"setne", Op_DstRMBNT},
    {"setng", Op_DstRMBNT},
    {"setnge", Op_DstRMBNT},
    {"setnl", Op_DstRMBNT},
    {"setnle", Op_DstRMBNT},
    {"setno", Op_DstRMBNT},
    {"setnp", Op_DstRMBNT},
    {"setns", Op_DstRMBNT},
    {"setnz", Op_DstRMBNT},
    {"seto", Op_DstRMBNT},
    {"setp", Op_DstRMBNT},
    {"setpe", Op_DstRMBNT},
    {"setpo", Op_DstRMBNT},
    {"sets", Op_DstRMBNT},
    {"setz", Op_DstRMBNT},
    {"sfence", Op_0},
    {"sgdt", Op_DstMemNT},
    {"sha1msg1", Op_UpdSrcSSE},
    {"sha1msg2", Op_UpdSrcSSE},
    {"sha1nexte", Op_UpdSrcSSE},
    {"sha1rnds4", Op_DstSrcImmS},
    {"sha256msg1", Op_UpdSrcSSE},
    {"sha256msg2", Op_UpdSrcSSE},
    {"sha256rnds2", Op_UpdSrcSSE}, // implicit xmm0
    {"shl", Op_Shift},
    {"shld", Op_UpdSrcShft},
    {"shr", Op_Shift},
    {"shrd", Op_UpdSrcShft},
    {"shufpd", Op_DstSrcImmS},
    {"shufps", Op_DstSrcImmS},
    {"sidt", Op_DstMemNT},
    {"sldt", Op_DstRMWNT},
    {"smsw", Op_DstRMWNT},
    {"sqrtpd", Op_DstSrcSSE},
    {"sqrtps", Op_DstSrcSSE},
    {"sqrtsd", Op_DstSrcSSE},
    {"sqrtss", Op_DstSrcSSE},
    {"stc", Op_Flags},
    {"std", Op_Flags},
    {"sti", Op_Flags},
    {"stmxcsr", Op_DstMemNT},
    {"stos", Op_stos},
    {"stosb", Op_stosX},
    {"stosd", Op_stosX},
#ifdef ASM_X86_64
    {"stosq", Op_stosX},
#endif
    {"stosw", Op_stosX},
    {"str", Op_DstMemNT}, // also r16
    {"sub", Op_UpdSrcF},
    {"subpd", Op_UpdSrcSSE},
    {"subps", Op_UpdSrcSSE},
#ifdef ASM_X86_64
    {"subq", Op_UpdSrcSSE}, // ?
#endif
    {"subsd", Op_UpdSrcSSE},
    {"subss", Op_UpdSrcSSE},
#ifdef ASM_X86_64
    {"swapgs", Op_0}, // Not supported by DMD
#endif
    {"syscall", Op_0},
    {"sysenter", Op_0},
    {"sysexit", Op_0},
    {"sysret", Op_0},
#ifdef ASM_X86_64
    {"sysretq", Op_0}, // ?
#endif
    {"test", Op_SrcSrcF},
    {"ucomisd", Op_SrcSrcSSEF},
    {"ucomiss", Op_SrcSrcSSEF},
    {"ud2", Op_0},
    {"unpckhpd", Op_UpdSrcSSE},
    {"unpckhps", Op_UpdSrcSSE},
    {"unpcklpd", Op_UpdSrcSSE},
    {"unpcklps", Op_UpdSrcSSE},
#if 0 // TODO
    {"vaddpd", XXXX},
    {"vaddps", XXXX},
    {"vaddsd", XXXX},
    {"vaddss", XXXX},
    {"vaddsubpd", XXXX},
    {"vaddsubps", XXXX},
    {"vaesdec", XXXX},
    {"vaesdeclast", XXXX},
    {"vaesenc", XXXX},
    {"vaesenclast", XXXX},
    {"vaesimc", XXXX},
    {"vaeskeygenassist", XXXX},
    {"vandnpd", XXXX},
    {"vandnps", XXXX},
    {"vandpd", XXXX},
    {"vandps", XXXX},
    {"vblendpd", XXXX},
    {"vblendps", XXXX},
    {"vblendvpd", XXXX},
    {"vblendvps", XXXX},
    {"vbroadcastf128", XXXX},
    {"vbroadcastsd", XXXX},
    {"vbroadcastss", XXXX},
    {"vcmppd", XXXX},
    {"vcmpps", XXXX},
    {"vcmpsd", XXXX},
    {"vcmpss", XXXX},
    {"vcomisd", XXXX},
    {"vcomiss", XXXX},
    {"vcvtdq2pd", XXXX},
    {"vcvtdq2ps", XXXX},
    {"vcvtpd2dq", XXXX},
    {"vcvtpd2ps", XXXX},
    {"vcvtph2ps", XXXX},
    {"vcvtps2dq", XXXX},
    {"vcvtps2pd", XXXX},
    {"vcvtps2ph", XXXX},
    {"vcvtsd2si", XXXX},
    {"vcvtsd2ss", XXXX},
    {"vcvtsi2sd", XXXX},
    {"vcvtsi2ss", XXXX},
    {"vcvtss2sd", XXXX},
    {"vcvtss2si", XXXX},
    {"vcvttpd2dq", XXXX},
    {"vcvttps2dq", XXXX},
    {"vcvttsd2si", XXXX},
    {"vcvttss2si", XXXX},
    {"vdivpd", XXXX},
    {"vdivps", XXXX},
    {"vdivsd", XXXX},
    {"vdivss", XXXX},
    {"vdppd", XXXX},
    {"vdpps", XXXX},
#endif
    {"verr", Op_SrcMemNTF},
    {"verw", Op_SrcMemNTF},
#if 0 // TODO
    {"vextractf128", XXXX},
    {"vextractps", XXXX},
    {"vfmadd132pd", XXXX},
    {"vfmadd132ps", XXXX},
    {"vfmadd132sd", XXXX},
    {"vfmadd132ss", XXXX},
    {"vfmadd213pd", XXXX},
    {"vfmadd213ps", XXXX},
    {"vfmadd213sd", XXXX},
    {"vfmadd213ss", XXXX},
    {"vfmadd231pd", XXXX},
    {"vfmadd231ps", XXXX},
    {"vfmadd231sd", XXXX},
    {"vfmadd231ss", XXXX},
    {"vfmaddsub132pd", XXXX},
    {"vfmaddsub132ps", XXXX},
    {"vfmaddsub213pd", XXXX},
    {"vfmaddsub213ps", XXXX},
    {"vfmaddsub231pd", XXXX},
    {"vfmaddsub231ps", XXXX},
    {"vfmsub132pd", XXXX},
    {"vfmsub132ps", XXXX},
    {"vfmsub132sd", XXXX},
    {"vfmsub132ss", XXXX},
    {"vfmsub213pd", XXXX},
    {"vfmsub213ps", XXXX},
    {"vfmsub213sd", XXXX},
    {"vfmsub213ss", XXXX},
    {"vfmsub231pd", XXXX},
    {"vfmsub231ps", XXXX},
    {"vfmsub231sd", XXXX},
    {"vfmsub231ss", XXXX},
    {"vfmsubadd132pd", XXXX},
    {"vfmsubadd132ps", XXXX},
    {"vfmsubadd213pd", XXXX},
    {"vfmsubadd213ps", XXXX},
    {"vfmsubadd231pd", XXXX},
    {"vfmsubadd231ps", XXXX},
    {"vhaddpd", XXXX},
    {"vhaddps", XXXX},
    {"vinsertf128", XXXX},
    {"vinsertps", XXXX},
    {"vlddqu", XXXX},
    {"vldmxcsr", XXXX},
    {"vmaskmovdqu", XXXX},
    {"vmaskmovpd", XXXX},
    {"vmaskmovps", XXXX},
    {"vmaxpd", XXXX},
    {"vmaxps", XXXX},
    {"vmaxsd", XXXX},
    {"vmaxss", XXXX},
    {"vminpd", XXXX},
    {"vminps", XXXX},
    {"vminsd", XXXX},
    {"vminss", XXXX},
    {"vmovapd", XXXX},
    {"vmovaps", XXXX},
    {"vmovd", XXXX},
    {"vmovddup", XXXX},
    {"vmovdqa", XXXX},
    {"vmovdqu", XXXX},
    {"vmovhlps", XXXX},
    {"vmovhpd", XXXX},
    {"vmovhps", XXXX},
    {"vmovlhps", XXXX},
    {"vmovlpd", XXXX},
    {"vmovlps", XXXX},
    {"vmovmskpd", XXXX},
    {"vmovmskps", XXXX},
    {"vmovntdq", XXXX},
    {"vmovntdqa", XXXX},
    {"vmovntpd", XXXX},
    {"vmovntps", XXXX},
    {"vmovq", XXXX},
    {"vmovsd", XXXX},
    {"vmovshdup", XXXX},
    {"vmovsldup", XXXX},
    {"vmovss", XXXX},
    {"vmovupd", XXXX},
    {"vmovups", XXXX},
    {"vmpsadbw", XXXX},
    {"vmulpd", XXXX},
    {"vmulps", XXXX},
    {"vmulsd", XXXX},
    {"vmulss", XXXX},
    {"vorpd", XXXX},
    {"vorps", XXXX},
    {"vpabsb", XXXX},
    {"vpabsd", XXXX},
    {"vpabsw", XXXX},
    {"vpackssdw", XXXX},
    {"vpacksswb", XXXX},
    {"vpackusdw", XXXX},
    {"vpackuswb", XXXX},
    {"vpaddb", XXXX},
    {"vpaddd", XXXX},
    {"vpaddq", XXXX},
    {"vpaddsb", XXXX},
    {"vpaddsw", XXXX},
    {"vpaddusb", XXXX},
    {"vpaddusw", XXXX},
    {"vpaddw", XXXX},
    {"vpalignr", XXXX},
    {"vpand", XXXX},
    {"vpandn", XXXX},
    {"vpavgb", XXXX},
    {"vpavgw", XXXX},
    {"vpblendvb", XXXX},
    {"vpblendw", XXXX},
    {"vpclmulqdq", XXXX},
    {"vpcmpeqb", XXXX},
    {"vpcmpeqd", XXXX},
    {"vpcmpeqq", XXXX},
    {"vpcmpeqw", XXXX},
    {"vpcmpestri", XXXX},
    {"vpcmpestrm", XXXX},
    {"vpcmpgtb", XXXX},
    {"vpcmpgtd", XXXX},
    {"vpcmpgtq", XXXX},
    {"vpcmpgtw", XXXX},
    {"vpcmpistri", XXXX},
    {"vpcmpistrm", XXXX},
    {"vperm2f128", XXXX},
    {"vpermilpd", XXXX},
    {"vpermilps", XXXX},
    {"vpextrb", XXXX},
    {"vpextrd", XXXX},
    {"vpextrq", XXXX},
    {"vpextrw", XXXX},
    {"vphaddd", XXXX},
    {"vphaddsw", XXXX},
    {"vphaddw", XXXX},
    {"vphminposuw", XXXX},
    {"vphsubd", XXXX},
    {"vphsubsw", XXXX},
    {"vphsubw", XXXX},
    {"vpinsrb", XXXX},
    {"vpinsrd", XXXX},
    {"vpinsrq", XXXX},
    {"vpinsrw", XXXX},
    {"vpmaddubsw", XXXX},
    {"vpmaddwd", XXXX},
    {"vpmaxsb", XXXX},
    {"vpmaxsd", XXXX},
    {"vpmaxsw", XXXX},
    {"vpmaxub", XXXX},
    {"vpmaxud", XXXX},
    {"vpmaxuw", XXXX},
    {"vpminsb", XXXX},
    {"vpminsd", XXXX},
    {"vpminsw", XXXX},
    {"vpminub", XXXX},
    {"vpminud", XXXX},
    {"vpminuw", XXXX},
    {"vpmovmskb", XXXX},
    {"vpmovsxbd", XXXX},
    {"vpmovsxbq", XXXX},
    {"vpmovsxbw", XXXX},
    {"vpmovsxdq", XXXX},
    {"vpmovsxwd", XXXX},
    {"vpmovsxwq", XXXX},
    {"vpmovzxbd", XXXX},
    {"vpmovzxbq", XXXX},
    {"vpmovzxbw", XXXX},
    {"vpmovzxdq", XXXX},
    {"vpmovzxwd", XXXX},
    {"vpmovzxwq", XXXX},
    {"vpmuldq", XXXX},
    {"vpmulhrsw", XXXX},
    {"vpmulhuw", XXXX},
    {"vpmulhw", XXXX},
    {"vpmulld", XXXX},
    {"vpmullw", XXXX},
    {"vpmuludq", XXXX},
    {"vpor", XXXX},
    {"vpsadbw", XXXX},
    {"vpshufb", XXXX},
    {"vpshufd", XXXX},
    {"vpshufhw", XXXX},
    {"vpshuflw", XXXX},
    {"vpsignb", XXXX},
    {"vpsignd", XXXX},
    {"vpsignw", XXXX},
    {"vpslld", XXXX},
    {"vpslldq", XXXX},
    {"vpsllq", XXXX},
    {"vpsllw", XXXX},
    {"vpsrad", XXXX},
    {"vpsraw", XXXX},
    {"vpsrld", XXXX},
    {"vpsrldq", XXXX},
    {"vpsrlq", XXXX},
    {"vpsrlw", XXXX},
    {"vpsubb", XXXX},
    {"vpsubd", XXXX},
    {"vpsubq", XXXX},
    {"vpsubsb", XXXX},
    {"vpsubsw", XXXX},
    {"vpsubusb", XXXX},
    {"vpsubusw", XXXX},
    {"vpsubw", XXXX},
    {"vptest", XXXX},
    {"vpunpckhbw", XXXX},
    {"vpunpckhdq", XXXX},
    {"vpunpckhqdq", XXXX},
    {"vpunpckhwd", XXXX},
    {"vpunpcklbw", XXXX},
    {"vpunpckldq", XXXX},
    {"vpunpcklqdq", XXXX},
    {"vpunpcklwd", XXXX},
    {"vpxor", XXXX},
    {"vrcpps", XXXX},
    {"vrcpss", XXXX},
    {"vroundpd", XXXX},
    {"vroundps", XXXX},
    {"vroundsd", XXXX},
    {"vroundss", XXXX},
    {"vshufpd", XXXX},
    {"vshufps", XXXX},
    {"vsqrtpd", XXXX},
    {"vsqrtps", XXXX},
    {"vsqrtsd", XXXX},
    {"vsqrtss", XXXX},
    {"vstmxcsr", XXXX},
    {"vsubpd", XXXX},
    {"vsubps", XXXX},
    {"vsubsd", XXXX},
    {"vsubss", XXXX},
    {"vucomisd", XXXX},
    {"vucomiss", XXXX},
    {"vunpckhpd", XXXX},
    {"vunpckhps", XXXX},
    {"vunpcklpd", XXXX},
    {"vunpcklps", XXXX},
    {"vxorpd", XXXX},
    {"vxorps", XXXX},
    {"vzeroall", XXXX},
    {"vzeroupper", XXXX},
#endif
#ifndef ASM_X86_64
    {"wait", Op_0},
#endif
    {"wbinvd", Op_0},
    //{"wrfsbase", XXXX},
    //{"wrgsbase", XXXX},
    {"wrmsr", Op_0},
    {"xadd", Op_UpdUpdF},
    {"xchg", Op_UpdUpd},
    {"xgetbv", Op_xgetbv},
    {"xlat", Op_xlat},
    {"xlatb", Op_0_AX},
    {"xor", Op_DstSrcF},
    {"xorpd", Op_UpdSrcSSE},
    {"xorps", Op_UpdSrcSSE},
#ifdef ASM_X86_64
    {"xorq", Op_DstSrcNT}, //?
#endif
    //{"xrstor", XXXX},
    //{"xrstor64", XXXX},
    //{"xsave", XXXX},
    //{"xsave64", XXXX},
    //{"xsaveopt", XXXX},
    //{"xsaveopt64", XXXX},
    //{"xsetbv", XXXX},
};

typedef enum {
  Default_Ptr = 0,
  Byte_Ptr = 1,
  Short_Ptr = 2,
  Int_Ptr = 4,
  QWord_Ptr = 8,
  Float_Ptr = 4,
  Double_Ptr = 8,
  Extended_Ptr = 10,
  Near_Ptr = 98,
  Far_Ptr = 99,
  N_PtrTypes
} PtrType;

static const int N_PtrNames = 8;
static const char *ptrTypeNameTable[N_PtrNames] = {
    "word", "dword", "qword", "float", "double", "extended", "near", "far"};

static Identifier *ptrTypeIdentTable[N_PtrNames];
static PtrType ptrTypeValueTable[N_PtrNames] = {
    Short_Ptr,  Int_Ptr,      QWord_Ptr, Float_Ptr,
    Double_Ptr, Extended_Ptr, Near_Ptr,  Far_Ptr};

typedef enum { Opr_Invalid, Opr_Immediate, Opr_Reg, Opr_Mem } OperandClass;

/* kill inlining if we reference a local? */

/* DMD seems to allow only one 'symbol' per operand .. include __LOCAL_SIZE */

/* DMD offset usage: <parm>[<reg>] seems to always be relative to EBP+8 .. even
   if naked.. */

// mov eax, 4
// mov eax, fs:4
// -- have to assume we know whether or not to use '$'

static Token eof_tok;
static Expression *Handled;
static Identifier *ident_seg;

struct AsmProcessor {
  struct Operand {
    int inBracket;
    int hasBracket;
    int hasNumber;
    int isOffset;

    Reg segmentPrefix;
    Reg reg;
    sinteger_t constDisplacement; // use to build up.. should be int constant in
                                  // the end..
    Expressions symbolDisplacement; // array of expressions or..
    Reg baseReg;
    Reg indexReg;
    int scale;

    OperandClass cls;
    PtrType dataSize;
    PtrType dataSizeHint; // DMD can use the type of a referenced variable
  };

  static const unsigned Max_Operands = 3;

  InlineAsmStatement *stmt;
  Scope *sc;

  Token *token;
  std::ostringstream insnTemplate;

  AsmOp op;
  AsmOpInfo *opInfo;
  Operand operands[Max_Operands];
  Identifier *opIdent;
  Operand *operand;

  AsmProcessor(Scope *sc, InlineAsmStatement *stmt) {
    this->sc = sc;
    this->stmt = stmt;
    token = stmt->tokens;

    opInfo = nullptr;

    if (!regInfo[0].ident) {
      char buf[8], *p;

      for (int i = 0; i < N_Regs; i++) {
        strncpy(buf, regInfo[i].name.data(), sizeof(buf) - 1);
        for (p = buf; *p; p++) {
          *p = std::tolower(*p);
        }
        regInfo[i].gccName = std::string(buf, p - buf);
        if ((i <= Reg_ST || i > Reg_ST7) && i != Reg_EFLAGS) {
          regInfo[i].ident =
              Identifier::idPool(regInfo[i].name.data(),
                                 static_cast<unsigned>(regInfo[i].name.size()));
        }
      }

      for (int i = 0; i < N_PtrNames; i++) {
        ptrTypeIdentTable[i] = Identifier::idPool(ptrTypeNameTable[i]);
      }

      Handled = createExpression(Loc(), EXP::void_);

      ident_seg = Identifier::idPool("seg");

      eof_tok.value = TOK::endOfFile;
      eof_tok.next = nullptr;
    }
  }

  void run() { parse(); }

  void nextToken() {
    if (token->next) {
      token = token->next;
    } else {
      token = &eof_tok;
    }
  }

  Token *peekToken() {
    if (token->next) {
      return token->next;
    }
    return &eof_tok;
  }

  void expectEnd() {
    if (token->value != TOK::endOfFile) {
      error(stmt->loc, "expected end of statement"); // %% extra at end...
    }
  }

  void parse() {
    op = parseOpcode();

    switch (op) {
    case Op_Align:
      doAlign();
      expectEnd();
      break;
    case Op_Even:
      doEven();
      expectEnd();
      break;
    case Op_Naked:
      doNaked();
      expectEnd();
      break;
    case Op_Invalid:
      break;
    default:
      if (op >= Op_db && op <= Op_de) {
        doData();
      } else {
        doInstruction();
      }
    }
  }

  AsmOp parseOpcode() {
    static const int N_ents = sizeof(opData) / sizeof(AsmOpEnt);

    switch (token->value) {
    case TOK::align_:
      nextToken();
      return Op_Align;
    case TOK::in_:
      nextToken();
      opIdent = Id::___in;
      return Op_in;
    case TOK::int32: // "int"
      nextToken();
      opIdent = Id::__int;
      return Op_SrcImm;
    case TOK::out_:
      nextToken();
      opIdent = Id::___out;
      return Op_out;
    case TOK::identifier:
      // search for mnemonic below
      break;
    default:
      error(stmt->loc, "expected opcode");
      return Op_Invalid;
    }

    opIdent = token->ident;
    const char *opcode = token->ident->toChars();

    nextToken();

    // %% okay to use bsearch?
    int i = 0, j = N_ents, k, l;
    do {
      k = (i + j) / 2;
      l = strcmp(opcode, opData[k].inMnemonic);
      if (!l) {
        return opData[k].asmOp;
      }
      if (l < 0) {
        j = k;
      } else {
        i = k + 1;
      }
    } while (i != j);

    error(stmt->loc, "unknown opcode `%s`", opcode);

    return Op_Invalid;
  }

  // need clobber information.. use information is good too...
  void doInstruction() {
    unsigned operand_i = 0;

    opInfo = &asmOpInfo[op];
    memset(operands, 0, sizeof(operands));

    if (token->value == TOK::endOfFile && op == Op_FMath0) {
// no iteration for x86 vs. single iteration for x64
#ifndef ASM_X86_64
      while (false)
#else
      for (operand_i = 0; operand_i < 1; operand_i++)
#endif
      {
        operand = &operands[operand_i];
        operand->reg = operand->baseReg = operand->indexReg =
            operand->segmentPrefix = Reg_Invalid;
        operand->cls = Opr_Reg;
        if (operand_i == 0) {
          operand->reg = Reg_ST;
        } else {
          operand->reg = Reg_ST1;
        }
        operand->hasNumber = 0;
        operand->constDisplacement = 0;
        parseOperand();

        if (matchOperands(operand_i)) {
          auto asmcode = new AsmCode(N_Regs);

          if (formatInstruction(operand_i, asmcode)) {
            stmt->asmcode = asmcode;
          }
        }
      }
      return;
    }

    while (token->value != TOK::endOfFile) {
      if (operand_i < Max_Operands) {
        operand = &operands[operand_i];
        operand->reg = operand->baseReg = operand->indexReg =
            operand->segmentPrefix = Reg_Invalid;
        parseOperand();
        operand_i++;
      } else {
        error(stmt->loc, "too many operands for instruction");
        break;
      }

      if (token->value == TOK::comma) {
        nextToken();
      } else if (token->value != TOK::endOfFile) {
        error(stmt->loc, "end of instruction expected, not `%s`", token->toChars());
        return;
      }
    }
    /*
    if (operand_i < opInfo->minOperands) {
      error(stmt->loc, "too few operands for instruction");
    }
    */

    if (matchOperands(operand_i)) {
      auto asmcode = new AsmCode(N_Regs);

      if (formatInstruction(operand_i, asmcode)) {
        stmt->asmcode = asmcode;
      }
    }
  }

  void setAsmCode() {
    auto asmcode = new AsmCode(N_Regs);
    asmcode->insnTemplate = insnTemplate.str();
    Logger::cout() << "insnTemplate = " << asmcode->insnTemplate << '\n';
    stmt->asmcode = asmcode;
  }

  // note: doesn't update AsmOp op
  bool matchOperands(unsigned nOperands) {
    bool wrong_number = true;

    for (unsigned i = 0; i < nOperands; i++) {
      classifyOperand(&operands[i]);
    }

    while (1) {
      if (nOperands == opInfo->nOperands()) {
        wrong_number = false;
        /*  Cases in which number of operands is not
            enough for a match: Op_FCmp/Op_FCmp1,
            Op_FCmpP/Op_FCmpP1 */
        for (unsigned i = 0; i < nOperands; i++) {
          Operand *operand = &operands[i];

          switch (opInfo->operands[i] & Opr_ClassMask) {
          case OprC_Mem: // no FPMem currently
            if (operand->cls != Opr_Mem) {
              goto no_match;
            }
            break;
          case OprC_RFP:
            if (!(operand->reg >= Reg_ST && operand->reg <= Reg_ST7)) {
              goto no_match;
            }
            break;
          default:
            break;
          }
        }

        return true;
      }
    no_match:
      if (opInfo->linkType == Next_Form) {
        opInfo = &asmOpInfo[op = static_cast<AsmOp>(opInfo->link)];
      } else {
        break;
      }
    }
    if (wrong_number) {
      error(stmt->loc, "wrong number of operands");
    } else {
      error(stmt->loc, "wrong operand types");
    }
    return false;
  }

  // OSX and 32-bit Windows need an extra leading underscore when mangling a
  // symbol name.
  static bool prependExtraUnderscore(LINK link) {
    const auto &triple = *global.params.targetTriple;
    return triple.isOSDarwin() ||
           // Win32: all symbols except for MSVC++ ones
           (triple.isOSWindows() && triple.isArch32Bit() && link != LINK::cpp);
  }

  void addOperand(const char *fmt, AsmArgType type, Expression *e,
                  AsmCode *asmcode, AsmArgMode mode = Mode_Input) {
    using namespace dmd;

    if (sc->func->isNaked()) {
      switch (type) {
      case Arg_Integer:
        if (e->type->isunsigned()) {
          insnTemplate << "$" << e->toUInteger();
        } else {
#ifndef ASM_X86_64
          insnTemplate << "$" << static_cast<sinteger_t>(e->toInteger());
#else
          insnTemplate << "$" << e->toInteger();
#endif
        }
        break;

      case Arg_Pointer:
        error(stmt->loc, "unsupported pointer reference to `%s` in naked asm",
                    e->toChars());
        break;

      case Arg_Memory:
        // Peel off one layer of explicitly taking the address, if present.
        if (auto ae = e->isAddrExp()) {
          e = ae->e1;
        }

        if (auto v = e->isVarExp()) {
          if (VarDeclaration *vd = v->var->isVarDeclaration()) {
            if (!vd->isDataseg()) {
              error(stmt->loc, "only global variables can be referenced by "
                          "identifier in naked asm");
              break;
            }

            // print out the mangle
            if (prependExtraUnderscore(vd->resolvedLinkage())) {
              insnTemplate << "_";
            }
            OutBuffer buf;
            mangleToBuffer(vd, buf);
            insnTemplate << buf.peekChars();
            getIrGlobal(vd, true)->nakedUse = true;
            break;
          }
        }
        error(stmt->loc, "unsupported memory reference to `%s` in naked asm",
                    e->toChars());
        break;

      default:
        llvm_unreachable("Unsupported argument in asm.");
        break;
      }
    } else {
      insnTemplate << fmt << "<<" << (mode == Mode_Input ? "in" : "out")
                   << asmcode->args.size() << ">>";
      asmcode->args.push_back(AsmArg(type, e, mode));
    }
  }
  void addOperand2(const char *fmtpre, const char *fmtpost, AsmArgType type,
                   Expression *e, AsmCode *asmcode,
                   AsmArgMode mode = Mode_Input) {
    if (sc->func->isNaked()) {
      // taken from above
      error(stmt->loc, "only global variables can be referenced by identifier in "
                  "naked asm");
      return;
    }

    insnTemplate << fmtpre << "<<" << (mode == Mode_Input ? "in" : "out")
                 << ">>" << fmtpost;
    asmcode->args.push_back(AsmArg(type, e, mode));
  }

  void addLabel(const char *id) {
    // We need to delay emitting the actual function name, see
    // replace_func_name in asmstmt.cpp for details.
    printLabelName(insnTemplate, "<<func>>", id);
  }

  /* Determines whether the operand is a register, memory reference
     or immediate.  Immediate addresses are currently classified as
     memory.  This function is called before the exact instructions
     is known and thus, should not use opInfo. */
  void classifyOperand(Operand *operand) {
    operand->cls = classifyOperand1(operand);
  }

  OperandClass classifyOperand1(Operand *operand) {
    bool is_localsize = false;
    bool really_have_symbol = false;

    if (operand->symbolDisplacement.length) {
      is_localsize = isLocalSize(operand->symbolDisplacement[0]);
      really_have_symbol = !is_localsize;
    }

    if (operand->isOffset && !operand->hasBracket) {
      return Opr_Immediate;
    }

    if (operand->hasBracket ||
        really_have_symbol) // %% redo for 'offset' function
    {
      if (operand->reg != Reg_Invalid) {
        invalidExpression();
        return Opr_Invalid;
      }

      return Opr_Mem;
    }

    if (operand->reg != Reg_Invalid && operand->constDisplacement != 0) {
      invalidExpression();
      return Opr_Invalid;
    }

    if (operand->segmentPrefix != Reg_Invalid) {
      if (operand->reg != Reg_Invalid) {
        invalidExpression();
        return Opr_Invalid;
      }

      return Opr_Mem;
    }

    if (operand->reg != Reg_Invalid && !operand->hasNumber) {
      return Opr_Reg;
    }

    // should check immediate given (operand->hasNumber);
    //
    if (operand->hasNumber || is_localsize) {
      // size determination not correct if there are symbols Opr_Immediate
      if (operand->dataSize == Default_Ptr) {
        if (operand->constDisplacement < 0x100) {
          operand->dataSize = Byte_Ptr;
        } else if (operand->constDisplacement < 0x10000) {
          operand->dataSize = Short_Ptr;
        }
#ifndef ASM_X86_64
        else {
          operand->dataSize = Int_Ptr;
        }
#else
        else if (operand->constDisplacement <= 0xFFFFFFFF) {
          operand->dataSize = Int_Ptr;
        } else {
          // This could be possible since we are using 48 bits
          operand->dataSize = QWord_Ptr;
        }
#endif
      }
      return Opr_Immediate;
    }

    // probably a bug,?
    error(stmt->loc, "invalid operand");
    return Opr_Invalid;
  }

  void writeReg(Reg reg) { insnTemplate << "%" << regInfo[reg].gccName; }

  bool opTakesLabel() {
    switch (op) {
    case Op_Branch:
    case Op_CBranch:
    case Op_Loop:
      return true;
    default:
      return false;
    }
  }

  bool getTypeSuffix(TypeNeeded needed, PtrType ptrtype,
                     std::string &type_suffix) {
    switch (needed) {
    case Byte_NoType:
      return ptrtype == Byte_Ptr;
    case Word_Types:
      if (ptrtype == Byte_Ptr) {
        return false;
      }
    // fallthrough
    case Int_Types:
      switch (ptrtype) {
      case Byte_Ptr:
        type_suffix = 'b';
        break;
      case Short_Ptr:
        type_suffix = 'w';
        break;
      case Int_Ptr:
        type_suffix = 'l';
        break;
#ifdef ASM_X86_64
      case QWord_Ptr:
        type_suffix = 'q';
        break;
#endif
      default:
        // %% these may be too strict
        return false;
      }
      break;
    case FPInt_Types:
      switch (ptrtype) {
      case Short_Ptr:
        type_suffix = 's';
        break;
      case Int_Ptr:
        type_suffix = 'l';
        break;
      case QWord_Ptr:
        type_suffix = "ll";
        break;
      default:
        return false;
      }
      break;
    case FP_Types:
      switch (ptrtype) {
      case Float_Ptr:
        type_suffix = 's';
        break;
      case Double_Ptr:
        type_suffix = 'l';
        break;
      case Extended_Ptr:
        type_suffix = 't';
        break;
      default:
        return false;
      }
      break;
    default:
      return false;
    }
    return true;
  }

  // also set impl clobbers
  bool formatInstruction(int nOperands, AsmCode *asmcode) {
    using namespace dmd;

    const char *fmt;
    const char *mnemonic;
    std::string type_suffix;
    bool use_star;
    AsmArgMode mode;

    insnTemplate.str("");
    // %% todo: special case for something..
    if (opInfo->linkType == Out_Mnemonic) {
      mnemonic = alternateMnemonics[opInfo->link];
    } else {
      mnemonic = opIdent->toChars();
    }

    // handle two-operand form where second arg is ignored.
    // must be done before type_suffix detection
    if (op == Op_FidR_P || op == Op_fxch || op == Op_FfdRR_P) {
      if (operands[1].cls == Opr_Reg && operands[1].reg == Reg_ST) {
        nOperands = 1;
      } else {
        error(stmt->loc, "instruction allows only `ST` as second argument");
      }
    }

#ifdef ASM_X86_64
    if (op == Op_FCmpFlgP) {
      // Explicitly add %st as second argument to fucomip – it should
      // be implicit, but the GNU as shipping with Mac OS X (Apple Inc
      // version cctools-800~26, GNU assembler version 1.38) chokes on
      // it otherwise.
      assert(nOperands == 1);
      nOperands = 2;
      operands[1] = operands[0];
      memset(operands, 0, sizeof(Operand));
      operands[0].cls = Opr_Reg;
      operands[0].reg = Reg_ST;
    }
#endif

    if (opInfo->needsType) {
      PtrType exact_type = Default_Ptr;
      PtrType min_type = Default_Ptr;
      PtrType hint_type = Default_Ptr;

      /* Default types: This attempts to match the observed behavior of DMD
       */
      switch (opInfo->needsType) {
      case Int_Types:
        min_type = Byte_Ptr;
        break;
      case Word_Types:
        min_type = Short_Ptr;
        break;
      case FPInt_Types:
        if (op == Op_Fis_ST) { // integer math instructions
          min_type = Int_Ptr;
        } else { // compare, load, store
          min_type = Short_Ptr;
        }
        break;
      case FP_Types:
        min_type = Float_Ptr;
        break;
      }
      if (op == Op_push && operands[0].cls == Opr_Immediate) {
#ifndef ASM_X86_64
        min_type = Int_Ptr;
#else
        min_type = QWord_Ptr;
#endif
      }

      for (int i = 0; i < nOperands; i++) {
        if (hint_type == Default_Ptr && !(opInfo->operands[i] & Opr_NoType)) {
          hint_type = operands[i].dataSizeHint;
        }

        if ((opInfo->operands[i] & Opr_NoType) ||
            operands[i].dataSize == Default_Ptr) {
          continue;
        }
        if (operands[i].cls == Opr_Immediate) {
          min_type =
              operands[i].dataSize > min_type ? operands[i].dataSize : min_type;
        } else {
          exact_type =
              operands[i].dataSize; // could check for conflicting types
          break;
        }
      }

      bool type_ok;
      if (exact_type == Default_Ptr) {
        type_ok = getTypeSuffix(static_cast<TypeNeeded>(opInfo->needsType),
                                hint_type, type_suffix);
        if (!type_ok) {
          type_ok = getTypeSuffix(static_cast<TypeNeeded>(opInfo->needsType),
                                  min_type, type_suffix);
        }
      } else {
        type_ok = getTypeSuffix(static_cast<TypeNeeded>(opInfo->needsType),
                                exact_type, type_suffix);
      }

      if (!type_ok) {
        error(stmt->loc, "invalid operand size");
        return false;
      }
    } else if (op == Op_Branch) {
      if (operands[0].dataSize == Far_Ptr) { // %% type=Far_Ptr not set by
        // Seg:Ofss OTOH, we don't support
        // that..
        insnTemplate << 'l';
      }
    } else if (op == Op_FMath0 || op == Op_FdST0ST1) {
      operands[0].cls = Opr_Reg;
      operands[0].reg = Reg_ST1;
      operands[1].cls = Opr_Reg;
      operands[1].reg = Reg_ST;
      nOperands = 2;
    }

    switch (op) {
    case Op_SizedStack: {
      int mlen = strlen(mnemonic);
      if (mnemonic[mlen - 1] == 'd') {
        insnTemplate.write(mnemonic, mlen - 1);
      } else {
        insnTemplate << mnemonic << 'w';
      }
    } break;
    case Op_cmpsd:
    case Op_insX:
    case Op_lodsX:
    case Op_movsd:
    case Op_outsX:
    case Op_scasX:
    case Op_stosX: {
      int mlen = strlen(mnemonic);
      if (mnemonic[mlen - 1] == 'd') {
        insnTemplate.write(mnemonic, mlen - 1) << 'l';
      } else {
        insnTemplate << mnemonic;
      }
    } break;
    case Op_movsx:
    case Op_movzx: {
      char tc_1;
      int mlen = strlen(mnemonic);
      PtrType op1_size = operands[1].dataSize;
      if (op1_size == Default_Ptr) {
        op1_size = operands[1].dataSizeHint;
      }
      // Need type char for source arg
      switch (op1_size) {
      case Byte_Ptr:
      case Default_Ptr:
        tc_1 = 'b';
        break;
      case Short_Ptr:
        tc_1 = 'w';
        break;
      default:
        error(stmt->loc, "invalid operand size/type");
        return false;
      }
      assert(!type_suffix.empty());
      insnTemplate.write(mnemonic, mlen - 1) << tc_1 << type_suffix;
    } break;

    default:
      // special case fdiv, fsub: see dmd 840, ldc 256
      if ((strncmp(mnemonic, "fsub", 4) == 0 ||
           strncmp(mnemonic, "fdiv", 4) == 0) &&
          operands[0].reg != Reg_ST && op != Op_FMath) {
        // replace:
        //   f{sub,div}r{p,} <-> f{sub,div}{p,}
        if (mnemonic[4] == 'r') {
          insnTemplate.write(mnemonic, 4);
          insnTemplate.write(mnemonic + 5, strlen(mnemonic) - 5);
        } else {
          insnTemplate.write(mnemonic, 4) << "r";
          insnTemplate.write(mnemonic + 4, strlen(mnemonic) - 4);
        }
      } else {
        insnTemplate << mnemonic;
      }
      // the no-operand versions of floating point ops always pop
      if (op == Op_FMath0) {
        insnTemplate << "p";
      }
      insnTemplate << type_suffix;
      break;
    }

    switch (opInfo->implicitClobbers & Clb_DXAX_Mask) {
    case Clb_SizeAX:
    case Clb_EAX:
      asmcode->regs[Reg_EAX] = true;
      break;
    case Clb_SizeDXAX:
      asmcode->regs[Reg_EAX] = true;
      if (type_suffix != "b") {
        asmcode->regs[Reg_EDX] = true;
      }
      break;
#ifdef ASM_X86_64
    case Clb_SizeRDXRAX:
      asmcode->regs[Reg_RAX] = true;
      asmcode->regs[Reg_RDX] = true;
      break;
#endif
    default:
      // nothing
      break;
    }

    if (opInfo->implicitClobbers & Clb_DI) {
      asmcode->regs[Reg_EDI] = true;
    }
    if (opInfo->implicitClobbers & Clb_SI) {
      asmcode->regs[Reg_ESI] = true;
    }
    if (opInfo->implicitClobbers & Clb_CX) {
      asmcode->regs[Reg_ECX] = true;
    }
    if (opInfo->implicitClobbers & Clb_SP) {
      asmcode->regs[Reg_ESP] = true;
    }
    if (opInfo->implicitClobbers & Clb_ST) {
      /* Can't figure out how to tell GCC that an
         asm statement leaves an arg pushed on the stack.
         Maybe if the statment had and input or output
         operand it would work...  In any case, clobbering
         all FP prevents incorrect code generation. */
      asmcode->regs[Reg_ST] = true;
      asmcode->regs[Reg_ST1] = true;
      asmcode->regs[Reg_ST2] = true;
      asmcode->regs[Reg_ST3] = true;
      asmcode->regs[Reg_ST4] = true;
      asmcode->regs[Reg_ST5] = true;
      asmcode->regs[Reg_ST6] = true;
      asmcode->regs[Reg_ST7] = true;
    }
    if (opInfo->implicitClobbers & Clb_Flags) {
      asmcode->regs[Reg_EFLAGS] = true;
    }
    if (op == Op_cpuid) {
      asmcode->regs[Reg_EAX] = true;
      asmcode->regs[Reg_EBX] = true;
      asmcode->regs[Reg_ECX] = true;
      asmcode->regs[Reg_EDX] = true;
    }

    insnTemplate << ' ';
    for (int i__ = 0; i__ < nOperands; i__++) {
      int i;
      if (i__ != 0) {
        insnTemplate << ", ";
      }
      fmt = "$";

      switch (op) {
      case Op_mul:
        // gas won't accept the two-operand form; skip to the source
        // operand
        i__ = 1;
      // fallthrough
      case Op_bound:
      case Op_enter:
        i = i__;
        break;
      default:
        i = nOperands - 1 - i__; // operand = & operands[ nOperands - 1 - i ];
        break;
      }
      operand = &operands[i];

      // This is used only in the case of `operand->cls` being `Opr_Mem`.
      bool hasConstDisplacement = false;

      switch (operand->cls) {
      case Opr_Immediate:
        // for implementing offset:
        // $var + $7 // fails
        // $var + 7  // ok
        // $7 + $var // ok

        // DMD doesn't seem to allow this
        /*
        if ( opTakesLabel() )  tho... (near ptr <Number> would be abs?)
            fmt = "$a"; // GAS won't accept "jmp $42"; must be "jmp 42"
        (rel) or
        "jmp *42" (abs)
        */
        if (opTakesLabel()) {
          // "relative addressing not allowed in branch instructions" ..
          error(stmt->loc, "integer constant not allowed in branch instructions");
          return false;
        }

        if (operand->symbolDisplacement.length &&
            isLocalSize(operand->symbolDisplacement[0])) {
          // handle __LOCAL_SIZE, which in this constant, is an immediate
          // should do this in slotexp..
          addOperand("$", Arg_LocalSize, operand->symbolDisplacement[0],
                     asmcode);
          if (operand->constDisplacement) {
            insnTemplate << '+';
          } else {
            break;
          }
        }

        if (operand->symbolDisplacement.length) {
          fmt = "$a";
          addOperand("$", Arg_Pointer, operand->symbolDisplacement[0], asmcode);

          if (operand->constDisplacement) {
            insnTemplate << '+';
          } else {
            // skip the addOperand(fmt, Arg_Integer...) below
            break;
          }
        }
        addOperand(fmt, Arg_Integer, newIntExp(operand->constDisplacement),
                   asmcode);
        break;
      case Opr_Reg:
        if (opInfo->operands[i] & Opr_Dest) {
          Reg clbr_reg = static_cast<Reg>(regInfo[operand->reg].baseReg);
          if (clbr_reg != Reg_Invalid) {
            asmcode->regs[clbr_reg] = true;
          }
        }
        if (opTakesLabel()) {
          insnTemplate << '*';
        }
        writeReg(operand->reg);
        /*
        insnTemplate << "%";
        insnTemplate << regInfo[operand->reg].name;
        */
        break;
      case Opr_Mem:
        // better: use output operands for simple variable references
        if ((opInfo->operands[i] & Opr_Update) == Opr_Update) {
          mode = Mode_Update;
        } else if (opInfo->operands[i] & Opr_Dest) {
          mode = Mode_Output;
        } else {
          mode = Mode_Input;
        }

        use_star = opTakesLabel();

        if (Logger::enabled()) {
          Logger::cout() << "Opr_Mem\n";
          LOG_SCOPE
          Logger::cout() << "baseReg: " << operand->baseReg << '\n';
          Logger::cout() << "segmentPrefix: " << operand->segmentPrefix << '\n';
          Logger::cout() << "constDisplacement: " << operand->constDisplacement
                         << '\n';
          for (unsigned i = 0; i < operand->symbolDisplacement.length; i++) {
            Expression *expr = operand->symbolDisplacement[i];
            Logger::cout() << "symbolDisplacement[" << i
                           << "] = " << expr->toChars() << '\n';
          }
        }
        if (operand->segmentPrefix != Reg_Invalid) {
          if (op != Op_Branch) {
            writeReg(operand->segmentPrefix);
            insnTemplate << ':';
          } else {
            error(stmt->loc, "Cannot generate a segment prefix for a "
                        "branching instruction");
          }
        }

        // We make note of this for later on, as if a const-displacement is in play we
        // may need to change the asm template to avoid it expanding to an invalid syntax.
        hasConstDisplacement = (operand->segmentPrefix != Reg_Invalid &&
                               operand->symbolDisplacement.length == 0) ||
                               operand->constDisplacement;
        if (hasConstDisplacement) {
          insnTemplate << operand->constDisplacement;
          if (operand->symbolDisplacement.length) {
            insnTemplate << '+';
          }
          operand->constDisplacement = 0;
          // addOperand(fmt, Arg_Integer,
          // newIntExp(operand->constDisplacement),
          // asmcode);
          if (opInfo->operands[i] & Opr_Dest) {
            asmcode->clobbersMemory = 1;
          }
        }
        if (operand->symbolDisplacement.length) {
          Expression *e = operand->symbolDisplacement[0];
          Declaration *decl = nullptr;

          if (auto ve = e->isVarExp()) {
            decl = ve->var;
          }

          if (operand->baseReg != Reg_Invalid && decl && !decl->isDataseg()) {

            // Use the offset from frame pointer

            /* GCC doesn't give the front end access to stack offsets
               when optimization is turned on (3.x) or at all (4.x).

               Try to convert var[EBP] (or var[ESP] for naked funcs) to
               a memory expression that does not require us to know
               the stack offset.
            */

            if (operand->indexReg == Reg_Invalid && decl->isVarDeclaration() &&
#ifndef ASM_X86_64
                ((operand->baseReg == Reg_EBP && !sc->func->isNaked()) ||
                 (operand->baseReg == Reg_ESP && sc->func->isNaked())))
#else
                (((operand->baseReg == Reg_EBP ||
                   operand->baseReg == Reg_RBP) &&
                  !sc->func->isNaked()) ||
                 ((operand->baseReg == Reg_ESP ||
                   operand->baseReg == Reg_RSP) &&
                  sc->func->isNaked())))
#endif
            {

              e = createAddrExp(Loc(), e);
              e->type = pointerTo(decl->type);

              operand->constDisplacement = 0;
              operand->baseReg = Reg_Invalid;

              addOperand(fmt, Arg_Memory, e, asmcode, mode);

            } else {
              // FIXME: what is this ?
              addOperand2("${", ":a}", Arg_FrameRelative, e, asmcode);
            }
            if (opInfo->operands[i] & Opr_Dest) {
              asmcode->clobbersMemory = 1;
            }
          } else {
            // Plain memory reference to variable or reference to label.

            /* If in a reg, DMD moves to memory.. even with -O, so we'll
               do the
               same
               by always using the "m" contraint.

               In order to get the correct output for function and label
               symbols,
               the %an format must be used with the "p" constraint.
            */
            if (isDollar(e)) {
              error(stmt->loc, "dollar labels are not supported");
              asmcode->dollarLabel = 1;
            } else if (auto dse = e->isDsymbolExp()) {
              LabelDsymbol *lbl = dse->s->isLabel();
              stmt->isBranchToLabel = lbl;

              use_star = false;
              addLabel(lbl->ident->toChars());
            } else if ((decl && decl->isCodeseg())) // if function or label
            {
              use_star = false;
              // simply write out the mangle
              if (prependExtraUnderscore(decl->resolvedLinkage())) {
                insnTemplate << "_";
              }
              OutBuffer buf;
              mangleToBuffer(decl, buf);
              insnTemplate << buf.peekChars();
              //              addOperand2("${", ":c}", Arg_Pointer, e,
              //              asmcode);
            } else {
              // We don't know ahead of time what kind of memory operand will be generated for
              // our template: it could be a symbolic reference, or a Scale-Index-Base with or without
              // an offset. So, if we have a const-displacement of 4, it could look like one of:
              // `4+someGlobalVariable`, or `4+8(%ebp)`, or `4+(%ebp)`.
              // Notice how that last possibility is an invalid syntax, it should be `4(%ebp)`.
              // But, if we removed the `+`, then the other possibilities would become invalid or wrong.
              // We don't know which form will be generated, but we can force the third possibility to
              // be like the second possibility.
              //
              // For x86 assembly templates, memory operands can be marked with the `H` modifier, which
              // unconditionally adds an offset of 8 to the memory operand.
              // If we subtract 8 from our const-displacement, then we can use the `H` modifier to ensure
              // that we always end up with a valid syntax for a memory operand with an offset.
              // So, we do just that when we have const-displacement in play.
              // (Only for non-naked asm, as this isn't an issue for naked asm.)
              //
              // See also: https://lists.llvm.org/pipermail/llvm-dev/2017-August/116244.html
              const auto forceLeadingDisplacement = hasConstDisplacement && !sc->func->isNaked();
              if (forceLeadingDisplacement) {
                // Subtract 8 from our const-displacement, and prepare to add the 8 from the `H` modifier.
                insnTemplate << "-8+";
              }

              if (use_star) {
                insnTemplate << '*';
                use_star = false;
              }

              if (!sc->func->isNaked()) // no addrexp in naked asm please :)
              {
                Type *tt = pointerTo(e->type);
                e = createAddrExp(Loc(), e);
                e->type = tt;
              }

              if (forceLeadingDisplacement) {
                // We have a const-displacement in play, so we add the `H` modifier, as described earlier.
                insnTemplate << "${" << "<<" << (mode == Mode_Input ? "in" : "out")
                             << asmcode->args.size() << ">>" << ":H}";
                asmcode->args.push_back(AsmArg(Arg_Memory, e, mode));
              } else {
                addOperand(fmt, Arg_Memory, e, asmcode, mode);
              }
            }
          }
        }
        if (use_star) {
          insnTemplate << '*';
        }
        if (operand->baseReg != Reg_Invalid ||
            operand->indexReg != Reg_Invalid) {
          insnTemplate << '(';
          if (operand->baseReg != Reg_Invalid) {
            writeReg(operand->baseReg);
          }
          if (operand->indexReg != Reg_Invalid) {
            insnTemplate << ',';
            writeReg(operand->indexReg);
            if (operand->scale) {
              insnTemplate << "," << operand->scale;
            }
          }
          insnTemplate << ')';
          if (opInfo->operands[i] & Opr_Dest) {
            asmcode->clobbersMemory = 1;
          }
        }
        break;
      case Opr_Invalid:
        return false;
      }
    }

    mem.addRange(asmcode->args.data(), asmcode->args.size() * sizeof(AsmArg));

    asmcode->insnTemplate = insnTemplate.str();
    Logger::cout() << "insnTemplate = " << asmcode->insnTemplate << '\n';
    return true;
  }

  bool isIntExp(Expression *exp) { return exp->op == EXP::int64; }
  bool isRegExp(Expression *exp) { return exp->op == EXP::mod; } // ewww.%%
  bool isLocalSize(Expression *exp) {
    // cleanup: make a static var
    auto ie = exp->isIdentifierExp();
    return ie && ie->ident == Id::__LOCAL_SIZE;
  }
  bool isDollar(Expression *exp) {
    auto ie = exp->isIdentifierExp();
    return ie && ie->ident == Id::dollar;
  }

  Expression *newRegExp(int regno) {
    auto e = IntegerExp::create(Loc(), regno, Type::tint32);
    e->op = EXP::mod;
    return e;
  }

#ifndef ASM_X86_64
  Expression *newIntExp(int v /* %% type */) {
    // Only handles 32-bit numbers as this is IA-32.
    return IntegerExp::create(stmt->loc, v, Type::tint32);
  }
#else
  Expression *newIntExp(long long v /* %% type */) {
    // Handle 64 bit
    return IntegerExp::create(stmt->loc, v, Type::tint64);
  }
#endif

  void slotExp(Expression *exp) {
    /*
      if offset, make a note

      if integer, add to immediate
      if reg:
          if not in bracket, set reg (else error)
          if in bracket:
              if not base, set base
              if not index, set index
          else, error
      if symbol:
        set symbol field
     */

    bool is_offset = false;
    if (auto ae = exp->isAddrExp()) {
      exp = ae->e1;
      is_offset = true;
    }

    if (isIntExp(exp)) {
      if (is_offset) {
        invalidExpression();
      }
      operand->constDisplacement += exp->toInteger();
      if (!operand->inBracket) {
        operand->hasNumber = 1;
      }
    } else if (isRegExp(exp)) {
      if (is_offset) {
        invalidExpression();
      }
      if (!operand->inBracket) {
        if (operand->reg == Reg_Invalid) {
          operand->reg = static_cast<Reg>(exp->toInteger());
        } else {
          error(stmt->loc, "too many registers in operand (use brackets)");
        }
      } else {
        if (operand->baseReg == Reg_Invalid) {
          operand->baseReg = static_cast<Reg>(exp->toInteger());
        } else if (operand->indexReg == Reg_Invalid) {
          operand->indexReg = static_cast<Reg>(exp->toInteger());
          operand->scale = 1;
        } else {
          error(stmt->loc, "too many registers memory operand");
        }
      }
    } else if (auto ve = exp->isVarExp()) {
      VarDeclaration *v = ve->var->isVarDeclaration();

      if (v && v->storage_class & STCfield) {
        operand->constDisplacement += v->offset;
        if (!operand->inBracket) {
          operand->hasNumber = 1;
        }
      } else {
        if (v && v->type->isscalar()) {
          // DMD doesn't check Tcomplex*, and counts Tcomplex32 as
          // Tfloat64
          TY ty = v->type->toBasetype()->ty;
          operand->dataSizeHint =
              (ty == TY::Tfloat80 || ty == TY::Timaginary80) &&
                      !global.params.targetTriple->isWindowsMSVCEnvironment()
                  ? Extended_Ptr
                  : static_cast<PtrType>(dmd::size(v->type));
        }

        if (!operand->symbolDisplacement.length) {
          if (is_offset && !operand->inBracket) {
            operand->isOffset = 1;
          }
          operand->symbolDisplacement.push(exp);
        } else {
          error(stmt->loc, "too many symbols in operand");
        }
      }
    } else if (exp->op == EXP::identifier || exp->op == EXP::dSymbol) {
      // %% localsize could be treated as a simple constant..
      // change to addSymbolDisp(e)
      if (!operand->symbolDisplacement.length) {
        operand->symbolDisplacement.push(exp);
      } else {
        error(stmt->loc, "too many symbols in operand");
      }
    } else if (exp == Handled) {
      // nothing
    } else {
      error(stmt->loc, "invalid operand");
    }
  }

  void invalidExpression() {
    // %% report operand number
    error(stmt->loc, "invalid expression");
  }

  Expression *intOp(TOK op, Expression *e1, Expression *e2) {
    using namespace dmd;

    if (isIntExp(e1) && (!e2 || isIntExp(e2))) {
      Expression *e = createExpressionForIntOp(stmt->loc, op, e1, e2);
      e = expressionSemantic(e, sc);
      return ctfeInterpret(e);
    }

    error(stmt->loc, "expected integer operand(s) for `%s`", Token::toChars(op));
    return newIntExp(0);
  }

  void parseOperand() {
    Expression *exp = parseAsmExp();
    slotExp(exp);
    if (isRegExp(exp)) {
      operand->dataSize = static_cast<PtrType>(regInfo[exp->toInteger()].size);
    }
  }

  Expression *parseAsmExp() { return parseCondExp(); }

  Expression *parseCondExp() {
    Expression *exp = parseLogOrExp();
    if (token->value == TOK::question) {
      nextToken();
      Expression *exp2 = parseCondExp();
      if (token->value != TOK::colon) {
        return exp;
      }
      nextToken();
      Expression *exp3 = parseCondExp();
      exp = exp->toUInteger() ? exp2 : exp3;
    }
    return exp;
  }

  Expression *parseLogOrExp() {
    Expression *exp = parseLogAndExp();
    while (token->value == TOK::orOr) {
      nextToken();
      Expression *exp2 = parseLogAndExp();
      if (isIntExp(exp) && isIntExp(exp2)) {
        exp = intOp(TOK::andAnd, exp, exp2);
      } else {
        error(stmt->loc, "bad integral operand");
      }
    }
    return exp;
  }

  Expression *parseLogAndExp() {
    Expression *exp = parseIncOrExp();
    while (token->value == TOK::orOr) {
      nextToken();
      Expression *exp2 = parseIncOrExp();
      if (isIntExp(exp) && isIntExp(exp2)) {
        exp = intOp(TOK::orOr, exp, exp2);
      } else {
        error(stmt->loc, "bad integral operand");
      }
    }
    return exp;
  }

  Expression *parseIncOrExp() {
    Expression *exp = parseXOrExp();
    while (token->value == TOK::or_) {
      nextToken();
      Expression *exp2 = parseXOrExp();
      if (isIntExp(exp) && isIntExp(exp2)) {
        exp = intOp(TOK::or_, exp, exp2);
      } else {
        error(stmt->loc, "bad integral operand");
      }
    }
    return exp;
  }

  Expression *parseXOrExp() {
    Expression *exp = parseAndExp();
    while (token->value == TOK::xor_) {
      nextToken();
      Expression *exp2 = parseAndExp();
      if (isIntExp(exp) && isIntExp(exp2)) {
        exp = intOp(TOK::xor_, exp, exp2);
      } else {
        error(stmt->loc, "bad integral operand");
      }
    }
    return exp;
  }

  Expression *parseAndExp() {
    Expression *exp = parseEqualExp();
    while (token->value == TOK::and_) {
      nextToken();
      Expression *exp2 = parseEqualExp();
      if (isIntExp(exp) && isIntExp(exp2)) {
        exp = intOp(TOK::and_, exp, exp2);
      } else {
        error(stmt->loc, "bad integral operand");
      }
    }
    return exp;
  }

  Expression *parseEqualExp() {
    const auto exp = parseRelExp();
    const auto tok = token->value;
    if (tok != TOK::equal && tok != TOK::notEqual) {
      return exp;
    }

    nextToken();
    const auto exp2 = parseRelExp();
    if (isIntExp(exp) && isIntExp(exp2)) {
      return intOp(tok, exp, exp2);
    }

    error(stmt->loc, "bad integral operand");
    // TODO: Return ErrorExp?
    return exp;
  }

  Expression *parseRelExp() {
    const auto exp = parseShiftExp();
    const auto tok = token->value;
    if (tok != TOK::greaterThan && tok != TOK::greaterOrEqual &&
        tok != TOK::lessThan && tok != TOK::lessOrEqual) {
      return exp;
    }

    nextToken();
    const auto exp2 = parseShiftExp();
    if (isIntExp(exp) && isIntExp(exp2)) {
      return intOp(tok, exp, exp2);
    }

    error(stmt->loc, "bad integral operand");
    // TODO: Return ErrorExp?
    return exp;
  }

  Expression *parseShiftExp() {
    Expression *e1 = parseAddExp();
    Expression *e2;

    while (1) {
      TOK tv = token->value;
      switch (tv) {
      case TOK::leftShift:
      case TOK::rightShift:
      case TOK::unsignedRightShift:
        nextToken();
        e2 = parseAddExp();
        e1 = intOp(tv, e1, e2);
        continue;
      default:
        break;
      }
      break;
    }
    return e1;
  }

  Expression *parseAddExp() {
    Expression *e1 = parseMultExp();
    Expression *e2;

    while (1) {
      TOK tv = token->value;
      switch (tv) {
      case TOK::add:
        nextToken();
        e2 = parseMultExp();
        if (isIntExp(e1) && isIntExp(e2)) {
          e1 = intOp(tv, e1, e2);
        } else {
          slotExp(e1);
          slotExp(e2);
          e1 = Handled;
        }
        continue;
      case TOK::min:
        // Note: no support for symbol address difference
        nextToken();
        e2 = parseMultExp();
        if (isIntExp(e1) && isIntExp(e2)) {
          e1 = intOp(tv, e1, e2);
        } else {
          slotExp(e1);
          e2 = intOp(TOK::min, e2, nullptr); // verifies e2 is an int
          slotExp(e2);
          e1 = Handled;
        }
        continue;
      default:
        break;
      }
      break;
    }
    return e1;
  }

  bool tryScale(Expression *e1, Expression *e2) {
    Expression *et;
    if (isIntExp(e1) && isRegExp(e2)) {
      et = e1;
      e1 = e2;
      e2 = et;
      goto do_scale;
    } else if (isRegExp(e1) && isIntExp(e2)) {
    do_scale:
      if (!operand->inBracket) {
        invalidExpression(); // maybe should allow, e.g. DS:EBX+EAX*4
      }

      if (operand->scale || operand->indexReg != Reg_Invalid) {
        invalidExpression();
        return true;
      }

      operand->indexReg = static_cast<Reg>(e1->toInteger());
      operand->scale = e2->toInteger();
      switch (operand->scale) {
      case 1:
      case 2:
      case 4:
      case 8:
        // ok; do nothing
        break;
      default:
        error(stmt->loc, "invalid index register scale '%d'", operand->scale);
        return true;
      }

      return true;
    }
    return false;
  }

  Expression *parseMultExp() {
    Expression *e1 = parseBrExp();
    Expression *e2;

    while (1) {
      TOK tv = token->value;
      switch (tv) {
      case TOK::mul:
        nextToken();
        e2 = parseMultExp();
        if (isIntExp(e1) && isIntExp(e2)) {
          e1 = intOp(tv, e1, e2);
        } else if (tryScale(e1, e2)) {
          e1 = Handled;
        } else {
          invalidExpression();
        }
        continue;
      case TOK::div:
      case TOK::mod:
        nextToken();
        e2 = parseMultExp();
        e1 = intOp(tv, e1, e2);
        continue;
      default:
        break;
      }
      break;
    }
    return e1;
  }

  Expression *parseBrExp() {
    // %% check (why is bracket lower precends..)
    // 3+4[eax] -> 3 + (4 [EAX]) ..

    // only one bracked allowed, so this doesn't quite handle
    // the spec'd syntax
    Expression *e;

    if (token->value == TOK::leftBracket) {
      e = Handled;
    } else {
      e = parseUnaExp();
    }

    // DMD allows multiple bracket expressions.
    while (token->value == TOK::leftBracket) {
      nextToken();

      operand->inBracket = operand->hasBracket = 1;
      slotExp(parseAsmExp());
      operand->inBracket = 0;

      if (token->value == TOK::rightBracket) {
        nextToken();
      } else {
        error(stmt->loc, "missing `]`");
      }
    }

    return e;
  }

  PtrType isPtrType(Token *tok) {
    switch (tok->value) {
    case TOK::int8:
      return Byte_Ptr;
    case TOK::int16:
      return Short_Ptr;
    case TOK::int32:
      return Int_Ptr;
#ifndef ASM_X86_64
// 'long ptr' isn't accepted?
#else
    case TOK::int64:
      return QWord_Ptr;
#endif
    case TOK::float32:
      return Float_Ptr;
    case TOK::float64:
      return Double_Ptr;
    case TOK::float80:
      return global.params.targetTriple->isWindowsMSVCEnvironment()
                 ? Double_Ptr
                 : Extended_Ptr;
    case TOK::identifier:
      for (int i = 0; i < N_PtrNames; i++) {
        if (tok->ident == ptrTypeIdentTable[i]) {
          return ptrTypeValueTable[i];
        }
      }
      break;
    default:
      break;
    }
    return Default_Ptr;
  }

  Expression *parseUnaExp() {
    Expression *e = nullptr;
    PtrType ptr_type;

    // First, check for type prefix.
    if (token->value != TOK::endOfFile &&
        peekToken()->value == TOK::identifier &&
        peekToken()->ident == Id::ptr) {

      ptr_type = isPtrType(token);
      if (ptr_type != Default_Ptr) {
        if (operand->dataSize == Default_Ptr) {
          operand->dataSize = ptr_type;
        } else {
          error(stmt->loc, "multiple specifications of operand size");
        }
      } else {
        error(stmt->loc, "unknown operand size `%s`", token->toChars());
      }
      nextToken();
      nextToken();
      return parseAsmExp();
    }

    TOK tv = token->value;
    switch (tv) {
    case TOK::identifier:
      if (token->ident == ident_seg) {
        nextToken();
        error(stmt->loc, "`seg` not supported");
        e = parseAsmExp();
      } else if (token->ident == Id::offset || token->ident == Id::offsetof) {
        if (token->ident == Id::offset &&
            global.params.useDeprecated == DIAGNOSTICerror) {
          error(stmt->loc, "offset deprecated, use `offsetof`");
        }
        nextToken();
        e = parseAsmExp();
        e = createAddrExp(stmt->loc, e);
      } else {
        // primary exp
        break;
      }
      return e;
    case TOK::add:
    case TOK::min:
    case TOK::not_:
    case TOK::tilde:
      nextToken();
      e = parseUnaExp();
      return intOp(tv, e, nullptr);
    default:
      // primary exp
      break;
    }
    return parsePrimaryExp();
  }

  Expression *parsePrimaryExp() {
    using namespace dmd;

    Expression *e;
    Identifier *ident = nullptr;

    // get rid of short/long prefixes for branches
    if (opTakesLabel() &&
        (token->value == TOK::int16 || token->value == TOK::int64)) {
      nextToken();
    }

    switch (token->value) {
    case TOK::int32Literal:
    case TOK::uns32Literal:
    case TOK::int64Literal:
    case TOK::uns64Literal:
// semantic here?
#ifndef ASM_X86_64
      // %% for tok64 really should use 64bit type
      e = IntegerExp::create(stmt->loc, token->unsvalue, Type::tint32);
#else
      e = IntegerExp::create(stmt->loc, token->unsvalue, Type::tint64);
#endif
      nextToken();
      break;
    case TOK::float32Literal:
    case TOK::float64Literal:
    case TOK::float80Literal:
      // %% need different types?
      e = RealExp::create(stmt->loc, token->floatvalue, Type::tfloat80);
      nextToken();
      break;
    case TOK::identifier: {
      ident = token->ident;
      nextToken();

      if (ident == Id::__LOCAL_SIZE) {
        return IdentifierExp::create(stmt->loc, ident);
      }
      if (ident == Id::dollar) {
      do_dollar:
        return IdentifierExp::create(stmt->loc, ident);
      } else {
        e = IdentifierExp::create(stmt->loc, ident);
      }

      // If this is more than one component ref, it gets complicated:
      // *(&Field +
      // n)
      // maybe just do one step at a time..
      // simple case is Type.f -> VarDecl(field)
      // actually, DMD only supports on level...
      // X.y+Y.z[EBX] is supported, tho..
      // %% doesn't handle properties (check%%)
      while (token->value == TOK::dot) {
        nextToken();
        if (token->value == TOK::identifier) {
          e = DotIdExp::create(stmt->loc, e, token->ident);
          nextToken();
        } else {
          error(stmt->loc, "expected identifier");
          return Handled;
        }
      }

      // check for reg first then dotexp is an error?
      if (e->op == EXP::identifier) {
        for (int i = 0; i < N_Regs; i++) {
          const auto reg = regInfo[i].ident;
          const auto matchesRegister =
              stmt->caseSensitive
                  ? ident == reg
                  : reg && llvm::StringRef(ident->toChars())
                               .equals_insensitive(reg->toChars());
          if (matchesRegister) {
            if (static_cast<Reg>(i) == Reg_ST &&
                token->value == TOK::leftParenthesis) {
              nextToken();
              switch (token->value) {
              case TOK::int32Literal:
              case TOK::uns32Literal:
              case TOK::int64Literal:
              case TOK::uns64Literal:
                if (token->unsvalue < 8) {
                  e = newRegExp(static_cast<Reg>(Reg_ST + token->unsvalue));
                } else {
                  error(stmt->loc, "invalid floating point register index");
                  e = Handled;
                }
                nextToken();
                if (token->value == TOK::rightParenthesis) {
                  nextToken();
                } else {
                  error(stmt->loc, "expected `)`");
                }
                return e;
              default:
                break;
              }
              invalidExpression();
              return Handled;
            }
            if (token->value == TOK::colon) {
              nextToken();
              if (operand->segmentPrefix != Reg_Invalid) {
                error(stmt->loc, "too many segment prefixes");
              } else if (i >= Reg_CS && i <= Reg_GS) {
                operand->segmentPrefix = static_cast<Reg>(i);
              } else {
                error(stmt->loc, "`%s` is not a segment register", ident->toChars());
              }
              return parseAsmExp();
            }
            return newRegExp(static_cast<Reg>(i));
          }
        }
      }

      if (e->op == EXP::identifier) {
        // DMD uses labels secondarily to other symbols, so check
        // if IdentifierExp::semantic won't find anything.
        Dsymbol *scopesym;
        if (!sc->search(stmt->loc, ident, scopesym)) {
          if (LabelDsymbol *labelsym = sc->func->searchLabel(ident, stmt->loc)) {
            e = createDsymbolExp(stmt->loc, labelsym);
            if (opTakesLabel()) {
              return e;
            }
            return createAddrExp(stmt->loc, e);
          }
        }
      }

      e = expressionSemantic(e, sc);
      e = optimize(e, WANTvalue);

      // Special case for floating point constant declarations.
      if (e->op == EXP::float64) {
        Dsymbol *scopesym;
        Dsymbol *sym = sc->search(stmt->loc, ident, scopesym);
        if (sym) {
          VarDeclaration *v = sym->isVarDeclaration();
          if (v) {
            Expression *ve = VarExp::create(stmt->loc, v);
            ve->type = e->type;
            e = ve;
          }
        }
      }
      return e;
    } break;
    case TOK::dollar:
      nextToken();
      ident = Id::dollar;
      goto do_dollar;
      break;
    default:
      if (op == Op_FMath0 || op == Op_FdST0ST1 || op == Op_FMath) {
        return Handled;
      }
      // DMD does not emit an error message here.
      // invalidExpression();
      return Handled;
    }
    return e;
  }

  void doAlign() {
    using namespace dmd;

    // .align bits vs. bytes...
    // apparently a.out platforms use bits instead of bytes...

    // parse primary: DMD allows 'MyAlign' (const int) but not '2+2'
    // GAS is padding with NOPs last time I checked.
    Expression *e = ctfeInterpret(parseAsmExp());
    uinteger_t align = e->toUInteger();

    if ((align & (align - 1)) == 0) {
// FIXME: This printf is not portable. The use of `align` varies from
// system to system; on i386 using a.out, .align `n` will align on a 2^`n`
// boundary instead of an `n` boundary
#ifdef HAVE_GAS_BALIGN_AND_P2ALIGN
      insnTemplate << ".balign\t" << align;
#else
      insnTemplate << ".align\t" << align;
#endif
    } else {
      error(stmt->loc, "alignment must be a power of 2, not %u",
                  static_cast<unsigned>(align));
    }

    setAsmCode();
  }

  void doEven() {
// .align for GAS is in bits, others probably use bytes..
#ifdef HAVE_GAS_BALIGN_AND_P2ALIGN
    insnTemplate << ".align\t2";
#else
    insnTemplate << ".align\t2";
#endif
    setAsmCode();
  }

  void doNaked() {
    // %% can we assume sc->func != 0?
    sc->func->isNaked(true);
  }

  void doData() {
    static const char *directives[] = {".byte", ".short", ".long", ".quad",
                                       ".long", ".quad",  ".quad"};

    insnTemplate << directives[op - Op_db] << ' ';

    const llvm::fltSemantics *realSemantics = nullptr;
    unsigned realSizeInBits = 0;

    do {
      // DMD is pretty strict here, not even constant expressions are allowed..
      switch (op) {
      case Op_db:
      case Op_ds:
      case Op_di:
      case Op_dl:
        if (token->value == TOK::int32Literal ||
            token->value == TOK::uns32Literal ||
            token->value == TOK::int64Literal ||
            token->value == TOK::uns64Literal) {
          // As per usual with GNU, assume at least 32-bit host
          if (op != Op_dl) {
            insnTemplate << static_cast<uint32_t>(token->unsvalue);
          } else {
            insnTemplate << token->unsvalue;
          }
        } else {
          error(stmt->loc, "expected integer constant");
        }
        break;
      case Op_df:
      case Op_dd:
      case Op_de:
        if (token->value == TOK::float32Literal ||
            token->value == TOK::float64Literal ||
            token->value == TOK::float80Literal) {
          if (op == Op_df) {
            const float value = static_cast<float>(token->floatvalue);
            uint32_t reinterpreted; // explicitly use memcpy because of strict-aliasing
            std::memcpy(&reinterpreted, &value, sizeof value);
            insnTemplate << reinterpreted;
          } else if (op == Op_dd) {
            const double value = static_cast<double>(token->floatvalue);
            uint64_t reinterpreted; // explicitly use memcpy because of strict-aliasing
            std::memcpy(&reinterpreted, &value, sizeof value);
            insnTemplate << reinterpreted;
          } else if (op == Op_de) {
            llvm::APFloat value(0.0);
            CTFloat::toAPFloat(token->floatvalue, value);

            if (!realSemantics)
              realSemantics = &DtoType(Type::tfloat80)->getFltSemantics();

            if (&value.getSemantics() != realSemantics) {
              bool ignored;
              value.convert(*realSemantics, APFloat::rmNearestTiesToEven,
                            &ignored);
            }

            const auto asInt = value.bitcastToAPInt();
            if (realSizeInBits == 0)
              realSizeInBits = asInt.getBitWidth();

            auto *ptr = reinterpret_cast<const uint64_t *>(asInt.getRawData());
            insnTemplate << ptr[0];
            if (realSizeInBits == 64) { // e.g., MSVC x86(_64)
              // nothing left to do
            } else if (realSizeInBits == 128) { // e.g., Android x64
              insnTemplate << ',' << ptr[1];
            } else if (realSizeInBits == 80) {
              // DMD outputs 10 bytes, so we need to switch to .short here
              insnTemplate << "\n\t.short "
                           << *reinterpret_cast<const uint16_t *>(ptr + 1);
            } else {
              error(stmt->loc, "unsupported target `real` size");
            }
          } else {
            llvm_unreachable("unexpected op");
          }
        } else {
          error(stmt->loc, "expected float constant");
        }
        break;
      default:
        error(stmt->loc, "Unsupported data definition directive inside inline asm.");
        break;
      }

      nextToken();
      if (token->value == TOK::comma) {
        if (op == Op_de && realSizeInBits == 80) {
          insnTemplate << "\n\t.quad "; // switch from .short back to .quad
        } else {
          insnTemplate << ',';
        }
        nextToken();
      } else if (token->value == TOK::endOfFile) {
        break;
      } else {
        error(stmt->loc, "expected comma");
      }
    } while (1);

    setAsmCode();
  }
};

// FIXME
#define HOST_WIDE_INT long
bool getFrameRelativeValue(LLValue *decl, HOST_WIDE_INT *result) {
  llvm_unreachable("getFrameRelativeValue not implemented.");
  // FIXME
  //     // Using this instead of DECL_RTL for struct args seems like a
  //     // good way to get hit by a truck because it may not agree with
  //     // non-asm access, but asm code wouldn't know what GCC does
  //     anyway. */
  //     rtx r = DECL_INCOMING_RTL(decl);
  //     rtx e1, e2;
  //
  //     // Local variables don't have DECL_INCOMING_RTL
  //     if (r == NULL_RTX)
  //  r = DECL_RTL(decl);
  //
  //     if (r != NULL_RTX && GET_CODE(r) == MEM /* && r->frame_related */
  //     ) {
  //  r = XEXP(r, 0);
  //  if (GET_CODE(r) == PLUS) {
  //      e1 = XEXP(r, 0);
  //      e2 = XEXP(r, 1);
  //      if (e1 == virtual_incoming_args_rtx && GET_CODE(e2) ==
  //      CONST_INT) {
  //      *result = INTVAL(e2) + 8; // %% 8 is 32-bit specific...
  //      return true;
  //      } else if (e1 == virtual_stack_vars_rtx && GET_CODE(e2) ==
  //      CONST_INT)
  //      {
  //      *result = INTVAL(e2); // %% 8 is 32-bit specific...
  //      return true;
  //      }
  //  } else if (r == virtual_incoming_args_rtx) {
  //      *result = 8;
  //      return true; // %% same as above
  //  }
  //  // shouldn't have virtual_stack_vars_rtx by itself
  //     }
  //
  return false;
}

struct AsmParser : public AsmParserCommon {
  void run(Scope *sc, InlineAsmStatement *asmst) override {
    AsmProcessor ap(sc, asmst);
    ap.run();
  }

  std::string getRegName(int i) override { return regInfo[i].gccName; }
};

} // namespace
