translate: few more ops and corner cases
[ia32rtools.git] / tools / translate.c
index e9ea443..78ff9d2 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "my_assert.h"
 #include "my_str.h"
+#include "common.h"
 
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
 #define IS(w, y) !strcmp(w, y)
@@ -41,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 */
@@ -72,6 +73,7 @@ enum op_op {
        OP_MOVSX,
        OP_XCHG,
        OP_NOT,
+       OP_XLAT,
        OP_CDQ,
        OP_LODS,
        OP_STOS,
@@ -89,6 +91,7 @@ enum op_op {
        OP_SHL,
        OP_SHR,
        OP_SAR,
+       OP_SHLD,
        OP_SHRD,
        OP_ROL,
        OP_ROR,
@@ -109,6 +112,7 @@ enum op_op {
        OP_CALL,
        OP_JMP,
        OP_JECXZ,
+       OP_LOOP,
        OP_JCC,
        OP_SCC,
        // x87
@@ -242,8 +246,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)
@@ -508,19 +512,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;
   }
@@ -842,6 +846,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 },
@@ -869,6 +874,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 },
@@ -890,6 +896,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
@@ -1071,6 +1078,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);
@@ -1114,12 +1128,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:
@@ -2141,6 +2158,10 @@ static void op_set_clear_flag(struct parsed_op *po,
   || ((ops[_i].flags & (OPF_JMP|OPF_CJMP|OPF_RMD)) == OPF_JMP \
       && ops[_i].op != OP_CALL))
 
