translate: push/pop corner cases
[ia32rtools.git] / tools / translate.c
index 1fa3d95..e03e3f0 100644 (file)
@@ -42,7 +42,7 @@ enum op_flags {
   OPF_DATA   = (1 << 1), /* data processing - writes to dst opr */
   OPF_FLAGS  = (1 << 2), /* sets flags */
   OPF_JMP    = (1 << 3), /* branch, call */
-  OPF_CJMP   = (1 << 4), /* cond. branch (cc or jecxz) */
+  OPF_CJMP   = (1 << 4), /* cond. branch (cc or jecxz/loop) */
   OPF_CC     = (1 << 5), /* uses flags */
   OPF_TAIL   = (1 << 6), /* ret or tail call */
   OPF_RSAVE  = (1 << 7), /* push/pop is local reg save/load */
@@ -73,6 +73,7 @@ enum op_op {
        OP_MOVSX,
        OP_XCHG,
        OP_NOT,
+       OP_XLAT,
        OP_CDQ,
        OP_LODS,
        OP_STOS,
@@ -90,6 +91,7 @@ enum op_op {
        OP_SHL,
        OP_SHR,
        OP_SAR,
+       OP_SHLD,
        OP_SHRD,
        OP_ROL,
        OP_ROR,
@@ -110,6 +112,7 @@ enum op_op {
        OP_CALL,
        OP_JMP,
        OP_JECXZ,
+       OP_LOOP,
        OP_JCC,
        OP_SCC,
        // x87
@@ -137,6 +140,8 @@ enum opr_lenmod {
        OPLM_QWORD,
 };
 
+#define MAX_EXITS 128
+
 #define MAX_OPERANDS 3
 #define NAMELEN 112
 
@@ -243,8 +248,8 @@ static int g_quiet_pp;
 static int g_header_mode;
 
 #define ferr(op_, fmt, ...) do { \
-  printf("%s:%d: error: [%s] '%s': " fmt, asmfn, (op_)->asmln, g_func, \
-    dump_op(op_), ##__VA_ARGS__); \
+  printf("%s:%d: error %u: [%s] '%s': " fmt, asmfn, (op_)->asmln, \
+    __LINE__, g_func, dump_op(op_), ##__VA_ARGS__); \
   fcloseall(); \
   exit(1); \
 } while (0)
@@ -253,8 +258,7 @@ static int g_header_mode;
     dump_op(op_), ##__VA_ARGS__)
 
 #define ferr_assert(op_, cond) do { \
-  if (!(cond)) ferr(op_, "assertion '%s' failed on ln :%d\n", #cond, \
-                    __LINE__); \
+  if (!(cond)) ferr(op_, "assertion '%s' failed\n", #cond); \
 } while (0)
 
 const char *regs_r32[] = {
@@ -509,19 +513,19 @@ static const char *parse_stack_el(const char *name, char *extra_reg,
 
 static int guess_lmod_from_name(struct parsed_opr *opr)
 {
-  if (!strncmp(opr->name, "dword_", 6)) {
+  if (IS_START(opr->name, "dword_") || IS_START(opr->name, "off_")) {
     opr->lmod = OPLM_DWORD;
     return 1;
   }
-  if (!strncmp(opr->name, "word_", 5)) {
+  if (IS_START(opr->name, "word_")) {
     opr->lmod = OPLM_WORD;
     return 1;
   }
-  if (!strncmp(opr->name, "byte_", 5)) {
+  if (IS_START(opr->name, "byte_")) {
     opr->lmod = OPLM_BYTE;
     return 1;
   }
-  if (!strncmp(opr->name, "qword_", 6)) {
+  if (IS_START(opr->name, "qword_")) {
     opr->lmod = OPLM_QWORD;
     return 1;
   }
@@ -843,6 +847,7 @@ static const struct {
   { "movsx",OP_MOVSX,  2, 2, OPF_DATA },
   { "xchg", OP_XCHG,   2, 2, OPF_DATA },
   { "not",  OP_NOT,    1, 1, OPF_DATA },
+  { "xlat", OP_XLAT,   0, 0, OPF_DATA },
   { "cdq",  OP_CDQ,    0, 0, OPF_DATA },
   { "lodsb",OP_LODS,   0, 0, OPF_DATA },
   { "lodsw",OP_LODS,   0, 0, OPF_DATA },
@@ -870,6 +875,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 },
+  { "shld", OP_SHLD,   3, 3, 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 },
@@ -891,6 +897,7 @@ static const struct {
   { "call", OP_CALL,   1, 1, OPF_JMP|OPF_DATA|OPF_FLAGS },
   { "jmp",  OP_JMP,    1, 1, OPF_JMP },
   { "jecxz",OP_JECXZ,  1, 1, OPF_JMP|OPF_CJMP },
+  { "loop", OP_LOOP,   1, 1, OPF_JMP|OPF_CJMP|OPF_DATA },
   { "jo",   OP_JCC,    1, 1, OPF_CJMP_CC, PFO_O,  0 }, // 70 OF=1
   { "jno",  OP_JCC,    1, 1, OPF_CJMP_CC, PFO_O,  1 }, // 71 OF=0
   { "jc",   OP_JCC,    1, 1, OPF_CJMP_CC, PFO_C,  0 }, // 72 CF=1
@@ -1072,6 +1079,13 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
     break;
 
   // ops with implicit argumets
+  case OP_XLAT:
+    op->operand_cnt = 2;
+    setup_reg_opr(&op->operand[0], xAX, OPLM_BYTE, &op->regmask_src);
+    op->regmask_dst = op->regmask_src;
+    setup_reg_opr(&op->operand[1], xDX, OPLM_DWORD, &op->regmask_src);
+    break;
+
   case OP_CDQ:
     op->operand_cnt = 2;
     setup_reg_opr(&op->operand[0], xDX, OPLM_DWORD, &op->regmask_dst);
@@ -1115,12 +1129,15 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
     op->regmask_dst = op->regmask_src;
     break;
 
+  case OP_LOOP:
+    op->regmask_dst = 1 << xCX;
+    // fallthrough
   case OP_JECXZ:
-    op->operand_cnt = 1;
+    op->operand_cnt = 2;
     op->regmask_src = 1 << xCX;
-    op->operand[0].type = OPT_REG;
-    op->operand[0].reg = xCX;
-    op->operand[0].lmod = OPLM_DWORD;
+    op->operand[1].type = OPT_REG;
+    op->operand[1].reg = xCX;
+    op->operand[1].lmod = OPLM_DWORD;
     break;
 
   case OP_IMUL:
@@ -1208,6 +1225,17 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
   default:
     break;
   }
+
+  if (op->operand[0].type == OPT_REG
+   && op->operand[0].lmod == OPLM_DWORD
+   && op->operand[1].type == OPT_CONST)
+  {
+    if ((op->op == OP_AND && op->operand[1].val == 0)
+     || (op->op == OP_OR && op->operand[1].val == ~0))
+    {
+      op->regmask_src = 0;
+    }
+  }
 }
 
 static const char *op_name(struct parsed_op *po)
@@ -2146,11 +2174,14 @@ static void op_set_clear_flag(struct parsed_op *po,
   if ((i) < 0) \
     ferr(po, "bad " #i ": %d\n", i)
 
-static int scan_for_pop(int i, int opcnt, const char *reg,
-  int magic, int depth, int *maxdepth, int do_flags)
+// note: this skips over calls and rm'd stuff assuming they're handled
+// so it's intended to use at one of final passes
+static int scan_for_pop(int i, int opcnt, int magic, int reg,
+  int depth, int *maxdepth, int do_flags)
 {
   const struct parsed_proto *pp;
   struct parsed_op *po;
+  int relevant;
   int ret = 0;
   int j;
 
@@ -2170,8 +2201,7 @@ static int scan_for_pop(int i, int opcnt, const char *reg,
       return -1; // deadend
     }
 
-    if ((po->flags & (OPF_RMD|OPF_DONE))
-        || (po->op == OP_PUSH && po->p_argnum != 0)) // arg push
+    if (po->flags & (OPF_RMD|OPF_DONE|OPF_FARG))
       continue;
 
     if ((po->flags & OPF_JMP) && po->op != OP_CALL) {
@@ -2179,7 +2209,7 @@ static int scan_for_pop(int i, int opcnt, const char *reg,
         // jumptable
         for (j = 0; j < po->btj->count; j++) {
           check_i(po, po->btj->d[j].bt_i);
-          ret |= scan_for_pop(po->btj->d[j].bt_i, opcnt, reg, magic,
+          ret |= scan_for_pop(po->btj->d[j].bt_i, opcnt, magic, reg,
                    depth, maxdepth, do_flags);
           if (ret < 0)
             return ret; // dead end
@@ -2189,7 +2219,7 @@ static int scan_for_pop(int i, int opcnt, const char *reg,
 
       check_i(po, po->bt_i);
       if (po->flags & OPF_CJMP) {
-        ret |= scan_for_pop(po->bt_i, opcnt, reg, magic,
+        ret |= scan_for_pop(po->bt_i, opcnt, magic, reg,
                  depth, maxdepth, do_flags);
         if (ret < 0)
           return ret; // dead end
@@ -2200,30 +2230,34 @@ static int scan_for_pop(int i, int opcnt, const char *reg,
       continue;
     }
 
+    relevant = 0;
     if ((po->op == OP_POP || po->op == OP_PUSH)
-        && po->operand[0].type == OPT_REG
-        && IS(po->operand[0].name, reg))
+      && po->operand[0].type == OPT_REG && po->operand[0].reg == reg)
     {
-      if (po->op == OP_PUSH && !(po->flags & OPF_FARGNR)) {
-        depth++;
+      relevant = 1;
+    }
+
+    if (po->op == OP_PUSH) {
+      depth++;
+      if (relevant) {
         if (depth > *maxdepth)
           *maxdepth = depth;
         if (do_flags)
           op_set_clear_flag(po, OPF_RSAVE, OPF_RMD);
       }
-      else if (po->op == OP_POP) {
-        if (depth == 0) {
-          if (do_flags)
-            op_set_clear_flag(po, OPF_RMD, OPF_RSAVE);
-          return 1;
-        }
-        else {
-          depth--;
-          if (depth < 0) // should not happen
-            ferr(po, "fail with depth\n");
-          if (do_flags)
-            op_set_clear_flag(po, OPF_RSAVE, OPF_RMD);
-        }
+    }
+    else if (po->op == OP_POP) {
+      if (depth == 0) {
+        if (relevant && do_flags)
+          op_set_clear_flag(po, OPF_RMD, OPF_RSAVE);
+        return 1;
+      }
+      else {
+        depth--;
+        if (depth < 0) // should not happen
+          ferr(po, "fail with depth\n");
+        if (do_flags)
+          op_set_clear_flag(po, OPF_RSAVE, OPF_RMD);
       }
     }
   }
@@ -2231,79 +2265,317 @@ static int scan_for_pop(int i, int opcnt, const char *reg,
   return ret;
 }
 
-// scan for pop starting from 'ret' op (all paths)
-static int scan_for_pop_ret(int i, int opcnt, const char *reg,
-  int flag_set)
+// scan for 'reg' pop backwards starting from i
+// intended to use for register restore search, so other reg
+// references are considered an error
+static int scan_for_rsave_pop_reg(int i, int magic, int reg, int set_flags)
 {
-  int found = 0;
+  struct parsed_op *po;
+  struct label_ref *lr;
+  int ret = 0;
+
+  ops[i].cc_scratch = magic;
+
+  while (1)
+  {
+    if (g_labels[i] != NULL) {
+      lr = &g_label_refs[i];
+      for (; lr != NULL; lr = lr->next) {
+        check_i(&ops[i], lr->i);
+        ret |= scan_for_rsave_pop_reg(lr->i, magic, reg, set_flags);
+        if (ret < 0)
+          return ret;
+      }
+      if (i > 0 && LAST_OP(i - 1))
+        return ret;
+    }
+
+    i--;
+    if (i < 0)
+      break;
+
+    if (ops[i].cc_scratch == magic)
+      return ret;
+    ops[i].cc_scratch = magic;
+
+    po = &ops[i];
+    if (po->op == OP_POP && po->operand[0].reg == reg) {
+      if (po->flags & (OPF_RMD|OPF_DONE))
+        return -1;
+
+      po->flags |= set_flags;
+      return 1;
+    }
+
+    // this also covers the case where we reach corresponding push
+    if ((po->regmask_dst | po->regmask_src) & (1 << reg))
+      return -1;
+  }
+
+  // nothing interesting on this path
+  return 0;
+}
+
+static void find_reachable_exits(int i, int opcnt, int magic,
+  int *exits, int *exit_count)
+{
+  struct parsed_op *po;
   int j;
 
-  for (; i < opcnt; i++) {
-    if (!(ops[i].flags & OPF_TAIL))
-      continue;
+  for (; i < opcnt; i++)
+  {
+    po = &ops[i];
+    if (po->cc_scratch == magic)
+      return;
+    po->cc_scratch = magic;
+
+    if (po->flags & OPF_TAIL) {
+      ferr_assert(po, *exit_count < MAX_EXITS);
+      exits[*exit_count] = i;
+      (*exit_count)++;
+      return;
+    }
 
-    for (j = i - 1; j >= 0; j--) {
-      if (ops[j].flags & (OPF_RMD|OPF_DONE))
+    if ((po->flags & OPF_JMP) && po->op != OP_CALL) {
+      if (po->flags & OPF_RMD)
         continue;
-      if (ops[j].flags & OPF_JMP)
-        return -1;
 
-      if (ops[j].op == OP_POP && ops[j].datap == NULL
-          && ops[j].operand[0].type == OPT_REG
-          && IS(ops[j].operand[0].name, reg))
-      {
-        found = 1;
-        ops[j].flags |= flag_set;
-        break;
+      if (po->btj != NULL) {
+        for (j = 0; j < po->btj->count; j++) {
+          check_i(po, po->btj->d[j].bt_i);
+          find_reachable_exits(po->btj->d[j].bt_i, opcnt, magic,
+                  exits, exit_count);
+        }
+        return;
       }
 
-      if (g_labels[j] != NULL)
-        return -1;
+      check_i(po, po->bt_i);
+      if (po->flags & OPF_CJMP)
+        find_reachable_exits(po->bt_i, opcnt, magic, exits, exit_count);
+      else
+        i = po->bt_i - 1;
+      continue;
     }
   }
+}
+
+// scan for 'reg' pop backwards starting from exits (all paths)
+static int scan_for_pop_ret(int i, int opcnt, int reg, int set_flags)
+{
+  static int exits[MAX_EXITS];
+  static int exit_count;
+  int j, ret;
 
-  return found ? 0 : -1;
+  if (!set_flags) {
+    exit_count = 0;
+    find_reachable_exits(i, opcnt, i + opcnt * 15, exits,
+      &exit_count);
+    ferr_assert(&ops[i], exit_count > 0);
+  }
+
+  for (j = 0; j < exit_count; j++) {
+    ret = scan_for_rsave_pop_reg(exits[j], i + opcnt * 16 + set_flags,
+            reg, set_flags);
+    if (ret == -1)
+      return -1;
+  }
+
+  return 0;
 }
 
-static void scan_for_pop_const(int i, int opcnt, int *regmask_pp)
+// scan for one or more pop of push <const>
+static int scan_for_pop_const_r(int i, int opcnt, int magic,
+  int push_i, int is_probe)
 {
   struct parsed_op *po;
-  int is_multipath = 0;
+  struct label_ref *lr;
+  int ret = 0;
   int j;
 
-  for (j = i + 1; j < opcnt; j++) {
-    po = &ops[j];
+  for (; i < opcnt; i++)
+  {
+    po = &ops[i];
+    if (po->cc_scratch == magic)
+      return ret; // already checked
+    po->cc_scratch = magic;
+
+    if (po->flags & OPF_JMP) {
+      if (po->flags & OPF_RMD)
+        continue;
+      if (po->op == OP_CALL)
+        return -1;
+
+      if (po->btj != NULL) {
+        for (j = 0; j < po->btj->count; j++) {
+          check_i(po, po->btj->d[j].bt_i);
+          ret |= scan_for_pop_const_r(po->btj->d[j].bt_i, opcnt, magic,
+                  push_i, is_probe);
+          if (ret < 0)
+            return ret;
+        }
+        return ret;
+      }
 
-    if (po->op == OP_JMP && po->btj == NULL) {
-      ferr_assert(po, po->bt_i >= 0);
-      j = po->bt_i - 1;
+      check_i(po, po->bt_i);
+      if (po->flags & OPF_CJMP) {
+        ret |= scan_for_pop_const_r(po->bt_i, opcnt, magic, push_i,
+                 is_probe);
+        if (ret < 0)
+          return ret;
+      }
+      else {
+        i = po->bt_i - 1;
+      }
       continue;
     }
 
-    if ((po->flags & (OPF_JMP|OPF_TAIL|OPF_RSAVE))
-      || po->op == OP_PUSH)
+    if ((po->flags & (OPF_TAIL|OPF_RSAVE)) || po->op == OP_PUSH)
+      return -1;
+
+    if (g_labels[i] != NULL) {
+      // all refs must be visited
+      lr = &g_label_refs[i];
+      for (; lr != NULL; lr = lr->next) {
+        check_i(po, lr->i);
+        if (ops[lr->i].cc_scratch != magic)
+          return -1;
+      }
+      if (i > 0 && !LAST_OP(i - 1) && ops[i - 1].cc_scratch != magic)
+        return -1;
+    }
+
+    if (po->op == OP_POP)
     {
-      break;
+      if (po->flags & (OPF_RMD|OPF_DONE))
+        return -1;
+
+      if (!is_probe) {
+        po->flags |= OPF_DONE;
+        po->datap = &ops[push_i];
+      }
+      return 1;
     }
+  }
 
-    if (g_labels[j] != NULL)
-      is_multipath = 1;
+  return -1;
+}
 
-    if (po->op == OP_POP && !(po->flags & OPF_RMD))
-    {
-      is_multipath |= !!(po->flags & OPF_PPUSH);
-      if (is_multipath) {
-        ops[i].flags |= OPF_PPUSH | OPF_DONE;
-        ops[i].datap = po;
-        po->flags |= OPF_PPUSH | OPF_DONE;
-        *regmask_pp |= 1 << po->operand[0].reg;
+static void scan_for_pop_const(int i, int opcnt, int magic)
+{
+  int ret;
+
+  ret = scan_for_pop_const_r(i + 1, opcnt, magic, i, 1);
+  if (ret == 1) {
+    ops[i].flags |= OPF_RMD | OPF_DONE;
+    scan_for_pop_const_r(i + 1, opcnt, magic + 1, i, 0);
+  }
+}
+
+// check if all branch targets within a marked path are also marked
+// note: the path checked must not be empty or end with a branch
+static int check_path_branches(int opcnt, int magic)
+{
+  struct parsed_op *po;
+  int i, j;
+
+  for (i = 0; i < opcnt; i++) {
+    po = &ops[i];
+    if (po->cc_scratch != magic)
+      continue;
+
+    if (po->flags & OPF_JMP) {
+      if ((po->flags & OPF_RMD) || po->op == OP_CALL)
+        continue;
+
+      if (po->btj != NULL) {
+        for (j = 0; j < po->btj->count; j++) {
+          check_i(po, po->btj->d[j].bt_i);
+          if (ops[po->btj->d[j].bt_i].cc_scratch != magic)
+            return 0;
+        }
       }
-      else {
-        ops[i].flags |= OPF_RMD | OPF_DONE;
-        po->flags |= OPF_DONE;
-        po->datap = &ops[i];
+
+      check_i(po, po->bt_i);
+      if (ops[po->bt_i].cc_scratch != magic)
+        return 0;
+      if ((po->flags & OPF_CJMP) && ops[i + 1].cc_scratch != magic)
+        return 0;
+    }
+  }
+
+  return 1;
+}
+
+// scan for multiple pushes for given pop
+static int scan_pushes_for_pop_r(int i, int magic, int pop_i,
+  int is_probe)
+{
+  int reg = ops[pop_i].operand[0].reg;
+  struct parsed_op *po;
+  struct label_ref *lr;
+  int ret = 0;
+
+  ops[i].cc_scratch = magic;
+
+  while (1)
+  {
+    if (g_labels[i] != NULL) {
+      lr = &g_label_refs[i];
+      for (; lr != NULL; lr = lr->next) {
+        check_i(&ops[i], lr->i);
+        ret |= scan_pushes_for_pop_r(lr->i, magic, pop_i, is_probe);
+        if (ret < 0)
+          return ret;
       }
+      if (i > 0 && LAST_OP(i - 1))
+        return ret;
+    }
+
+    i--;
+    if (i < 0)
       break;
+
+    if (ops[i].cc_scratch == magic)
+      return ret;
+    ops[i].cc_scratch = magic;
+
+    po = &ops[i];
+    if (po->op == OP_CALL)
+      return -1;
+    if ((po->flags & (OPF_TAIL|OPF_RSAVE)) || po->op == OP_POP)
+      return -1;
+
+    if (po->op == OP_PUSH)
+    {
+      if (po->datap != NULL)
+        return -1;
+      if (po->operand[0].type == OPT_REG && po->operand[0].reg == reg)
+        // leave this case for reg save/restore handlers
+        return -1;
+
+      if (!is_probe) {
+        po->flags |= OPF_PPUSH | OPF_DONE;
+        po->datap = &ops[pop_i];
+      }
+      return 1;
+    }
+  }
+
+  return -1;
+}
+
+static void scan_pushes_for_pop(int i, int opcnt, int *regmask_pp)
+{
+  int magic = i + opcnt * 14;
+  int ret;
+
+  ret = scan_pushes_for_pop_r(i, magic, i, 1);
+  if (ret == 1) {
+    ret = check_path_branches(opcnt, magic);
+    if (ret == 1) {
+      ops[i].flags |= OPF_PPUSH | OPF_DONE;
+      *regmask_pp |= 1 << ops[i].operand[0].reg;
+      scan_pushes_for_pop_r(i, magic + 1, i, 0);
     }
   }
 }
@@ -2493,8 +2765,9 @@ static int scan_for_flag_set(int i, int magic, int *branched,
 
   while (i >= 0) {
     if (ops[i].cc_scratch == magic) {
-      ferr(&ops[i], "%s looped\n", __func__);
-      return -1;
+      // is this a problem?
+      //ferr(&ops[i], "%s looped\n", __func__);
+      return 0;
     }
     ops[i].cc_scratch = magic;
 
@@ -2607,10 +2880,16 @@ static void patch_esp_adjust(struct parsed_op *po, int adj)
 static int scan_for_esp_adjust(int i, int opcnt,
   int adj_expect, int *adj, int *is_multipath, int do_update)
 {
+  int adj_expect_unknown = 0;
   struct parsed_op *po;
   int first_pop = -1;
+  int adj_best = 0;
 
   *adj = *is_multipath = 0;
+  if (adj_expect < 0) {
+    adj_expect_unknown = 1;
+    adj_expect = 32 * 4; // enough?
+  }
 
   for (; i < opcnt && *adj < adj_expect; i++) {
     if (g_labels[i] != NULL)
@@ -2654,6 +2933,8 @@ static int scan_for_esp_adjust(int i, int opcnt,
       }
 
       *adj += lmod_bytes(po, po->operand[0].lmod);
+      if (*adj > adj_best)
+        adj_best = *adj;
     }
     else if (po->flags & (OPF_JMP|OPF_TAIL)) {
       if (po->op == OP_JMP && po->btj == NULL) {
@@ -2668,12 +2949,15 @@ static int scan_for_esp_adjust(int i, int opcnt,
         break;
       if (po->pp != NULL && po->pp->is_stdcall)
         break;
+      if (adj_expect_unknown && first_pop >= 0)
+        break;
       // assume it's another cdecl call
     }
   }
 
   if (first_pop >= 0) {
-    // probably 'pop ecx' was used..
+    // probably only 'pop ecx' was used
+    *adj = adj_best;
     return first_pop;
   }
 
@@ -2825,7 +3109,7 @@ static void scan_for_call_type(int i, const struct parsed_opr *opr,
 
   if (i < 0) {
     // reached the top - can only be an arg-reg
-    if (opr->type != OPT_REG)
+    if (opr->type != OPT_REG || g_func_pp == NULL)
       return;
 
     for (i = 0; i < g_func_pp->argc; i++) {
@@ -3103,22 +3387,25 @@ static void scan_prologue_epilogue(int opcnt)
         ferr(&ops[j], "'pop ebp' expected\n");
 
       if (g_stack_fsz != 0) {
-        if (ops[j - 1].op == OP_MOV
+        if (ops[j].op == OP_LEAVE)
+          j--;
+        else if (ops[j].op == OP_POP
+            && ops[j - 1].op == OP_MOV
             && IS(opr_name(&ops[j - 1], 0), "esp")
             && IS(opr_name(&ops[j - 1], 1), "ebp"))
         {
           ops[j - 1].flags |= OPF_RMD | OPF_DONE;
+          j -= 2;
         }
-        else if (ops[j].op != OP_LEAVE
-          && !(g_ida_func_attr & IDAFA_NORETURN))
+        else if (!(g_ida_func_attr & IDAFA_NORETURN))
         {
-          ferr(&ops[j - 1], "esp restore expected\n");
+          ferr(&ops[j], "esp restore expected\n");
         }
 
-        if (ecx_push && ops[j - 2].op == OP_POP
-          && IS(opr_name(&ops[j - 2], 0), "ecx"))
+        if (ecx_push && j >= 0 && ops[j].op == OP_POP
+          && IS(opr_name(&ops[j], 0), "ecx"))
         {
-          ferr(&ops[j - 2], "unexpected ecx pop\n");
+          ferr(&ops[j], "unexpected ecx pop\n");
         }
       }
 
@@ -3456,7 +3743,7 @@ static struct parsed_proto *process_call_early(int i, int opcnt,
   struct parsed_proto *pp;
   int multipath = 0;
   int adj = 0;
-  int ret;
+  int j, ret;
 
   pp = po->pp;
   if (pp == NULL || pp->is_vararg || pp->argc_reg != 0)
@@ -3473,8 +3760,15 @@ static struct parsed_proto *process_call_early(int i, int opcnt,
       return NULL;
     if (multipath)
       return NULL;
-    if (ops[ret].op == OP_POP && adj != 4)
-      return NULL;
+    if (ops[ret].op == OP_POP) {
+      for (j = 1; j < adj / 4; j++) {
+        if (ops[ret + j].op != OP_POP
+          || ops[ret + j].operand[0].reg != xCX)
+        {
+          return NULL;
+        }
+      }
+    }
   }
 
   *adj_i = ret;
@@ -3515,13 +3809,13 @@ static struct parsed_proto *process_call(int i, int opcnt)
           && ops[call_i].operand[1].type == OPT_LABEL)
         {
           // no other source users?
-          ret = resolve_last_ref(i, &po->operand[0], opcnt * 10,
+          ret = resolve_last_ref(i, &po->operand[0], i + opcnt * 10,
                   &ref_i);
           if (ret == 1 && call_i == ref_i) {
             // and nothing uses it after us?
             ref_i = -1;
             ret = find_next_read(i + 1, opcnt, &po->operand[0],
-                    opcnt * 11, &ref_i);
+                    i + opcnt * 11, &ref_i);
             if (ret != 1)
               // then also don't need the source mov
               ops[call_i].flags |= OPF_RMD;
@@ -3541,7 +3835,7 @@ static struct parsed_proto *process_call(int i, int opcnt)
 
       pp->is_fptr = 1;
       ret = scan_for_esp_adjust(i + 1, opcnt,
-              32*4, &adj, &multipath, 0);
+              -1, &adj, &multipath, 0);
       if (ret < 0 || adj < 0) {
         if (!g_allow_regfunc)
           ferr(po, "non-__cdecl indirect call unhandled yet\n");
@@ -3562,9 +3856,11 @@ static struct parsed_proto *process_call(int i, int opcnt)
   // look for and make use of esp adjust
   multipath = 0;
   ret = -1;
-  if (!pp->is_stdcall && pp->argc_stack > 0)
+  if (!pp->is_stdcall && pp->argc_stack > 0) {
+    int adj_expect = pp->is_vararg ? -1 : pp->argc_stack * 4;
     ret = scan_for_esp_adjust(i + 1, opcnt,
-            pp->argc_stack * 4, &adj, &multipath, 0);
+            adj_expect, &adj, &multipath, 0);
+  }
   if (ret >= 0) {
     if (pp->is_vararg) {
       if (adj / 4 < pp->argc_stack) {
@@ -3764,7 +4060,8 @@ static int collect_call_args_r(struct parsed_op *po, int i,
       if (pp->is_unresolved)
         break;
 
-      ferr(po, "arg collect %d/%d hit esp adjust of %d\n",
+      fnote(po, "(this call)\n");
+      ferr(&ops[j], "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 && !(ops[j].flags & OPF_DONE))
@@ -3772,7 +4069,8 @@ static int collect_call_args_r(struct parsed_op *po, int i,
       if (pp->is_unresolved)
         break;
 
-      ferr(po, "arg collect %d/%d hit pop\n", arg, pp->argc);
+      fnote(po, "(this call)\n");
+      ferr(&ops[j], "arg collect %d/%d hit pop\n", arg, pp->argc);
     }
     else if (ops[j].flags & OPF_CJMP)
     {
@@ -3838,7 +4136,9 @@ 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) {
+      if (!pp->is_unresolved && g_func_pp != NULL
+        && pp->arg[arg].type.is_va_list)
+      {
         k = -1;
         ret = resolve_origin(j, &ops[j].operand[0],
                 magic + 1, &k, NULL);
@@ -4164,11 +4464,12 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
       if (pp != NULL) {
         if (j >= 0) {
           // commit esp adjust
-          ops[j].flags |= OPF_RMD;
           if (ops[j].op != OP_POP)
             patch_esp_adjust(&ops[j], pp->argc_stack * 4);
-          else
-            ops[j].flags |= OPF_DONE;
+          else {
+            for (l = 0; l < pp->argc_stack; l++)
+              ops[j + l].flags |= OPF_DONE | OPF_RMD;
+          }
         }
 
         if (strstr(pp->ret_type.name, "int64"))
@@ -4203,7 +4504,11 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
     }
     else if (po->op == OP_PUSH && !(po->flags & OPF_FARG)
       && !(po->flags & OPF_RSAVE) && po->operand[0].type == OPT_CONST)
-        scan_for_pop_const(i, opcnt, &regmask_pp);
+    {
+      scan_for_pop_const(i, opcnt, i + opcnt * 12);
+    }
+    else if (po->op == OP_POP)
+      scan_pushes_for_pop(i, opcnt, &regmask_pp);
   }
 
   // pass6:
@@ -4219,15 +4524,6 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
     if (po->flags & (OPF_RMD|OPF_DONE))
       continue;
 
-    if (po->op == OP_PUSH && (po->flags & OPF_RSAVE)) {
-      reg = po->operand[0].reg;
-      if (!(regmask & (1 << reg)))
-        // not a reg save after all, rerun scan_for_pop
-        po->flags &= ~OPF_RSAVE;
-      else
-        regmask_save |= 1 << reg;
-    }
-
     if (po->op == OP_PUSH && !(po->flags & OPF_FARG)
       && !(po->flags & OPF_RSAVE) && !g_func_pp->is_userstack)
     {
@@ -4237,28 +4533,29 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         if (reg < 0)
           ferr(po, "reg not set for push?\n");
 
+        // FIXME: OPF_RSAVE, regmask_save
         depth = 0;
-        ret = scan_for_pop(i + 1, opcnt,
-                po->operand[0].name, i + opcnt * 3, 0, &depth, 0);
+        ret = scan_for_pop(i + 1, opcnt, i + opcnt * 3,
+                po->operand[0].reg, 0, &depth, 0);
         if (ret == 1) {
-          if (depth > 1)
+          if (depth > 0)
             ferr(po, "too much depth: %d\n", depth);
 
           po->flags |= OPF_RMD;
-          scan_for_pop(i + 1, opcnt, po->operand[0].name,
-            i + opcnt * 4, 0, &depth, 1);
+          scan_for_pop(i + 1, opcnt, i + opcnt * 4,
+            po->operand[0].reg, 0, &depth, 1);
           continue;
         }
-        ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, 0);
+        ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].reg, 0);
         if (ret == 0) {
           arg = OPF_RMD;
           if (regmask & (1 << reg)) {
             if (regmask_save & (1 << reg))
               ferr(po, "%s already saved?\n", po->operand[0].name);
-            arg = OPF_RSAVE;
+            // arg = OPF_RSAVE; // FIXME
           }
           po->flags |= arg;
-          scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, arg);
+          scan_for_pop_ret(i + 1, opcnt, po->operand[0].reg, arg);
           continue;
         }
       }
@@ -4850,6 +5147,14 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         fprintf(fout, "  %s = ~%s;", buf1, buf1);
         break;
 
+      case OP_XLAT:
+        assert_operand_cnt(2);
+        out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]);
+        out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[1]);
+        fprintf(fout, "  %s = *(u8 *)(%s + %s);", buf1, buf2, buf1);
+        strcpy(g_comment, "xlat");
+        break;
+
       case OP_CDQ:
         assert_operand_cnt(2);
         fprintf(fout, "  %s = (s32)%s >> 31;",
@@ -4983,22 +5288,20 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
 
       // arithmetic w/flags
       case OP_AND:
-        if (po->operand[1].type == OPT_CONST && !po->operand[1].val) {
-          // deal with complex dst clear
-          assert_operand_cnt(2);
-          fprintf(fout, "  %s = %s;",
-            out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]),
-            out_src_opr(buf2, sizeof(buf2), po, &po->operand[1],
-             default_cast_to(buf3, sizeof(buf3), &po->operand[0]), 0));
-          output_std_flags(fout, po, &pfomask, buf1);
-          last_arith_dst = &po->operand[0];
-          delayed_flag_op = NULL;
-          break;
-        }
-        // fallthrough
+        if (po->operand[1].type == OPT_CONST && !po->operand[1].val)
+          goto dualop_arith_const;
+        propagate_lmod(po, &po->operand[0], &po->operand[1]);
+        goto dualop_arith;
+
       case OP_OR:
         propagate_lmod(po, &po->operand[0], &po->operand[1]);
-        // fallthrough
+        if (po->operand[1].type == OPT_CONST) {
+          j = lmod_bytes(po, po->operand[0].lmod);
+          if (((1ull << j * 8) - 1) == po->operand[1].val)
+            goto dualop_arith_const;
+        }
+        goto dualop_arith;
+
       dualop_arith:
         assert_operand_cnt(2);
         fprintf(fout, "  %s %s= %s;",
@@ -5010,6 +5313,18 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         delayed_flag_op = NULL;
         break;
 
+      dualop_arith_const:
+        // and 0, or ~0 used instead mov
+        assert_operand_cnt(2);
+        fprintf(fout, "  %s = %s;",
+          out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]),
+          out_src_opr(buf2, sizeof(buf2), po, &po->operand[1],
+           default_cast_to(buf3, sizeof(buf3), &po->operand[0]), 0));
+        output_std_flags(fout, po, &pfomask, buf1);
+        last_arith_dst = &po->operand[0];
+        delayed_flag_op = NULL;
+        break;
+
       case OP_SHL:
       case OP_SHR:
         assert_operand_cnt(2);
@@ -5034,8 +5349,11 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
             ferr(po, "TODO\n");
           pfomask &= ~(1 << PFO_C);
         }
-        fprintf(fout, "  %s %s= %s;", buf1, op_to_c(po),
+        fprintf(fout, "  %s %s= %s", buf1, op_to_c(po),
             out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[1]));
+        if (po->operand[1].type != OPT_CONST)
+          fprintf(fout, " & 0x1f");
+        fprintf(fout, ";");
         output_std_flags(fout, po, &pfomask, buf1);
         last_arith_dst = &po->operand[0];
         delayed_flag_op = NULL;
@@ -5052,6 +5370,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         delayed_flag_op = NULL;
         break;
 
+      case OP_SHLD:
       case OP_SHRD:
         assert_operand_cnt(3);
         propagate_lmod(po, &po->operand[0], &po->operand[1]);
@@ -5059,9 +5378,18 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         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");
+        if (po->operand[2].type != OPT_CONST)
+          ferr(po, "TODO: masking\n");
+        if (po->op == OP_SHLD) {
+          fprintf(fout, "  %s <<= %s; %s |= %s >> (%d - %s);",
+            buf1, buf3, buf1, buf2, l, buf3);
+          strcpy(g_comment, "shld");
+        }
+        else {
+          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;
@@ -5370,6 +5698,12 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         strcat(g_comment, "jecxz");
         break;
 
+      case OP_LOOP:
+        fprintf(fout, "  if (--ecx == 0)\n");
+        fprintf(fout, "    goto %s;", po->operand[0].name);
+        strcat(g_comment, "loop");
+        break;
+
       case OP_JMP:
         assert_operand_cnt(1);
         last_arith_dst = NULL;
@@ -5621,7 +5955,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
           break;
         }
         else if (po->flags & OPF_PPUSH) {
-          // push/pop graph
+          // push/pop graph / non-const
           ferr_assert(po, po->datap == NULL);
           fprintf(fout, "  %s = pp_%s;", buf1, buf1);
           break;
@@ -5856,6 +6190,9 @@ static void gen_hdr_dep_pass(int i, int opcnt, unsigned char *cbits,
     po = &ops[i];
 
     if ((po->flags & OPF_JMP) && po->op != OP_CALL) {
+      if (po->flags & OPF_RMD)
+        continue;
+
       if (po->btj != NULL) {
         // jumptable
         for (j = 0; j < po->btj->count; j++) {
@@ -5893,13 +6230,13 @@ static void gen_hdr_dep_pass(int i, int opcnt, unsigned char *cbits,
         continue;
 
       depth = 0;
-      ret = scan_for_pop(i + 1, opcnt,
-              po->operand[0].name, i + opcnt * 2, 0, &depth, 0);
+      ret = scan_for_pop(i + 1, opcnt, i + opcnt * 2,
+              po->operand[0].reg, 0, &depth, 0);
       if (ret == 1) {
         regmask_save |= 1 << reg;
         po->flags |= OPF_RMD;
-        scan_for_pop(i + 1, opcnt,
-          po->operand[0].name, i + opcnt * 3, 0, &depth, 1);
+        scan_for_pop(i + 1, opcnt, i + opcnt * 3,
+          po->operand[0].reg, 0, &depth, 1);
         continue;
       }
     }
@@ -5985,7 +6322,8 @@ static void gen_hdr(const char *funcn, int opcnt)
   int regmask_dep;
   int max_bp_offset = 0;
   int has_ret;
-  int i, j, ret;
+  int i, j, l;
+  int ret;
 
   pp_c = proto_parse(g_fhdr, funcn, 1);
   if (pp_c != NULL)
@@ -6043,7 +6381,7 @@ static void gen_hdr(const char *funcn, int opcnt)
       continue;
 
     if (po->op == OP_PUSH && po->operand[0].type == OPT_CONST)
-      scan_for_pop_const(i, opcnt, &regmask_dummy);
+      scan_for_pop_const(i, opcnt, i + opcnt * 13);
   }
 
   // pass5:
@@ -6067,11 +6405,12 @@ static void gen_hdr(const char *funcn, int opcnt)
       if (pp != NULL) {
         if (j >= 0) {
           // commit esp adjust
-          ops[j].flags |= OPF_RMD;
           if (ops[j].op != OP_POP)
             patch_esp_adjust(&ops[j], pp->argc_stack * 4);
-          else
-            ops[j].flags |= OPF_DONE;
+          else {
+            for (l = 0; l < pp->argc_stack; l++)
+              ops[j + l].flags |= OPF_DONE | OPF_RMD;
+          }
         }
 
         po->flags |= OPF_DONE;
@@ -6088,16 +6427,17 @@ static void gen_hdr(const char *funcn, int opcnt)
     if (po->flags & (OPF_RMD|OPF_DONE))
       continue;
 
-    if (po->op == OP_PUSH && po->operand[0].type == OPT_REG)
+    if (po->op == OP_PUSH && po->operand[0].type == OPT_REG
+      && po->operand[0].reg != xCX)
     {
-      ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, 0);
+      ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].reg, 0);
       if (ret == 0) {
         // regmask_save |= 1 << po->operand[0].reg; // do it later
         po->flags |= OPF_RSAVE | OPF_RMD | OPF_DONE;
-        scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, OPF_RMD);
+        scan_for_pop_ret(i + 1, opcnt, po->operand[0].reg, OPF_RMD);
       }
     }
-    else if (po->op == OP_CALL && !(po->flags & OPF_DONE))
+    else if (po->op == OP_CALL)
     {
       pp = process_call(i, opcnt);
 
@@ -6122,6 +6462,13 @@ static void gen_hdr(const char *funcn, int opcnt)
     if (cbits[i >> 3] & (1 << (i & 7)))
       continue;
 
+    if (g_labels[i] == NULL && i > 0 && ops[i - 1].op == OP_CALL
+      && ops[i - 1].pp != NULL && ops[i - 1].pp->is_osinc)
+    {
+      // the compiler sometimes still generates code after
+      // noreturn OS functions
+      break;
+    }
     if (ops[i].op != OP_NOP)
       ferr(&ops[i], "unreachable code\n");
   }