translate: rudimentary mmx supprt, more flags for dec
[ia32rtools.git] / tools / translate.c
index de2b30c..be4e24d 100644 (file)
@@ -54,6 +54,7 @@ enum op_flags {
   OPF_ATAIL  = (1 << 14), /* tail call with reused arg frame */
   OPF_32BIT  = (1 << 15), /* 32bit division */
   OPF_LOCK   = (1 << 16), /* op has lock prefix */
+  OPF_VAPUSH = (1 << 17), /* vararg ptr push (as call arg) */
 };
 
 enum op_op {
@@ -85,6 +86,7 @@ enum op_op {
        OP_SHL,
        OP_SHR,
        OP_SAR,
+       OP_SHRD,
        OP_ROL,
        OP_ROR,
        OP_RCL,
@@ -106,6 +108,9 @@ enum op_op {
        OP_JECXZ,
        OP_JCC,
        OP_SCC,
+       // x87
+       // mmx
+       OP_EMMS,
 };
 
 enum opr_type {
@@ -123,6 +128,7 @@ enum opr_lenmod {
        OPLM_BYTE,
        OPLM_WORD,
        OPLM_DWORD,
+       OPLM_QWORD,
 };
 
 #define MAX_OPERANDS 3
@@ -149,11 +155,12 @@ struct parsed_op {
   unsigned char pfo;
   unsigned char pfo_inv;
   unsigned char operand_cnt;
-  unsigned char pad;
+  unsigned char p_argnum; // push: altered before call arg #
+  unsigned char p_argpass;// push: arg of host func
+  unsigned char pad[3];
   int regmask_src;        // all referensed regs
   int regmask_dst;
   int pfomask;            // flagop: parsed_flag_op that can't be delayed
-  int argnum;             // push: altered before call arg #
   int cc_scratch;         // scratch storage during analysis
   int bt_i;               // branch target for branches
   struct parsed_data *btj;// branch targets for jumptables
@@ -230,9 +237,11 @@ static int g_allow_regfunc;
   printf("%s:%d: note: [%s] '%s': " fmt, asmfn, (op_)->asmln, g_func, \
     dump_op(op_), ##__VA_ARGS__)
 
-#define MAX_REGS 8
-
-const char *regs_r32[] = { "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp" };
+const char *regs_r32[] = {
+  "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp",
+  // not r32, but list here for easy parsing and printing
+  "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7",
+};
 const char *regs_r16[] = { "ax", "bx", "cx", "dx", "si", "di", "bp", "sp" };
 const char *regs_r8l[] = { "al", "bl", "cl", "dl" };
 const char *regs_r8h[] = { "ah", "bh", "ch", "dh" };
@@ -251,6 +260,11 @@ enum parsed_flag_op {
   PFO_LE, // e ZF=1||SF!=OF
 };
 
+#define PFOB_O   (1 << PFO_O)
+#define PFOB_C   (1 << PFO_C)
+#define PFOB_Z   (1 << PFO_Z)
+#define PFOB_S   (1 << PFO_S)
+
 static const char *parsed_flag_op_names[] = {
   "o", "c", "z", "be", "s", "p", "l", "le"
 };
@@ -273,11 +287,31 @@ static void printf_number(char *buf, size_t buf_size,
   snprintf(buf, buf_size, number < 10 ? "%lu" : "0x%02lx", number);
 }
 
+static int check_segment_prefix(const char *s)
+{
+  if (s[0] == 0 || s[1] != 's' || s[2] != ':')
+    return 0;
+
+  switch (s[0]) {
+  case 'c': return 1;
+  case 'd': return 2;
+  case 's': return 3;
+  case 'e': return 4;
+  case 'f': return 5;
+  case 'g': return 6;
+  default:  return 0;
+  }
+}
+
 static int parse_reg(enum opr_lenmod *reg_lmod, const char *s)
 {
   int reg;
 
   reg = char_array_i(regs_r32, ARRAY_SIZE(regs_r32), s);
+  if (reg >= 8) {
+    *reg_lmod = OPLM_QWORD;
+    return reg;
+  }
   if (reg >= 0) {
     *reg_lmod = OPLM_DWORD;
     return reg;
@@ -324,8 +358,8 @@ static int parse_indmode(char *name, int *regmask, int need_c_cvt)
       s++;
     *d = 0;
 
-    // skip 'ds:' prefix
-    if (IS_START(s, "ds:"))
+    // skip '?s:' prefixes
+    if (check_segment_prefix(s))
       s += 3;
 
     s = next_idt(w, sizeof(w), s);
@@ -371,7 +405,8 @@ static int is_reg_in_str(const char *s)
   return 0;
 }
 
-static const char *parse_stack_el(const char *name, char *extra_reg)
+static const char *parse_stack_el(const char *name, char *extra_reg,
+  int early_try)
 {
   const char *p, *p2, *s;
   char *endp = NULL;
@@ -379,31 +414,34 @@ static const char *parse_stack_el(const char *name, char *extra_reg)
   long val;
   int len;
 
-  p = name;
-  if (IS_START(p + 3, "+ebp+") && is_reg_in_str(p)) {
-    p += 4;
-    if (extra_reg != NULL) {
-      strncpy(extra_reg, name, 3);
-      extra_reg[4] = 0;
+  if (g_bp_frame || early_try)
+  {
+    p = name;
+    if (IS_START(p + 3, "+ebp+") && is_reg_in_str(p)) {
+      p += 4;
+      if (extra_reg != NULL) {
+        strncpy(extra_reg, name, 3);
+        extra_reg[4] = 0;
+      }
     }
-  }
 
-  if (IS_START(p, "ebp+")) {
-    p += 4;
+    if (IS_START(p, "ebp+")) {
+      p += 4;
 
-    p2 = strchr(p, '+');
-    if (p2 != NULL && is_reg_in_str(p)) {
-      if (extra_reg != NULL) {
-        strncpy(extra_reg, p, p2 - p);
-        extra_reg[p2 - p] = 0;
+      p2 = strchr(p, '+');
+      if (p2 != NULL && is_reg_in_str(p)) {
+        if (extra_reg != NULL) {
+          strncpy(extra_reg, p, p2 - p);
+          extra_reg[p2 - p] = 0;
+        }
+        p = p2 + 1;
       }
-      p = p2 + 1;
-    }
 
-    if (!('0' <= *p && *p <= '9'))
-      return p;
+      if (!('0' <= *p && *p <= '9'))
+        return p;
 
-    return NULL;
+      return NULL;
+    }
   }
 
   if (!IS_START(name, "esp+"))
@@ -463,6 +501,10 @@ static int guess_lmod_from_name(struct parsed_opr *opr)
     opr->lmod = OPLM_BYTE;
     return 1;
   }
+  if (!strncmp(opr->name, "qword_", 6)) {
+    opr->lmod = OPLM_QWORD;
+    return 1;
+  }
   return 0;
 }
 
@@ -472,7 +514,8 @@ static int guess_lmod_from_c_type(enum opr_lenmod *lmod,
   static const char *dword_types[] = {
     "int", "_DWORD", "UINT_PTR", "DWORD",
     "WPARAM", "LPARAM", "UINT", "__int32",
-    "LONG", "HIMC",
+    "LONG", "HIMC", "BOOL", "size_t",
+    "float",
   };
   static const char *word_types[] = {
     "uint16_t", "int16_t", "_WORD", "WORD",
@@ -608,7 +651,10 @@ static int parse_operand(struct parsed_opr *opr,
 
     if (label != NULL) {
       opr->type = OPT_LABEL;
-      if (IS_START(label, "ds:")) {
+      ret = check_segment_prefix(label);
+      if (ret != 0) {
+        if (ret >= 5)
+          aerr("fs/gs used\n");
         opr->had_ds = 1;
         label += 3;
       }
@@ -625,6 +671,8 @@ static int parse_operand(struct parsed_opr *opr,
         opr->lmod = OPLM_WORD;
       else if (IS(words[w], "byte"))
         opr->lmod = OPLM_BYTE;
+      else if (IS(words[w], "qword"))
+        opr->lmod = OPLM_QWORD;
       else
         aerr("type parsing failed\n");
       w += 2;
@@ -654,7 +702,10 @@ static int parse_operand(struct parsed_opr *opr,
   if (wordc_in != 1)
     aerr("parse_operand 1 word expected\n");
 
-  if (IS_START(words[w], "ds:")) {
+  ret = check_segment_prefix(words[w]);
+  if (ret != 0) {
+    if (ret >= 5)
+      aerr("fs/gs used\n");
     opr->had_ds = 1;
     memmove(words[w], words[w] + 3, strlen(words[w]) - 2);
   }
@@ -667,10 +718,11 @@ static int parse_operand(struct parsed_opr *opr,
       aerr("[] parse failure\n");
 
     parse_indmode(opr->name, regmask_indirect, 1);
-    if (opr->lmod == OPLM_UNSPEC && parse_stack_el(opr->name, NULL)) {
+    if (opr->lmod == OPLM_UNSPEC && parse_stack_el(opr->name, NULL, 1))
+    {
       // might be an equ
       struct parsed_equ *eq =
-        equ_find(NULL, parse_stack_el(opr->name, NULL), &i);
+        equ_find(NULL, parse_stack_el(opr->name, NULL, 1), &i);
       if (eq)
         opr->lmod = eq->lmod;
     }
@@ -798,6 +850,7 @@ static const struct {
   { "shr",  OP_SHR,    2, 2, OPF_DATA|OPF_FLAGS },
   { "sal",  OP_SHL,    2, 2, OPF_DATA|OPF_FLAGS },
   { "sar",  OP_SAR,    2, 2, OPF_DATA|OPF_FLAGS },
+  { "shrd", OP_SHRD,   3, 3, OPF_DATA|OPF_FLAGS },
   { "rol",  OP_ROL,    2, 2, OPF_DATA|OPF_FLAGS },
   { "ror",  OP_ROR,    2, 2, OPF_DATA|OPF_FLAGS },
   { "rcl",  OP_RCL,    2, 2, OPF_DATA|OPF_FLAGS|OPF_CC, PFO_C },
@@ -876,6 +929,10 @@ static const struct {
   { "setng",  OP_SCC,  1, 1, OPF_DATA|OPF_CC, PFO_LE, 0 },
   { "setg",   OP_SCC,  1, 1, OPF_DATA|OPF_CC, PFO_LE, 1 },
   { "setnle", OP_SCC,  1, 1, OPF_DATA|OPF_CC, PFO_LE, 1 },
+  // x87
+  // mmx
+  { "emms",   OP_EMMS, 0, 0, OPF_DATA },
+  { "movq",   OP_MOV,  2, 2, OPF_DATA },
 };
 
 static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
@@ -1035,6 +1092,11 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
       op->operand[1].lmod = OPLM_BYTE;
     break;
 
+  case OP_SHRD:
+    if (op->operand[2].lmod == OPLM_UNSPEC)
+      op->operand[2].lmod = OPLM_BYTE;
+    break;
+
   case OP_PUSH:
     if (op->operand[0].lmod == OPLM_UNSPEC
         && (op->operand[0].type == OPT_CONST
@@ -1126,6 +1188,8 @@ static const char *lmod_type_u(struct parsed_op *po,
   enum opr_lenmod lmod)
 {
   switch (lmod) {
+  case OPLM_QWORD:
+    return "u64";
   case OPLM_DWORD:
     return "u32";
   case OPLM_WORD:
@@ -1142,6 +1206,8 @@ static const char *lmod_cast_u(struct parsed_op *po,
   enum opr_lenmod lmod)
 {
   switch (lmod) {
+  case OPLM_QWORD:
+    return "";
   case OPLM_DWORD:
     return "";
   case OPLM_WORD:
@@ -1158,6 +1224,8 @@ static const char *lmod_cast_u_ptr(struct parsed_op *po,
   enum opr_lenmod lmod)
 {
   switch (lmod) {
+  case OPLM_QWORD:
+    return "*(u64 *)";
   case OPLM_DWORD:
     return "*(u32 *)";
   case OPLM_WORD:
@@ -1174,6 +1242,8 @@ static const char *lmod_cast_s(struct parsed_op *po,
   enum opr_lenmod lmod)
 {
   switch (lmod) {
+  case OPLM_QWORD:
+    return "(s64)";
   case OPLM_DWORD:
     return "(s32)";
   case OPLM_WORD:
@@ -1197,6 +1267,8 @@ static const char *lmod_cast(struct parsed_op *po,
 static int lmod_bytes(struct parsed_op *po, enum opr_lenmod lmod)
 {
   switch (lmod) {
+  case OPLM_QWORD:
+    return 8;
   case OPLM_DWORD:
     return 4;
   case OPLM_WORD:
@@ -1227,7 +1299,7 @@ static unsigned int opr_const(struct parsed_op *po, int opr_num)
 
 static const char *opr_reg_p(struct parsed_op *po, struct parsed_opr *popr)
 {
-  if ((unsigned int)popr->reg >= MAX_REGS)
+  if ((unsigned int)popr->reg >= ARRAY_SIZE(regs_r32))
     ferr(po, "invalid reg: %d\n", popr->reg);
   return regs_r32[popr->reg];
 }
@@ -1314,7 +1386,7 @@ static struct parsed_equ *equ_find(struct parsed_op *po, const char *name,
 static int is_stack_access(struct parsed_op *po,
   const struct parsed_opr *popr)
 {
-  return (parse_stack_el(popr->name, NULL)
+  return (parse_stack_el(popr->name, NULL, 0)
     || (g_bp_frame && !(po->flags & OPF_EBP_S)
         && IS_START(popr->name, "ebp")));
 }
@@ -1345,7 +1417,7 @@ static void parse_stack_access(struct parsed_op *po,
       ferr(po, "ebp- parse of '%s' failed\n", name);
   }
   else {
-    bp_arg = parse_stack_el(name, ofs_reg);
+    bp_arg = parse_stack_el(name, ofs_reg, 0);
     snprintf(g_comment, sizeof(g_comment), "%s", bp_arg);
     eq = equ_find(po, bp_arg, &offset);
     if (eq == NULL)
@@ -1369,7 +1441,7 @@ static void parse_stack_access(struct parsed_op *po,
     *bp_arg_out = bp_arg;
 }
 
-static void stack_frame_access(struct parsed_op *po,
+static int stack_frame_access(struct parsed_op *po,
   struct parsed_opr *popr, char *buf, size_t buf_size,
   const char *name, const char *cast, int is_src, int is_lea)
 {
@@ -1381,6 +1453,7 @@ static void stack_frame_access(struct parsed_op *po,
   int unaligned = 0;
   int stack_ra = 0;
   int offset = 0;
+  int retval = -1;
   int sf_ofs;
   int lim;
 
@@ -1402,7 +1475,7 @@ static void stack_frame_access(struct parsed_op *po,
         if (cast[0] == 0)
           cast = "(u32)";
         snprintf(buf, buf_size, "%sap", cast);
-        return;
+        return -1;
       }
       ferr(po, "offset %d (%s,%d) doesn't map to any arg\n",
         offset, bp_arg, arg_i);
@@ -1421,6 +1494,7 @@ static void stack_frame_access(struct parsed_op *po,
       ferr(po, "arg %d not in prototype?\n", arg_i);
 
     popr->is_ptr = g_func_pp->arg[i].type.is_ptr;
+    retval = i;
 
     switch (popr->lmod)
     {
@@ -1492,7 +1566,8 @@ static void stack_frame_access(struct parsed_op *po,
     // common problem
     guess_lmod_from_c_type(&tmp_lmod, &g_func_pp->arg[i].type);
     if (tmp_lmod != OPLM_DWORD
-      && (unaligned || (!is_src && tmp_lmod < popr->lmod)))
+      && (unaligned || (!is_src && lmod_bytes(po, tmp_lmod)
+                         < lmod_bytes(po, popr->lmod) + (offset & 3))))
     {
       ferr(po, "bp_arg arg%d/w offset %d and type '%s' is too small\n",
         i + 1, offset, g_func_pp->arg[i].type.name);
@@ -1555,12 +1630,16 @@ static void stack_frame_access(struct parsed_op *po,
       ferr(po, "bp_stack bad lmod: %d\n", popr->lmod);
     }
   }
+
+  return retval;
 }
 
 static void check_func_pp(struct parsed_op *po,
   const struct parsed_proto *pp, const char *pfx)
 {
+  enum opr_lenmod tmp_lmod;
   char buf[256];
+  int ret, i;
 
   if (pp->argc_reg != 0) {
     if (/*!g_allow_regfunc &&*/ !pp->is_fastcall) {
@@ -1571,6 +1650,18 @@ static void check_func_pp(struct parsed_op *po,
       ferr(po, "%s: %d reg arg(s) with %d stack arg(s)\n",
         pfx, pp->argc_reg, pp->argc_stack);
   }
+
+  // fptrs must use 32bit args, callsite might have no information and
+  // lack a cast to smaller types, which results in incorrectly masked
+  // args passed (callee may assume masked args, it does on ARM)
+  if (!pp->is_oslib) {
+    for (i = 0; i < pp->argc; i++) {
+      ret = guess_lmod_from_c_type(&tmp_lmod, &pp->arg[i].type);
+      if (ret && tmp_lmod != OPLM_DWORD)
+        ferr(po, "reference to %s with arg%d '%s'\n", pp->name,
+          i + 1, pp->arg[i].type.name);
+    }
+  }
 }
 
 static const char *check_label_read_ref(struct parsed_op *po,
@@ -1607,6 +1698,9 @@ static char *out_src_opr(char *buf, size_t buf_size,
       ferr(po, "lea from reg?\n");
 
     switch (popr->lmod) {
+    case OPLM_QWORD:
+      snprintf(buf, buf_size, "%s%s.q", cast, opr_reg_p(po, popr));
+      break;
     case OPLM_DWORD:
       snprintf(buf, buf_size, "%s%s", cast, opr_reg_p(po, popr));
       break;
@@ -1712,6 +1806,9 @@ static char *out_dst_opr(char *buf, size_t buf_size,
   switch (popr->type) {
   case OPT_REG:
     switch (popr->lmod) {
+    case OPLM_QWORD:
+      snprintf(buf, buf_size, "%s.q", opr_reg_p(po, popr));
+      break;
     case OPLM_DWORD:
       snprintf(buf, buf_size, "%s", opr_reg_p(po, popr));
       break;
@@ -1802,7 +1899,7 @@ static void out_cmp_for_cc(char *buf, size_t buf_size,
   char buf1[256], buf2[256];
   enum opr_lenmod lmod;
 
-  if (po->operand[0].lmod != po->operand[1].lmod)
+  if (po->op != OP_DEC && po->operand[0].lmod != po->operand[1].lmod)
     ferr(po, "%s: lmod mismatch: %d %d\n", __func__,
       po->operand[0].lmod, po->operand[1].lmod);
   lmod = po->operand[0].lmod;
@@ -1828,7 +1925,10 @@ static void out_cmp_for_cc(char *buf, size_t buf_size,
   }
 
   out_src_opr(buf1, sizeof(buf1), po, &po->operand[0], cast_use, 0);
-  out_src_opr(buf2, sizeof(buf2), po, &po->operand[1], cast_use, 0);
+  if (po->op == OP_DEC)
+    snprintf(buf2, sizeof(buf2), "1");
+  else
+    out_src_opr(buf2, sizeof(buf2), po, &po->operand[1], cast_use, 0);
 
   switch (pfo) {
   case PFO_C:
@@ -1868,7 +1968,7 @@ static void out_cmp_for_cc(char *buf, size_t buf_size,
       buf1, is_inv ? ">=" : "<", buf2);
     break;
 
-  case PFO_LE:
+  case PFO_LE: // !g
     snprintf(buf, buf_size, "(%s %s %s)",
       buf1, is_inv ? ">" : "<=", buf2);
     break;
@@ -1996,7 +2096,7 @@ static int scan_for_pop(int i, int opcnt, const char *reg,
     }
 
     if ((po->flags & OPF_RMD)
-        || (po->op == OP_PUSH && po->argnum != 0)) // arg push
+        || (po->op == OP_PUSH && po->p_argnum != 0)) // arg push
       continue;
 
     if ((po->flags & OPF_JMP) && po->op != OP_CALL) {
@@ -2582,8 +2682,10 @@ static const struct parsed_proto *resolve_icall(int i, int opcnt,
   return pp;
 }
 
-static int try_resolve_const(int i, const struct parsed_opr *opr,
-  int magic, unsigned int *val)
+// find an instruction that changed opr before i op
+// *op_i must be set to -1
+static int resolve_origin(int i, const struct parsed_opr *opr,
+  int magic, int *op_i)
 {
   struct label_ref *lr;
   int ret = 0;
@@ -2594,7 +2696,7 @@ static int try_resolve_const(int i, const struct parsed_opr *opr,
     if (g_labels[i][0] != 0) {
       lr = &g_label_refs[i];
       for (; lr != NULL; lr = lr->next)
-        ret |= try_resolve_const(lr->i, opr, magic, val);
+        ret |= resolve_origin(lr->i, opr, magic, op_i);
       if (i > 0 && LAST_OP(i - 1))
         return ret;
     }
@@ -2611,12 +2713,36 @@ static int try_resolve_const(int i, const struct parsed_opr *opr,
       continue;
     if (!is_opr_modified(opr, &ops[i]))
       continue;
+
+    if (*op_i >= 0) {
+      if (*op_i == i)
+        return 1;
+      // XXX: could check if the other op does the same
+      return -1;
+    }
+
+    *op_i = i;
+    return 1;
+  }
+}
+
+static int try_resolve_const(int i, const struct parsed_opr *opr,
+  int magic, unsigned int *val)
+{
+  int s_i = -1;
+  int ret = 0;
+
+  ret = resolve_origin(i, opr, magic, &s_i);
+  if (ret == 1) {
+    i = s_i;
     if (ops[i].op != OP_MOV && ops[i].operand[1].type != OPT_CONST)
       return -1;
 
     *val = ops[i].operand[1].val;
     return 1;
   }
+
+  return -1;
 }
 
 static int collect_call_args_r(struct parsed_op *po, int i,
@@ -2626,8 +2752,11 @@ static int collect_call_args_r(struct parsed_op *po, int i,
   struct parsed_proto *pp_tmp;
   struct label_ref *lr;
   int need_to_save_current;
+  int save_args;
   int ret = 0;
-  int j;
+  int reg;
+  char buf[32];
+  int j, k;
 
   if (i < 0) {
     ferr(po, "dead label encountered\n");
@@ -2693,12 +2822,16 @@ static int collect_call_args_r(struct parsed_op *po, int i,
         ferr(po, "arg collect %d/%d hit '%s' with %d stack args\n",
           arg, pp->argc, opr_name(&ops[j], 0), pp_tmp->argc_stack);
     }
-    else if (ops[j].op == OP_ADD && ops[j].operand[0].reg == xSP) {
+    // esp adjust of 0 means we collected it before
+    else if (ops[j].op == OP_ADD && ops[j].operand[0].reg == xSP
+      && (ops[j].operand[1].type != OPT_CONST
+          || ops[j].operand[1].val != 0))
+    {
       if (pp->is_unresolved)
         break;
 
-      ferr(po, "arg collect %d/%d hit esp adjust\n",
-        arg, pp->argc);
+      ferr(po, "arg collect %d/%d hit esp adjust of %d\n",
+        arg, pp->argc, ops[j].operand[1].val);
     }
     else if (ops[j].op == OP_POP) {
       if (pp->is_unresolved)
@@ -2720,6 +2853,11 @@ static int collect_call_args_r(struct parsed_op *po, int i,
 
       pp->arg[arg].datap = &ops[j];
       need_to_save_current = 0;
+      save_args = 0;
+      reg = -1;
+      if (ops[j].operand[0].type == OPT_REG)
+        reg = ops[j].operand[0].reg;
+
       if (!need_op_saving) {
         ret = scan_for_mod(&ops[j], j + 1, i, 1);
         need_to_save_current = (ret >= 0);
@@ -2727,15 +2865,18 @@ static int collect_call_args_r(struct parsed_op *po, int i,
       if (need_op_saving || need_to_save_current) {
         // mark this push as one that needs operand saving
         ops[j].flags &= ~OPF_RMD;
-        if (ops[j].argnum == 0) {
-          ops[j].argnum = arg + 1;
-          *save_arg_vars |= 1 << arg;
+        if (ops[j].p_argnum == 0) {
+          ops[j].p_argnum = arg + 1;
+          save_args |= 1 << arg;
+        }
+        else if (ops[j].p_argnum < arg + 1) {
+          // XXX: might kill valid var..
+          //*save_arg_vars &= ~(1 << (ops[j].p_argnum - 1));
+          ops[j].p_argnum = arg + 1;
+          save_args |= 1 << arg;
         }
-        else if (ops[j].argnum < arg + 1)
-          ferr(&ops[j], "argnum conflict (%d<%d) for '%s'\n",
-            ops[j].argnum, arg + 1, pp->name);
       }
-      else if (ops[j].argnum == 0)
+      else if (ops[j].p_argnum == 0)
         ops[j].flags |= OPF_RMD;
 
       // some PUSHes are reused by different calls on other branches,
@@ -2746,6 +2887,49 @@ static int collect_call_args_r(struct parsed_op *po, int i,
 
       ops[j].flags &= ~OPF_RSAVE;
 
+      // check for __VALIST
+      if (!pp->is_unresolved && pp->arg[arg].type.is_va_list) {
+        k = -1;
+        ret = resolve_origin(j, &ops[j].operand[0], magic + 1, &k);
+        if (ret == 1 && k >= 0)
+        {
+          if (ops[k].op == OP_LEA) {
+            snprintf(buf, sizeof(buf), "arg_%X",
+              g_func_pp->argc_stack * 4);
+            if (!g_func_pp->is_vararg
+              || strstr(ops[k].operand[1].name, buf))
+            {
+              ops[k].flags |= OPF_RMD;
+              ops[j].flags |= OPF_RMD | OPF_VAPUSH;
+              save_args &= ~(1 << arg);
+              reg = -1;
+            }
+            else
+              ferr(&ops[j], "lea va_list used, but no vararg?\n");
+          }
+          // check for va_list from g_func_pp arg too
+          else if (ops[k].op == OP_MOV
+            && is_stack_access(&ops[k], &ops[k].operand[1]))
+          {
+            ret = stack_frame_access(&ops[k], &ops[k].operand[1],
+              buf, sizeof(buf), ops[k].operand[1].name, "", 1, 0);
+            if (ret >= 0) {
+              ops[k].flags |= OPF_RMD;
+              ops[j].flags |= OPF_RMD;
+              ops[j].p_argpass = ret + 1;
+              save_args &= ~(1 << arg);
+              reg = -1;
+            }
+          }
+        }
+      }
+
+      *save_arg_vars |= save_args;
+
+      // tracking reg usage
+      if (reg >= 0)
+        *regmask |= 1 << reg;
+
       arg++;
       if (!pp->is_unresolved) {
         // next arg
@@ -2754,10 +2938,6 @@ static int collect_call_args_r(struct parsed_op *po, int i,
             break;
       }
       magic = (magic & 0xffffff) | (arg << 24);
-
-      // tracking reg usage
-      if (ops[j].operand[0].type == OPT_REG)
-        *regmask |= 1 << ops[j].operand[0].reg;
     }
   }
 
@@ -3002,22 +3182,33 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
     } while (i < opcnt);
   }
   else {
-    for (i = 0; i < opcnt; i++) {
+    int ecx_push = 0, esp_sub = 0;
+
+    i = 0;
+    while (ops[i].op == OP_PUSH && IS(opr_name(&ops[i], 0), "ecx")) {
+      ops[i].flags |= OPF_RMD;
+      g_stack_fsz += 4;
+      ecx_push++;
+      i++;
+    }
+
+    for (; i < opcnt; i++) {
       if (ops[i].op == OP_PUSH || (ops[i].flags & (OPF_JMP|OPF_TAIL)))
         break;
       if (ops[i].op == OP_SUB && ops[i].operand[0].reg == xSP
         && ops[i].operand[1].type == OPT_CONST)
       {
-        g_sp_frame = 1;
+        g_stack_fsz = ops[i].operand[1].val;
+        ops[i].flags |= OPF_RMD;
+        esp_sub = 1;
         break;
       }
     }
 
     found = 0;
-    if (g_sp_frame)
+    if (ecx_push || esp_sub)
     {
-      g_stack_fsz = ops[i].operand[1].val;
-      ops[i].flags |= OPF_RMD;
+      g_sp_frame = 1;
 
       i++;
       do {
@@ -3031,14 +3222,31 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
           j--;
         }
 
-        if (ops[j].op != OP_ADD
-            || !IS(opr_name(&ops[j], 0), "esp")
-            || ops[j].operand[1].type != OPT_CONST
-            || ops[j].operand[1].val != g_stack_fsz)
-          ferr(&ops[j], "'add esp' expected\n");
-        ops[j].flags |= OPF_RMD;
+        if (ecx_push > 0) {
+          for (l = 0; l < ecx_push; l++) {
+            if (ops[j].op != OP_POP
+              || !IS(opr_name(&ops[j], 0), "ecx"))
+            {
+              ferr(&ops[j], "'pop ecx' expected\n");
+            }
+            ops[j].flags |= OPF_RMD;
+            j--;
+          }
+
+          found = 1;
+        }
+
+        if (esp_sub) {
+          if (ops[j].op != OP_ADD
+              || !IS(opr_name(&ops[j], 0), "esp")
+              || ops[j].operand[1].type != OPT_CONST
+              || ops[j].operand[1].val != g_stack_fsz)
+            ferr(&ops[j], "'add esp' expected\n");
+          ops[j].flags |= OPF_RMD;
+
+          found = 1;
+        }
 
-        found = 1;
         i++;
       } while (i < opcnt);
     }
@@ -3297,7 +3505,7 @@ tailcall:
         regmask_save |= 1 << reg;
     }
 
-    if (po->op == OP_PUSH && po->argnum == 0
+    if (po->op == OP_PUSH && po->p_argnum == 0
       && !(po->flags & OPF_RSAVE) && !g_func_pp->is_userstack)
     {
       if (po->operand[0].type == OPT_REG)
@@ -3453,7 +3661,7 @@ tailcall:
           tmp_op = pp->arg[arg].datap;
           if (tmp_op == NULL)
             ferr(po, "parsed_op missing for arg%d\n", arg);
-          if (tmp_op->argnum == 0 && tmp_op->operand[0].type == OPT_REG)
+          if (tmp_op->p_argnum == 0 && tmp_op->operand[0].type == OPT_REG)
             regmask_stack |= 1 << tmp_op->operand[0].reg;
         }
 
@@ -3718,7 +3926,7 @@ tailcall:
 
   regmask_now = regmask & ~regmask_arg;
   regmask_now &= ~(1 << xSP);
-  if (regmask_now) {
+  if (regmask_now & 0x00ff) {
     for (reg = 0; reg < 8; reg++) {
       if (regmask_now & (1 << reg)) {
         fprintf(fout, "  u32 %s", regs_r32[reg]);
@@ -3729,6 +3937,17 @@ tailcall:
       }
     }
   }
+  if (regmask_now & 0xff00) {
+    for (reg = 8; reg < 16; reg++) {
+      if (regmask_now & (1 << reg)) {
+        fprintf(fout, "  mmxr %s", regs_r32[reg]);
+        if (regmask_init & (1 << reg))
+          fprintf(fout, " = { 0, }");
+        fprintf(fout, ";\n");
+        had_decl = 1;
+      }
+    }
+  }
 
   if (regmask_save) {
     for (reg = 0; reg < 8; reg++) {
@@ -4141,6 +4360,21 @@ tailcall:
         delayed_flag_op = NULL;
         break;
 
+      case OP_SHRD:
+        assert_operand_cnt(3);
+        propagate_lmod(po, &po->operand[0], &po->operand[1]);
+        l = lmod_bytes(po, po->operand[0].lmod) * 8;
+        out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]);
+        out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[1]);
+        out_src_opr_u32(buf3, sizeof(buf3), po, &po->operand[2]);
+        fprintf(fout, "  %s >>= %s; %s |= %s << (%d - %s);",
+          buf1, buf3, buf1, buf2, l, buf3);
+        strcpy(g_comment, "shrd");
+        output_std_flags(fout, po, &pfomask, buf1);
+        last_arith_dst = &po->operand[0];
+        delayed_flag_op = NULL;
+        break;
+
       case OP_ROL:
       case OP_ROR:
         assert_operand_cnt(2);
@@ -4245,11 +4479,18 @@ tailcall:
       case OP_SUB:
         assert_operand_cnt(2);
         propagate_lmod(po, &po->operand[0], &po->operand[1]);
-        if (pfomask & (1 << PFO_C)) {
-          fprintf(fout, "  cond_c = %s < %s;\n",
-            out_src_opr_u32(buf1, sizeof(buf1), po, &po->operand[0]),
-            out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[1]));
-          pfomask &= ~(1 << PFO_C);
+        if (pfomask & ~((1 << PFO_Z) | (1 << PFO_S))) {
+          for (j = 0; j <= PFO_LE; j++) {
+            if (!(pfomask & (1 << j)))
+              continue;
+            if (j == PFO_Z || j == PFO_S)
+              continue;
+
+            out_cmp_for_cc(buf1, sizeof(buf1), po, j, 0);
+            fprintf(fout, "  cond_%s = %s;\n",
+              parsed_flag_op_names[j], buf1);
+            pfomask &= ~(1 << j);
+          }
         }
         goto dualop_arith;
 
@@ -4287,8 +4528,27 @@ tailcall:
         strcat(g_comment, "bsf");
         break;
 
-      case OP_INC:
       case OP_DEC:
+        if (pfomask & ~(PFOB_S | PFOB_S | PFOB_C)) {
+          for (j = 0; j <= PFO_LE; j++) {
+            if (!(pfomask & (1 << j)))
+              continue;
+            if (j == PFO_Z || j == PFO_S || j == PFO_C)
+              continue;
+
+            out_cmp_for_cc(buf1, sizeof(buf1), po, j, 0);
+            fprintf(fout, "  cond_%s = %s;\n",
+              parsed_flag_op_names[j], buf1);
+            pfomask &= ~(1 << j);
+          }
+        }
+        // fallthrough
+
+      case OP_INC:
+        if (pfomask & (1 << PFO_C))
+          // carry is unaffected by inc/dec.. wtf?
+          ferr(po, "carry propagation needed\n");
+
         out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]);
         if (po->operand[0].type == OPT_REG) {
           strcpy(buf2, po->op == OP_INC ? "++" : "--");
@@ -4537,8 +4797,15 @@ tailcall:
             tmp_op = pp->arg[arg].datap;
             if (tmp_op == NULL)
               ferr(po, "parsed_op missing for arg%d\n", arg);
-            if (tmp_op->argnum != 0) {
-              fprintf(fout, "%ss_a%d", cast, tmp_op->argnum);
+
+            if (tmp_op->flags & OPF_VAPUSH) {
+              fprintf(fout, "ap");
+            }
+            else if (tmp_op->p_argpass != 0) {
+              fprintf(fout, "a%d", tmp_op->p_argpass);
+            }
+            else if (tmp_op->p_argnum != 0) {
+              fprintf(fout, "%ss_a%d", cast, tmp_op->p_argnum);
             }
             else {
               fprintf(fout, "%s",
@@ -4626,9 +4893,9 @@ tailcall:
 
       case OP_PUSH:
         out_src_opr_u32(buf1, sizeof(buf1), po, &po->operand[0]);
-        if (po->argnum != 0) {
+        if (po->p_argnum != 0) {
           // special case - saved func arg
-          fprintf(fout, "  s_a%d = %s;", po->argnum, buf1);
+          fprintf(fout, "  s_a%d = %s;", po->p_argnum, buf1);
           break;
         }
         else if (po->flags & OPF_RSAVE) {
@@ -4673,6 +4940,11 @@ tailcall:
         no_output = 1;
         break;
 
+      // mmx
+      case OP_EMMS:
+        strcpy(g_comment, "(emms)");
+        break;
+
       default:
         no_output = 1;
         ferr(po, "unhandled op type %d, flags %x\n",
@@ -5372,6 +5644,8 @@ do_pending_endp:
         g_eqs[g_eqcnt].lmod = OPLM_WORD;
       else if (IS(words[2], "byte"))
         g_eqs[g_eqcnt].lmod = OPLM_BYTE;
+      else if (IS(words[2], "qword"))
+        g_eqs[g_eqcnt].lmod = OPLM_QWORD;
       else
         aerr("bad lmod: '%s'\n", words[2]);