+#define check_i(po, i) \
+  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)
 {
@@ -2173,6 +2194,7 @@ static int scan_for_pop(int i, int opcnt, const char *reg,
       if (po->btj != NULL) {
         // 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,
                    depth, maxdepth, do_flags);
           if (ret < 0)
@@ -2181,11 +2203,7 @@ static int scan_for_pop(int i, int opcnt, const char *reg,
         return ret;
       }
 
-      if (po->bt_i < 0) {
-        ferr(po, "dead branch\n");
-        return -1;
-      }
-
+      check_i(po, po->bt_i);
       if (po->flags & OPF_CJMP) {
         ret |= scan_for_pop(po->bt_i, opcnt, reg, magic,
                  depth, maxdepth, do_flags);
@@ -2323,16 +2341,14 @@ static void scan_propagate_df(int i, int opcnt)
     if (po->flags & OPF_JMP) {
       if (po->btj != NULL) {
         // jumptable
-        for (j = 0; j < po->btj->count; j++)
+        for (j = 0; j < po->btj->count; j++) {
+          check_i(po, po->btj->d[j].bt_i);
           scan_propagate_df(po->btj->d[j].bt_i, opcnt);
+        }
         return;
       }
 
-      if (po->bt_i < 0) {
-        ferr(po, "dead branch\n");
-        return;
-      }
-
+      check_i(po, po->bt_i);
       if (po->flags & OPF_CJMP)
         scan_propagate_df(po->bt_i, opcnt);
       else
@@ -2352,13 +2368,57 @@ static void scan_propagate_df(int i, int opcnt)
   ferr(po, "missing DF clear?\n");
 }
 
+// is operand 'opr' referenced by parsed_op 'po'?
+static int is_opr_referenced(const struct parsed_opr *opr,
+  const struct parsed_op *po)
+{
+  int i, mask;
+
+  if (opr->type == OPT_REG) {
+    mask = po->regmask_dst | po->regmask_src;
+    if (po->op == OP_CALL)
+      mask |= (1 << xAX) | (1 << xCX) | (1 << xDX);
+    if ((1 << opr->reg) & mask)
+      return 1;
+    else
+      return 0;
+  }
+
+  for (i = 0; i < po->operand_cnt; i++)
+    if (IS(po->operand[0].name, opr->name))
+      return 1;
+
+  return 0;
+}
+
+// is operand 'opr' read by parsed_op 'po'?
+static int is_opr_read(const struct parsed_opr *opr,
+  const struct parsed_op *po)
+{
+  int mask;
+
+  if (opr->type == OPT_REG) {
+    mask = po->regmask_src;
+    if (po->op == OP_CALL)
+      // assume worst case
+      mask |= (1 << xAX) | (1 << xCX) | (1 << xDX);
+    if ((1 << opr->reg) & mask)
+      return 1;
+    else
+      return 0;
+  }
+
+  // yes I'm lazy
+  return 0;
+}
+
 // is operand 'opr' modified by parsed_op 'po'?
 static int is_opr_modified(const struct parsed_opr *opr,
   const struct parsed_op *po)
 {
   int mask;
 
-  if ((po->flags & OPF_RMD) || !(po->flags & OPF_DATA))
+  if (!(po->flags & OPF_DATA))
     return 0;
 
   if (opr->type == OPT_REG) {
@@ -2441,10 +2501,6 @@ static int scan_for_mod_opr0(struct parsed_op *po_test,
   return -1;
 }
 
-#define check_i(po, i) \
-  if ((i) < 0) \
-    ferr(po, "bad " #i ": %d\n", i)
-
 static int scan_for_flag_set(int i, int magic, int *branched,
   int *setters, int *setter_cnt)
 {
@@ -2453,8 +2509,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;
 
@@ -2567,10 +2624,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)
@@ -2614,6 +2677,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) {
@@ -2628,12 +2693,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;
   }
 
@@ -2738,7 +2806,8 @@ static const struct parsed_proto *try_recover_pp(
 }
 
 static void scan_for_call_type(int i, const struct parsed_opr *opr,
-  int magic, const struct parsed_proto **pp_found, int *multi)
+  int magic, const struct parsed_proto **pp_found, int *pp_i,
+  int *multi)
 {
   const struct parsed_proto *pp = NULL;
   struct parsed_op *po;
@@ -2751,7 +2820,7 @@ static void scan_for_call_type(int i, const struct parsed_opr *opr,
       lr = &g_label_refs[i];
       for (; lr != NULL; lr = lr->next) {
         check_i(&ops[i], lr->i);
-        scan_for_call_type(lr->i, opr, magic, pp_found, multi);
+        scan_for_call_type(lr->i, opr, magic, pp_found, pp_i, multi);
       }
       if (i > 0 && LAST_OP(i - 1))
         return;
@@ -2784,7 +2853,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++) {
@@ -2816,24 +2885,176 @@ static void scan_for_call_type(int i, const struct parsed_opr *opr,
     }
     *multi = 1;
   }
-  if (pp != NULL)
+  if (pp != NULL) {
     *pp_found = pp;
+    *pp_i = po - ops;
+  }
 }
 
-// early check for tail call or branch back
-static int is_like_tailjmp(int j)
+static void add_label_ref(struct label_ref *lr, int op_i)
 {
-  if (!(ops[j].flags & OPF_JMP))
-    return 0;
+  struct label_ref *lr_new;
 
-  if (ops[j].op == OP_JMP && !ops[j].operand[0].had_ds)
-    // probably local branch back..
-    return 1;
-  if (ops[j].op == OP_CALL)
-    // probably noreturn call..
-    return 1;
+  if (lr->i == -1) {
+    lr->i = op_i;
+    return;
+  }
 
-  return 0;
+  lr_new = calloc(1, sizeof(*lr_new));
+  lr_new->i = op_i;
+  lr_new->next = lr->next;
+  lr->next = lr_new;
+}
+
+static struct parsed_data *try_resolve_jumptab(int i, int opcnt)
+{
+  struct parsed_op *po = &ops[i];
+  struct parsed_data *pd;
+  char label[NAMELEN], *p;
+  int len, j, l;
+
+  p = strchr(po->operand[0].name, '[');
+  if (p == NULL)
+    return NULL;
+
+  len = p - po->operand[0].name;
+  strncpy(label, po->operand[0].name, len);
+  label[len] = 0;
+
+  for (j = 0, pd = NULL; j < g_func_pd_cnt; j++) {
+    if (IS(g_func_pd[j].label, label)) {
+      pd = &g_func_pd[j];
+      break;
+    }
+  }
+  if (pd == NULL)
+    //ferr(po, "label '%s' not parsed?\n", label);
+    return NULL;
+
+  if (pd->type != OPT_OFFSET)
+    ferr(po, "label '%s' with non-offset data?\n", label);
+
+  // find all labels, link
+  for (j = 0; j < pd->count; j++) {
+    for (l = 0; l < opcnt; l++) {
+      if (g_labels[l] != NULL && IS(g_labels[l], pd->d[j].u.label)) {
+        add_label_ref(&g_label_refs[l], i);
+        pd->d[j].bt_i = l;
+        break;
+      }
+    }
+  }
+
+  return pd;
+}
+
+static void clear_labels(int count)
+{
+  int i;
+
+  for (i = 0; i < count; i++) {
+    if (g_labels[i] != NULL) {
+      free(g_labels[i]);
+      g_labels[i] = NULL;
+    }
+  }
+}
+
+static void resolve_branches_parse_calls(int opcnt)
+{
+  const struct parsed_proto *pp_c;
+  struct parsed_proto *pp;
+  struct parsed_data *pd;
+  struct parsed_op *po;
+  const char *tmpname;
+  int i, l, ret;
+
+  for (i = 0; i < opcnt; i++)
+  {
+    po = &ops[i];
+    po->bt_i = -1;
+    po->btj = NULL;
+
+    if (po->op == OP_CALL) {
+      pp = NULL;
+
+      if (po->operand[0].type == OPT_LABEL) {
+        tmpname = opr_name(po, 0);
+        if (IS_START(tmpname, "loc_"))
+          ferr(po, "call to loc_*\n");
+        pp_c = proto_parse(g_fhdr, tmpname, g_header_mode);
+        if (!g_header_mode && pp_c == NULL)
+          ferr(po, "proto_parse failed for call '%s'\n", tmpname);
+
+        if (pp_c != NULL) {
+          pp = proto_clone(pp_c);
+          my_assert_not(pp, NULL);
+        }
+      }
+      else if (po->datap != NULL) {
+        pp = calloc(1, sizeof(*pp));
+        my_assert_not(pp, NULL);
+
+        ret = parse_protostr(po->datap, pp);
+        if (ret < 0)
+          ferr(po, "bad protostr supplied: %s\n", (char *)po->datap);
+        free(po->datap);
+        po->datap = NULL;
+      }
+
+      if (pp != NULL) {
+        if (pp->is_fptr)
+          check_func_pp(po, pp, "fptr var call");
+        if (pp->is_noreturn)
+          po->flags |= OPF_TAIL;
+      }
+      po->pp = pp;
+      continue;
+    }
+
+    if (!(po->flags & OPF_JMP) || po->op == OP_RET)
+      continue;
+
+    if (po->operand[0].type == OPT_REGMEM) {
+      pd = try_resolve_jumptab(i, opcnt);
+      if (pd == NULL)
+        goto tailcall;
+
+      po->btj = pd;
+      continue;
+    }
+
+    for (l = 0; l < opcnt; l++) {
+      if (g_labels[l] != NULL
+          && IS(po->operand[0].name, g_labels[l]))
+      {
+        if (l == i + 1 && po->op == OP_JMP) {
+          // yet another alignment type..
+          po->flags |= OPF_RMD|OPF_DONE;
+          break;
+        }
+        add_label_ref(&g_label_refs[l], i);
+        po->bt_i = l;
+        break;
+      }
+    }
+
+    if (po->bt_i != -1 || (po->flags & OPF_RMD))
+      continue;
+
+    if (po->operand[0].type == OPT_LABEL)
+      // assume tail call
+      goto tailcall;
+
+    ferr(po, "unhandled branch\n");
+
+tailcall:
+    po->op = OP_CALL;
+    po->flags |= OPF_TAIL;
+    if (i > 0 && ops[i - 1].op == OP_POP)
+      po->flags |= OPF_ATAIL;
+    i--; // reprocess
+  }
 }
 
 static void scan_prologue_epilogue(int opcnt)
@@ -2883,12 +3104,13 @@ static void scan_prologue_epilogue(int opcnt)
     found = 0;
     do {
       for (; i < opcnt; i++)
-        if (ops[i].op == OP_RET)
+        if (ops[i].flags & OPF_TAIL)
           break;
       j = i - 1;
       if (i == opcnt && (ops[j].flags & OPF_JMP)) {
-        if (found && is_like_tailjmp(j))
-            break;
+        if (ops[j].bt_i != -1 || ops[j].btj != NULL)
+          break;
+        i--;
         j--;
       }
 
@@ -2897,26 +3119,37 @@ static void scan_prologue_epilogue(int opcnt)
       {
         ops[j].flags |= OPF_RMD | OPF_DONE;
       }
+      else if (ops[i].op == OP_CALL && ops[i].pp != NULL
+        && ops[i].pp->is_noreturn)
+      {
+        // on noreturn, msvc sometimes cleans stack, sometimes not
+        i++;
+        found = 1;
+        continue;
+      }
       else if (!(g_ida_func_attr & IDAFA_NORETURN))
         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");
         }
       }
 
@@ -2924,6 +3157,8 @@ static void scan_prologue_epilogue(int opcnt)
       i++;
     } while (i < opcnt);
 
+    if (!found)
+      ferr(ops, "missing ebp epilogue\n");
     return;
   }
 
@@ -2989,12 +3224,13 @@ static void scan_prologue_epilogue(int opcnt)
     i++;
     do {
       for (; i < opcnt; i++)
-        if (ops[i].op == OP_RET)
+        if (ops[i].flags & OPF_TAIL)
           break;
       j = i - 1;
       if (i == opcnt && (ops[j].flags & OPF_JMP)) {
-        if (found && is_like_tailjmp(j))
-            break;
+        if (ops[j].bt_i != -1 || ops[j].btj != NULL)
+          break;
+        i--;
         j--;
       }
 
@@ -3035,16 +3271,20 @@ static void scan_prologue_epilogue(int opcnt)
 
       i++;
     } while (i < opcnt);
+
+    if (!found)
+      ferr(ops, "missing esp epilogue\n");
   }
 }
 
 static const struct parsed_proto *resolve_icall(int i, int opcnt,
-  int *multi_src)
+  int *pp_i, int *multi_src)
 {
   const struct parsed_proto *pp = NULL;
   int search_advice = 0;
 
   *multi_src = 0;
+  *pp_i = -1;
 
   switch (ops[i].operand[0].type) {
   case OPT_REGMEM:
@@ -3056,7 +3296,7 @@ static const struct parsed_proto *resolve_icall(int i, int opcnt,
     // fallthrough
   default:
     scan_for_call_type(i, &ops[i].operand[0], i + opcnt * 9, &pp,
-      multi_src);
+      pp_i, multi_src);
     break;
   }
 
@@ -3064,7 +3304,7 @@ static const struct parsed_proto *resolve_icall(int i, int opcnt,
 }
 
 // find an instruction that changed opr before i op
-// *op_i must be set to -1 by caller
+// *op_i must be set to -1 by the caller
 // *entry is set to 1 if one source is determined to be the caller
 // returns 1 if found, *op_i is then set to origin
 static int resolve_origin(int i, const struct parsed_opr *opr,
@@ -3096,7 +3336,7 @@ static int resolve_origin(int i, const struct parsed_opr *opr,
     }
 
     if (ops[i].cc_scratch == magic)
-      return 0;
+      return ret;
     ops[i].cc_scratch = magic;
 
     if (!(ops[i].flags & OPF_DATA))
@@ -3106,16 +3346,121 @@ static int resolve_origin(int i, const struct parsed_opr *opr,
 
     if (*op_i >= 0) {
       if (*op_i == i)
-        return 1;
+        return ret | 1;
+
       // XXX: could check if the other op does the same
       return -1;
     }
 
+    *op_i = i;
+    return ret | 1;
+  }
+}
+
+// find an instruction that previously referenced opr
+// if multiple results are found - fail
+// *op_i must be set to -1 by the caller
+// returns 1 if found, *op_i is then set to referencer insn
+static int resolve_last_ref(int i, const struct parsed_opr *opr,
+  int magic, int *op_i)
+{
+  struct label_ref *lr;
+  int ret = 0;
+
+  if (ops[i].cc_scratch == magic)
+    return 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 |= resolve_last_ref(lr->i, opr, magic, op_i);
+      }
+      if (i > 0 && LAST_OP(i - 1))
+        return ret;
+    }
+
+    i--;
+    if (i < 0)
+      return -1;
+
+    if (ops[i].cc_scratch == magic)
+      return 0;
+    ops[i].cc_scratch = magic;
+
+    if (!is_opr_referenced(opr, &ops[i]))
+      continue;
+
+    if (*op_i >= 0)
+      return -1;
+
     *op_i = i;
     return 1;
   }
 }
 
+// find next instruction that reads opr
+// if multiple results are found - fail
+// *op_i must be set to -1 by the caller
+// returns 1 if found, *op_i is then set to referencer insn
+static int find_next_read(int i, int opcnt,
+  const struct parsed_opr *opr, int magic, int *op_i)
+{
+  struct parsed_op *po;
+  int j, ret = 0;
+
+  for (; i < opcnt; i++)
+  {
+    if (ops[i].cc_scratch == magic)
+      return 0;
+    ops[i].cc_scratch = magic;
+
+    po = &ops[i];
+    if ((po->flags & OPF_JMP) && po->op != OP_CALL) {
+      if (po->btj != NULL) {
+        // jumptable
+        for (j = 0; j < po->btj->count; j++) {
+          check_i(po, po->btj->d[j].bt_i);
+          ret |= find_next_read(po->btj->d[j].bt_i, opcnt, opr,
+                   magic, op_i);
+        }
+        return ret;
+      }
+
+      if (po->flags & OPF_RMD)
+        continue;
+      check_i(po, po->bt_i);
+      if (po->flags & OPF_CJMP) {
+        ret = find_next_read(po->bt_i, opcnt, opr, magic, op_i);
+        if (ret < 0)
+          return ret;
+      }
+
+      i = po->bt_i - 1;
+      continue;
+    }
+
+    if (!is_opr_read(opr, po)) {
+      if (is_opr_modified(opr, po))
+        // it's overwritten
+        return 0;
+      if (po->flags & OPF_TAIL)
+        return 0;
+      continue;
+    }
+
+    if (*op_i >= 0)
+      return -1;
+
+    *op_i = i;
+    return 1;
+  }
+
+  return 0;
+}
+
 static int try_resolve_const(int i, const struct parsed_opr *opr,
   int magic, unsigned int *val)
 {
@@ -3142,7 +3487,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)
@@ -3159,8 +3504,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;
@@ -3173,6 +3525,7 @@ static struct parsed_proto *process_call(int i, int opcnt)
   const struct parsed_proto *pp_c;
   struct parsed_proto *pp;
   const char *tmpname;
+  int call_i = -1, ref_i = -1;
   int adj = 0, multipath = 0;
   int ret, arg;
 
@@ -3181,7 +3534,7 @@ static struct parsed_proto *process_call(int i, int opcnt)
   if (pp == NULL)
   {
     // indirect call
-    pp_c = resolve_icall(i, opcnt, &multipath);
+    pp_c = resolve_icall(i, opcnt, &call_i, &multipath);
     if (pp_c != NULL) {
       if (!pp_c->is_func && !pp_c->is_fptr)
         ferr(po, "call to non-func: %s\n", pp_c->name);
@@ -3195,6 +3548,23 @@ static struct parsed_proto *process_call(int i, int opcnt)
       case OPT_REG:
         // we resolved this call and no longer need the register
         po->regmask_src &= ~(1 << po->operand[0].reg);
+
+        if (!multipath && i != call_i && ops[call_i].op == OP_MOV
+          && ops[call_i].operand[1].type == OPT_LABEL)
+        {
+          // no other source users?
+          ret = resolve_last_ref(i, &po->operand[0], 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);
+            if (ret != 1)
+              // then also don't need the source mov
+              ops[call_i].flags |= OPF_RMD;
+          }
+        }
         break;
       case OPT_REGMEM:
         pp->is_fptr = 1;
@@ -3209,7 +3579,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");
@@ -3230,9 +3600,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) {
@@ -3506,7 +3878,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);
@@ -3643,75 +4017,6 @@ static void pp_insert_reg_arg(struct parsed_proto *pp, const char *reg)
   pp->argc_reg++;
 }
 
-static void add_label_ref(struct label_ref *lr, int op_i)
-{
-  struct label_ref *lr_new;
-
-  if (lr->i == -1) {
-    lr->i = op_i;
-    return;
-  }
-
-  lr_new = calloc(1, sizeof(*lr_new));
-  lr_new->i = op_i;
-  lr_new->next = lr->next;
-  lr->next = lr_new;
-}
-
-static struct parsed_data *try_resolve_jumptab(int i, int opcnt)
-{
-  struct parsed_op *po = &ops[i];
-  struct parsed_data *pd;
-  char label[NAMELEN], *p;
-  int len, j, l;
-
-  p = strchr(po->operand[0].name, '[');
-  if (p == NULL)
-    return NULL;
-
-  len = p - po->operand[0].name;
-  strncpy(label, po->operand[0].name, len);
-  label[len] = 0;
-
-  for (j = 0, pd = NULL; j < g_func_pd_cnt; j++) {
-    if (IS(g_func_pd[j].label, label)) {
-      pd = &g_func_pd[j];
-      break;
-    }
-  }
-  if (pd == NULL)
-    //ferr(po, "label '%s' not parsed?\n", label);
-    return NULL;
-
-  if (pd->type != OPT_OFFSET)
-    ferr(po, "label '%s' with non-offset data?\n", label);
-
-  // find all labels, link
-  for (j = 0; j < pd->count; j++) {
-    for (l = 0; l < opcnt; l++) {
-      if (g_labels[l] != NULL && IS(g_labels[l], pd->d[j].u.label)) {
-        add_label_ref(&g_label_refs[l], i);
-        pd->d[j].bt_i = l;
-        break;
-      }
-    }
-  }
-
-  return pd;
-}
-
-static void clear_labels(int count)
-{
-  int i;
-
-  for (i = 0; i < count; i++) {
-    if (g_labels[i] != NULL) {
-      free(g_labels[i]);
-      g_labels[i] = NULL;
-    }
-  }
-}
-
 static void output_std_flags(FILE *fout, struct parsed_op *po,
   int *pfomask, const char *dst_opr_text)
 {
@@ -3821,153 +4126,69 @@ static char *saved_arg_name(char *buf, size_t buf_size, int grp, int num)
   return buf;
 }
 
-static void gen_x_cleanup(int opcnt);
-
-static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
-{
-  struct parsed_op *po, *delayed_flag_op = NULL, *tmp_op;
-  struct parsed_opr *last_arith_dst = NULL;
-  char buf1[256], buf2[256], buf3[256], cast[64];
-  const struct parsed_proto *pp_c;
-  struct parsed_proto *pp, *pp_tmp;
-  struct parsed_data *pd;
-  const char *tmpname;
-  unsigned int uval;
-  int save_arg_vars[MAX_ARG_GRP] = { 0, };
-  int cond_vars = 0;
-  int need_tmp_var = 0;
-  int need_tmp64 = 0;
-  int had_decl = 0;
-  int label_pending = 0;
-  int regmask_save = 0; // regs saved/restored in this func
-  int regmask_arg = 0;  // regs carrying function args (fastcall, etc)
-  int regmask_now;      // temp
-  int regmask_init = 0; // regs that need zero initialization
-  int regmask_pp = 0;   // regs used in complex push-pop graph
-  int regmask = 0;      // used regs
-  int pfomask = 0;
-  int found = 0;
-  int depth = 0;
-  int no_output;
-  int i, j, l;
-  int arg;
-  int reg;
-  int ret;
-
-  g_bp_frame = g_sp_frame = g_stack_fsz = 0;
-  g_stack_frame_used = 0;
-
-  g_func_pp = proto_parse(fhdr, funcn, 0);
-  if (g_func_pp == NULL)
-    ferr(ops, "proto_parse failed for '%s'\n", funcn);
-
-  regmask_arg = get_pp_arg_regmask(g_func_pp);
-
-  // pass1:
-  // - handle ebp/esp frame, remove ops related to it
-  scan_prologue_epilogue(opcnt);
-
-  // pass2:
-  // - parse calls with labels
-  // - resolve all branches
-  for (i = 0; i < opcnt; i++)
-  {
-    po = &ops[i];
-    po->bt_i = -1;
-    po->btj = NULL;
-
-    if (po->flags & (OPF_RMD|OPF_DONE))
-      continue;
-
-    if (po->op == OP_CALL) {
-      pp = NULL;
-
-      if (po->operand[0].type == OPT_LABEL) {
-        tmpname = opr_name(po, 0);
-        if (IS_START(tmpname, "loc_"))
-          ferr(po, "call to loc_*\n");
-        pp_c = proto_parse(fhdr, tmpname, 0);
-        if (pp_c == NULL)
-          ferr(po, "proto_parse failed for call '%s'\n", tmpname);
-
-        pp = proto_clone(pp_c);
-        my_assert_not(pp, NULL);
-      }
-      else if (po->datap != NULL) {
-        pp = calloc(1, sizeof(*pp));
-        my_assert_not(pp, NULL);
-
-        ret = parse_protostr(po->datap, pp);
-        if (ret < 0)
-          ferr(po, "bad protostr supplied: %s\n", (char *)po->datap);
-        free(po->datap);
-        po->datap = NULL;
-      }
-
-      if (pp != NULL) {
-        if (pp->is_fptr)
-          check_func_pp(po, pp, "fptr var call");
-        if (pp->is_noreturn)
-          po->flags |= OPF_TAIL;
-      }
-      po->pp = pp;
-      continue;
-    }
-
-    if (!(po->flags & OPF_JMP) || po->op == OP_RET)
-      continue;
-
-    if (po->operand[0].type == OPT_REGMEM) {
-      pd = try_resolve_jumptab(i, opcnt);
-      if (pd == NULL)
-        goto tailcall;
+static void gen_x_cleanup(int opcnt);
 
-      po->btj = pd;
-      continue;
-    }
+static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
+{
+  struct parsed_op *po, *delayed_flag_op = NULL, *tmp_op;
+  struct parsed_opr *last_arith_dst = NULL;
+  char buf1[256], buf2[256], buf3[256], cast[64];
+  struct parsed_proto *pp, *pp_tmp;
+  struct parsed_data *pd;
+  unsigned int uval;
+  int save_arg_vars[MAX_ARG_GRP] = { 0, };
+  int cond_vars = 0;
+  int need_tmp_var = 0;
+  int need_tmp64 = 0;
+  int had_decl = 0;
+  int label_pending = 0;
+  int regmask_save = 0; // regs saved/restored in this func
+  int regmask_arg = 0;  // regs carrying function args (fastcall, etc)
+  int regmask_now;      // temp
+  int regmask_init = 0; // regs that need zero initialization
+  int regmask_pp = 0;   // regs used in complex push-pop graph
+  int regmask = 0;      // used regs
+  int pfomask = 0;
+  int found = 0;
+  int depth = 0;
+  int no_output;
+  int i, j, l;
+  int arg;
+  int reg;
+  int ret;
 
-    for (l = 0; l < opcnt; l++) {
-      if (g_labels[l] != NULL
-          && IS(po->operand[0].name, g_labels[l]))
-      {
-        if (l == i + 1 && po->op == OP_JMP) {
-          // yet another alignment type..
-          po->flags |= OPF_RMD|OPF_DONE;
-          break;
-        }
-        add_label_ref(&g_label_refs[l], i);
-        po->bt_i = l;
-        break;
-      }
-    }
+  g_bp_frame = g_sp_frame = g_stack_fsz = 0;
+  g_stack_frame_used = 0;
 
-    if (po->bt_i != -1 || (po->flags & OPF_RMD))
-      continue;
+  g_func_pp = proto_parse(fhdr, funcn, 0);
+  if (g_func_pp == NULL)
+    ferr(ops, "proto_parse failed for '%s'\n", funcn);
 
-    if (po->operand[0].type == OPT_LABEL)
-      // assume tail call
-      goto tailcall;
+  regmask_arg = get_pp_arg_regmask(g_func_pp);
 
-    ferr(po, "unhandled branch\n");
+  // pass1:
+  // - resolve all branches
+  // - parse calls with labels
+  resolve_branches_parse_calls(opcnt);
 
-tailcall:
-    po->op = OP_CALL;
-    po->flags |= OPF_TAIL;
-    if (i > 0 && ops[i - 1].op == OP_POP)
-      po->flags |= OPF_ATAIL;
-    i--; // reprocess
-  }
+  // pass2:
+  // - handle ebp/esp frame, remove ops related to it
+  scan_prologue_epilogue(opcnt);
 
   // pass3:
   // - remove dead labels
-  // - process trivial calls
   for (i = 0; i < opcnt; i++)
   {
     if (g_labels[i] != NULL && g_label_refs[i].i == -1) {
       free(g_labels[i]);
       g_labels[i] = NULL;
     }
+  }
 
+  // pass4:
+  // - process trivial calls
+  for (i = 0; i < opcnt; i++)
+  {
     po = &ops[i];
     if (po->flags & (OPF_RMD|OPF_DONE))
       continue;
@@ -3985,11 +4206,12 @@ tailcall:
       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"))
@@ -4000,7 +4222,7 @@ tailcall:
     }
   }
 
-  // pass4:
+  // pass5:
   // - process calls
   // - handle push <const>/pop pairs
   for (i = 0; i < opcnt; i++)
@@ -4027,7 +4249,7 @@ tailcall:
         scan_for_pop_const(i, opcnt, &regmask_pp);
   }
 
-  // pass5:
+  // pass6:
   // - find POPs for PUSHes, rm both
   // - scan for STD/CLD, propagate DF
   // - scan for all used registers
@@ -4272,7 +4494,7 @@ tailcall:
     }
   }
 
-  // pass6:
+  // pass7:
   // - confirm regmask_save, it might have been reduced
   if (regmask_save != 0)
   {
@@ -4671,6 +4893,14 @@ tailcall:
         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;",
@@ -4804,6 +5034,19 @@ tailcall:
 
       // 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
       case OP_OR:
         propagate_lmod(po, &po->operand[0], &po->operand[1]);
         // fallthrough
@@ -4842,8 +5085,11 @@ tailcall:
             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;
@@ -4860,6 +5106,7 @@ tailcall:
         delayed_flag_op = NULL;
         break;
 
+      case OP_SHLD:
       case OP_SHRD:
         assert_operand_cnt(3);
         propagate_lmod(po, &po->operand[0], &po->operand[1]);
@@ -4867,9 +5114,18 @@ tailcall:
         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;
@@ -5178,6 +5434,12 @@ tailcall:
         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;
@@ -5577,6 +5839,25 @@ static int hg_var_cnt;
 static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
   int count);
 
+struct func_prototype *hg_fp_add(const char *funcn)
+{
+  struct func_prototype *fp;
+
+  if ((hg_fp_cnt & 0xff) == 0) {
+    hg_fp = realloc(hg_fp, sizeof(hg_fp[0]) * (hg_fp_cnt + 0x100));
+    my_assert_not(hg_fp, NULL);
+    memset(hg_fp + hg_fp_cnt, 0, sizeof(hg_fp[0]) * 0x100);
+  }
+
+  fp = &hg_fp[hg_fp_cnt];
+  snprintf(fp->name, sizeof(fp->name), "%s", funcn);
+  fp->id = hg_fp_cnt;
+  fp->argc_stack = -1;
+  hg_fp_cnt++;
+
+  return fp;
+}
+
 static struct func_proto_dep *hg_fp_find_dep(struct func_prototype *fp,
   const char *name)
 {
@@ -5645,20 +5926,20 @@ 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++) {
+          check_i(po, po->btj->d[j].bt_i);
           gen_hdr_dep_pass(po->btj->d[j].bt_i, opcnt, cbits, fp,
             regmask_save, regmask_dst, regmask_dep, has_ret);
         }
         return;
       }
 
-      if (po->bt_i < 0) {
-        ferr(po, "dead branch\n");
-        return;
-      }
-
+      check_i(po, po->bt_i);
       if (po->flags & OPF_CJMP) {
         gen_hdr_dep_pass(po->bt_i, opcnt, cbits, fp,
           regmask_save, regmask_dst, regmask_dep, has_ret);
@@ -5714,6 +5995,8 @@ static void gen_hdr_dep_pass(int i, int opcnt, unsigned char *cbits,
       }
     }
 
+    // if has_ret is 0, there is uninitialized eax path,
+    // which means it's most likely void func
     if (*has_ret != 0 && (po->flags & OPF_TAIL)) {
       if (po->op == OP_CALL) {
         j = i;
@@ -5728,13 +6011,13 @@ static void gen_hdr_dep_pass(int i, int opcnt, unsigned char *cbits,
         ret = resolve_origin(i, &opr, i + opcnt * 4, &j, &from_caller);
       }
 
-      if (ret == -1 && from_caller) {
+      if (ret != 1 && from_caller) {
         // unresolved eax - probably void func
         *has_ret = 0;
       }
       else {
-        if (ops[j].op == OP_CALL) {
-          dep = hg_fp_find_dep(fp, po->operand[0].name);
+        if (j >= 0 && ops[j].op == OP_CALL) {
+          dep = hg_fp_find_dep(fp, ops[j].operand[0].name);
           if (dep != NULL)
             dep->ret_dep = 1;
           else
@@ -5770,124 +6053,56 @@ static void gen_hdr(const char *funcn, int opcnt)
   const struct parsed_proto *pp_c;
   struct parsed_proto *pp;
   struct func_prototype *fp;
-  struct parsed_data *pd;
   struct parsed_op *po;
-  const char *tmpname;
   int regmask_dummy = 0;
   int regmask_dep;
   int max_bp_offset = 0;
   int has_ret;
-  int i, j, l, ret;
-
-  if ((hg_fp_cnt & 0xff) == 0) {
-    hg_fp = realloc(hg_fp, sizeof(hg_fp[0]) * (hg_fp_cnt + 0x100));
-    my_assert_not(hg_fp, NULL);
-    memset(hg_fp + hg_fp_cnt, 0, sizeof(hg_fp[0]) * 0x100);
-  }
-
-  fp = &hg_fp[hg_fp_cnt];
-  snprintf(fp->name, sizeof(fp->name), "%s", funcn);
-  fp->id = hg_fp_cnt;
-  fp->argc_stack = -1;
-  hg_fp_cnt++;
+  int i, j, l;
+  int ret;
 
-  // perhaps already in seed header?
-  fp->pp = proto_parse(g_fhdr, funcn, 1);
-  if (fp->pp != NULL) {
-    fp->argc_stack = fp->pp->argc_stack;
-    fp->is_stdcall = fp->pp->is_stdcall;
-    fp->regmask_dep = get_pp_arg_regmask(fp->pp);
-    fp->has_ret = !IS(fp->pp->ret_type.name, "void");
+  pp_c = proto_parse(g_fhdr, funcn, 1);
+  if (pp_c != NULL)
+    // already in seed, will add to hg_fp later
     return;
-  }
+
+  fp = hg_fp_add(funcn);
 
   g_bp_frame = g_sp_frame = g_stack_fsz = 0;
   g_stack_frame_used = 0;
 
   // pass1:
+  // - resolve all branches
+  // - parse calls with labels
+  resolve_branches_parse_calls(opcnt);
+
+  // pass2:
   // - handle ebp/esp frame, remove ops related to it
   scan_prologue_epilogue(opcnt);
 
-  // pass2:
+  // pass3:
+  // - remove dead labels
   // - collect calls
-  // - resolve all branches
   for (i = 0; i < opcnt; i++)
   {
-    po = &ops[i];
-    po->bt_i = -1;
-    po->btj = NULL;
+    if (g_labels[i] != NULL && g_label_refs[i].i == -1) {
+      free(g_labels[i]);
+      g_labels[i] = NULL;
+    }
 
+    po = &ops[i];
     if (po->flags & (OPF_RMD|OPF_DONE))
       continue;
 
     if (po->op == OP_CALL) {
-      tmpname = opr_name(po, 0);
-      pp = NULL;
-      if (po->operand[0].type == OPT_LABEL) {
-        hg_fp_add_dep(fp, tmpname);
-
-        // perhaps a call to already known func?
-        pp_c = proto_parse(g_fhdr, tmpname, 1);
-        if (pp_c != NULL)
-          pp = proto_clone(pp_c);
-      }
-      else if (po->datap != NULL) {
-        pp = calloc(1, sizeof(*pp));
-        my_assert_not(pp, NULL);
-
-        ret = parse_protostr(po->datap, pp);
-        if (ret < 0)
-          ferr(po, "bad protostr supplied: %s\n", (char *)po->datap);
-        free(po->datap);
-        po->datap = NULL;
-      }
-      if (pp != NULL && pp->is_noreturn)
-        po->flags |= OPF_TAIL;
-
-      po->pp = pp;
-      continue;
-    }
-
-    if (!(po->flags & OPF_JMP) || po->op == OP_RET)
-      continue;
-
-    if (po->operand[0].type == OPT_REGMEM) {
-      pd = try_resolve_jumptab(i, opcnt);
-      if (pd == NULL)
-        goto tailcall;
-
-      po->btj = pd;
-      continue;
-    }
-
-    for (l = 0; l < opcnt; l++) {
-      if (g_labels[l] != NULL
-          && IS(po->operand[0].name, g_labels[l]))
-      {
-        add_label_ref(&g_label_refs[l], i);
-        po->bt_i = l;
-        break;
-      }
+      if (po->operand[0].type == OPT_LABEL)
+        hg_fp_add_dep(fp, opr_name(po, 0));
+      else if (po->pp != NULL)
+        hg_fp_add_dep(fp, po->pp->name);
     }
-
-    if (po->bt_i != -1 || (po->flags & OPF_RMD))
-      continue;
-
-    if (po->operand[0].type == OPT_LABEL)
-      // assume tail call
-      goto tailcall;
-
-    ferr(po, "unhandled branch\n");
-
-tailcall:
-    po->op = OP_CALL;
-    po->flags |= OPF_TAIL;
-    if (i > 0 && ops[i - 1].op == OP_POP)
-      po->flags |= OPF_ATAIL;
-    i--; // reprocess
   }
 
-  // pass3:
+  // pass4:
   // - remove dead labels
   // - handle push <const>/pop pairs
   for (i = 0; i < opcnt; i++)
@@ -5905,7 +6120,7 @@ tailcall:
       scan_for_pop_const(i, opcnt, &regmask_dummy);
   }
 
-  // pass4:
+  // pass5:
   // - process trivial calls
   for (i = 0; i < opcnt; i++)
   {
@@ -5926,11 +6141,12 @@ tailcall:
       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;
@@ -5938,7 +6154,7 @@ tailcall:
     }
   }
 
-  // pass5:
+  // pass6:
   // - track saved regs (simple)
   // - process calls
   for (i = 0; i < opcnt; i++)
@@ -5968,7 +6184,7 @@ tailcall:
     }
   }
 
-  // pass6
+  // pass7
   memset(cbits, 0, sizeof(cbits));
   regmask_dep = 0;
   has_ret = -1;
@@ -5981,13 +6197,17 @@ tailcall:
     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");
   }
 
-  if (has_ret == -1 && (regmask_dep & (1 << xAX)))
-    has_ret = 1;
-
   for (i = 0; i < g_eqcnt; i++) {
     if (g_eqs[i].offset > max_bp_offset && g_eqs[i].offset < 4*32)
       max_bp_offset = g_eqs[i].offset;
@@ -6006,7 +6226,7 @@ tailcall:
   printf("// has_ret %d, regmask_dep %x\n",
     fp->has_ret, fp->regmask_dep);
   output_hdr_fp(stdout, fp, 1);
-  if (IS(funcn, "sub_100073FD")) exit(1);
+  if (IS(funcn, "sub_10007F72")) exit(1);
 #endif
 
   gen_x_cleanup(opcnt);
@@ -6035,7 +6255,7 @@ static void hg_fp_resolve_deps(struct func_prototype *fp)
       // printf("dep %s %s |= %x\n", fp->name,
       //   fp->dep_func[i].name, dep);
 
-      if (fp->has_ret == -1)
+      if (fp->has_ret == -1 && fp->dep_func[i].ret_dep)
         fp->has_ret = fp->dep_func[i].proto->has_ret;
     }
   }
@@ -6155,9 +6375,25 @@ static void output_hdr(FILE *fout)
     [OPLM_QWORD] = "uint64_t",
   };
   const struct scanned_var *var;
+  struct func_prototype *fp;
   char line[256] = { 0, };
+  char name[256];
   int i;
 
+  // add stuff from headers
+  for (i = 0; i < pp_cache_size; i++) {
+    if (pp_cache[i].is_cinc && !pp_cache[i].is_stdcall)
+      snprintf(name, sizeof(name), "_%s", pp_cache[i].name);
+    else
+      snprintf(name, sizeof(name), "%s", pp_cache[i].name);
+    fp = hg_fp_add(name);
+    fp->pp = &pp_cache[i];
+    fp->argc_stack = fp->pp->argc_stack;
+    fp->is_stdcall = fp->pp->is_stdcall;
+    fp->regmask_dep = get_pp_arg_regmask(fp->pp);
+    fp->has_ret = !IS(fp->pp->ret_type.name, "void");
+  }
+
   // resolve deps
   qsort(hg_fp, hg_fp_cnt, sizeof(hg_fp[0]), hg_fp_cmp_name);
   for (i = 0; i < hg_fp_cnt; i++)
@@ -6197,30 +6433,6 @@ static void output_hdr(FILE *fout)
     fwrite(line, 1, strlen(line), fout);
 }
 
-// read a line, truncating it if it doesn't fit
-static char *my_fgets(char *s, size_t size, FILE *stream)
-{
-  char *ret, *ret2;
-  char buf[64];
-  int p;
-
-  p = size - 2;
-  if (p >= 0)
-    s[p] = 0;
-
-  ret = fgets(s, size, stream);
-  if (ret != NULL && p >= 0 && s[p] != 0 && s[p] != '\n') {
-    p = sizeof(buf) - 2;
-    do {
-      buf[p] = 0;
-      ret2 = fgets(buf, sizeof(buf), stream);
-    }
-    while (ret2 != NULL && buf[p] != 0 && buf[p] != '\n');
-  }
-
-  return ret;
-}
-
 // '=' needs special treatment
 // also ' quote
 static char *next_word_s(char *w, size_t wsize, char *s)
@@ -6320,6 +6532,11 @@ static void scan_variables(FILE *fasm)
       if (wordc < 2)
         continue;
 
+      if (IS_START(words[0], "__IMPORT_DESCRIPTOR_")) {
+        // when this starts, we don't need anything from this section
+        break;
+      }
+
       if ((hg_var_cnt & 0xff) == 0) {
         hg_vars = realloc(hg_vars, sizeof(hg_vars[0])
                    * (hg_var_cnt + 0x100));