translate: use separate removed and done flags
[ia32rtools.git] / tools / translate.c
index c464758..99409c4 100644 (file)
@@ -37,7 +37,7 @@ static FILE *g_fhdr;
 #include "masm_tools.h"
 
 enum op_flags {
-  OPF_RMD    = (1 << 0), /* removed or optimized out */
+  OPF_RMD    = (1 << 0), /* removed from code generation */
   OPF_DATA   = (1 << 1), /* data processing - writes to dst opr */
   OPF_FLAGS  = (1 << 2), /* sets flags */
   OPF_JMP    = (1 << 3), /* branch, call */
@@ -56,6 +56,7 @@ enum op_flags {
   OPF_32BIT  = (1 << 16), /* 32bit division */
   OPF_LOCK   = (1 << 17), /* op has lock prefix */
   OPF_VAPUSH = (1 << 18), /* vararg ptr push (as call arg) */
+  OPF_DONE   = (1 << 19), /* already fully handled by analysis */
 };
 
 enum op_op {
@@ -247,6 +248,11 @@ static int g_header_mode;
   printf("%s:%d: note: [%s] '%s': " fmt, asmfn, (op_)->asmln, g_func, \
     dump_op(op_), ##__VA_ARGS__)
 
+#define ferr_assert(op_, cond) do { \
+  if (!(cond)) ferr(op_, "assertion '%s' failed on ln :%d\n", #cond, \
+                    __LINE__); \
+} while (0)
+
 const char *regs_r32[] = {
   "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp",
   // not r32, but list here for easy parsing and printing
@@ -1168,7 +1174,7 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
      && op->operand[0].reg == op->operand[1].reg
      && IS(op->operand[0].name, op->operand[1].name)) // ! ah, al..
     {
-      op->flags |= OPF_RMD;
+      op->flags |= OPF_RMD | OPF_DONE;
       op->regmask_src = op->regmask_dst = 0;
     }
     break;
@@ -1180,7 +1186,7 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
       char buf[16];
       snprintf(buf, sizeof(buf), "%s+0", op->operand[0].name);
       if (IS(buf, op->operand[1].name))
-        op->flags |= OPF_RMD;
+        op->flags |= OPF_RMD | OPF_DONE;
     }
     break;
 
@@ -1714,7 +1720,7 @@ static void check_func_pp(struct parsed_op *po,
   // 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) {
+  if (!pp->is_osinc) {
     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)
@@ -2155,7 +2161,7 @@ static int scan_for_pop(int i, int opcnt, const char *reg,
       return -1; // deadend
     }
 
-    if ((po->flags & OPF_RMD)
+    if ((po->flags & (OPF_RMD|OPF_DONE))
         || (po->op == OP_PUSH && po->p_argnum != 0)) // arg push
       continue;
 
@@ -2231,12 +2237,13 @@ static int scan_for_pop_ret(int i, int opcnt, const char *reg,
       continue;
 
     for (j = i - 1; j >= 0; j--) {
-      if (ops[j].flags & OPF_RMD)
+      if (ops[j].flags & (OPF_RMD|OPF_DONE))
         continue;
       if (ops[j].flags & OPF_JMP)
         return -1;
 
-      if (ops[j].op == OP_POP && ops[j].operand[0].type == OPT_REG
+      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;
@@ -2252,7 +2259,6 @@ static int scan_for_pop_ret(int i, int opcnt, const char *reg,
   return found ? 0 : -1;
 }
 
-// XXX: merge with scan_for_pop?
 static void scan_for_pop_const(int i, int opcnt)
 {
   int j;
@@ -2264,9 +2270,10 @@ static void scan_for_pop_const(int i, int opcnt)
       break;
     }
 
-    if (!(ops[j].flags & OPF_RMD) && ops[j].op == OP_POP)
+    if (ops[j].op == OP_POP && !(ops[j].flags & (OPF_RMD|OPF_DONE)))
     {
-      ops[i].flags |= OPF_RMD;
+      ops[i].flags |= OPF_RMD | OPF_DONE;
+      ops[j].flags |= OPF_DONE;
       ops[j].datap = &ops[i];
       break;
     }
@@ -2311,7 +2318,7 @@ static void scan_propagate_df(int i, int opcnt)
       break;
 
     if (po->op == OP_CLD) {
-      po->flags |= OPF_RMD;
+      po->flags |= OPF_RMD | OPF_DONE;
       return;
     }
   }
@@ -2524,11 +2531,13 @@ static int scan_for_esp_adjust(int i, int opcnt,
   *adj = *multipath = 0;
 
   for (; i < opcnt && *adj < adj_expect; i++) {
-    po = &ops[i];
-
     if (g_labels[i] != NULL)
       *multipath = 1;
 
+    po = &ops[i];
+    if (po->flags & OPF_DONE)
+      continue;
+
     if (po->op == OP_ADD && po->operand[0].reg == xSP) {
       if (po->operand[1].type != OPT_CONST)
         ferr(&ops[i], "non-const esp adjust?\n");
@@ -2537,12 +2546,12 @@ static int scan_for_esp_adjust(int i, int opcnt,
         ferr(&ops[i], "unaligned esp adjust: %x\n", *adj);
       return i;
     }
-    else if (po->op == OP_PUSH && !(po->flags & OPF_RMD)) {
+    else if (po->op == OP_PUSH) {
       //if (first_pop == -1)
       //  first_pop = -2; // none
       *adj -= lmod_bytes(po, po->operand[0].lmod);
     }
-    else if (po->op == OP_POP && !(po->flags & OPF_RMD)) {
+    else if (po->op == OP_POP) {
       // seems like msvc only uses 'pop ecx' for stack realignment..
       if (po->operand[0].type != OPT_REG || po->operand[0].reg != xCX)
         break;
@@ -2552,6 +2561,8 @@ static int scan_for_esp_adjust(int i, int opcnt,
     }
     else if (po->flags & (OPF_JMP|OPF_TAIL)) {
       if (po->op == OP_JMP && po->btj == NULL) {
+        if (po->bt_i <= i)
+          break;
         i = po->bt_i - 1;
         continue;
       }
@@ -2559,7 +2570,7 @@ static int scan_for_esp_adjust(int i, int opcnt,
         break;
       if (po->operand[0].type != OPT_LABEL)
         break;
-      if (po->pp != NULL && po->pp->is_stdcall)
+      if (po->pp == NULL || po->pp->is_stdcall)
         break;
     }
   }
@@ -2780,13 +2791,13 @@ static void scan_prologue_epilogue(int opcnt)
       && IS(opr_name(&ops[1], 1), "esp"))
   {
     g_bp_frame = 1;
-    ops[0].flags |= OPF_RMD;
-    ops[1].flags |= OPF_RMD;
+    ops[0].flags |= OPF_RMD | OPF_DONE;
+    ops[1].flags |= OPF_RMD | OPF_DONE;
     i = 2;
 
     if (ops[2].op == OP_SUB && IS(opr_name(&ops[2], 0), "esp")) {
       g_stack_fsz = opr_const(&ops[2], 1);
-      ops[2].flags |= OPF_RMD;
+      ops[2].flags |= OPF_RMD | OPF_DONE;
       i++;
     }
     else {
@@ -2794,7 +2805,7 @@ static void scan_prologue_epilogue(int opcnt)
       i = 2;
       while (ops[i].op == OP_PUSH && IS(opr_name(&ops[i], 0), "ecx")) {
         g_stack_fsz += 4;
-        ops[i].flags |= OPF_RMD;
+        ops[i].flags |= OPF_RMD | OPF_DONE;
         ecx_push++;
         i++;
       }
@@ -2805,9 +2816,9 @@ static void scan_prologue_epilogue(int opcnt)
           && IS(opr_name(&ops[i + 1], 0), "__alloca_probe"))
       {
         g_stack_fsz += ops[i].operand[1].val;
-        ops[i].flags |= OPF_RMD;
+        ops[i].flags |= OPF_RMD | OPF_DONE;
         i++;
-        ops[i].flags |= OPF_RMD;
+        ops[i].flags |= OPF_RMD | OPF_DONE;
         i++;
       }
     }
@@ -2827,7 +2838,7 @@ static void scan_prologue_epilogue(int opcnt)
       if ((ops[j].op == OP_POP && IS(opr_name(&ops[j], 0), "ebp"))
           || ops[j].op == OP_LEAVE)
       {
-        ops[j].flags |= OPF_RMD;
+        ops[j].flags |= OPF_RMD | OPF_DONE;
       }
       else if (!(g_ida_func_attr & IDAFA_NORETURN))
         ferr(&ops[j], "'pop ebp' expected\n");
@@ -2837,7 +2848,7 @@ static void scan_prologue_epilogue(int opcnt)
             && IS(opr_name(&ops[j - 1], 0), "esp")
             && IS(opr_name(&ops[j - 1], 1), "ebp"))
         {
-          ops[j - 1].flags |= OPF_RMD;
+          ops[j - 1].flags |= OPF_RMD | OPF_DONE;
         }
         else if (ops[j].op != OP_LEAVE
           && !(g_ida_func_attr & IDAFA_NORETURN))
@@ -2862,7 +2873,7 @@ static void scan_prologue_epilogue(int opcnt)
   // non-bp frame
   i = 0;
   while (ops[i].op == OP_PUSH && IS(opr_name(&ops[i], 0), "ecx")) {
-    ops[i].flags |= OPF_RMD;
+    ops[i].flags |= OPF_RMD | OPF_DONE;
     g_stack_fsz += 4;
     ecx_push++;
     i++;
@@ -2875,7 +2886,7 @@ static void scan_prologue_epilogue(int opcnt)
       && ops[i].operand[1].type == OPT_CONST)
     {
       g_stack_fsz = ops[i].operand[1].val;
-      ops[i].flags |= OPF_RMD;
+      ops[i].flags |= OPF_RMD | OPF_DONE;
       esp_sub = 1;
       break;
     }
@@ -2894,7 +2905,7 @@ static void scan_prologue_epilogue(int opcnt)
       while (i > 0 && j > 0) {
         i--;
         if (ops[i].op == OP_PUSH) {
-          ops[i].flags &= ~OPF_RMD;
+          ops[i].flags &= ~(OPF_RMD | OPF_DONE);
           j--;
         }
       }
@@ -2939,12 +2950,12 @@ static void scan_prologue_epilogue(int opcnt)
                    && ops[j].operand[1].type == OPT_CONST)
           {
             /* add esp, N */
-            ecx_push -= ops[j].operand[1].val / 4 - 1;
+            l += ops[j].operand[1].val / 4 - 1;
           }
           else
             ferr(&ops[j], "'pop ecx' expected\n");
 
-          ops[j].flags |= OPF_RMD;
+          ops[j].flags |= OPF_RMD | OPF_DONE;
           j--;
         }
         if (l != ecx_push)
@@ -2960,7 +2971,7 @@ static void scan_prologue_epilogue(int opcnt)
             || ops[j].operand[1].val != g_stack_fsz)
           ferr(&ops[j], "'add esp' expected\n");
 
-        ops[j].flags |= OPF_RMD;
+        ops[j].flags |= OPF_RMD | OPF_DONE;
         ops[j].operand[1].val = 0; // hack for stack arg scanner
         found = 1;
       }
@@ -3067,13 +3078,60 @@ static int try_resolve_const(int i, const struct parsed_opr *opr,
   return -1;
 }
 
+static struct parsed_proto *process_call_early(int i, int opcnt,
+  int *adj_i)
+{
+  struct parsed_op *po = &ops[i];
+  struct parsed_proto *pp;
+  int multipath = 0;
+  int adj = 0;
+  int ret;
+
+  pp = po->pp;
+  if (pp == NULL || pp->is_vararg || pp->argc_reg != 0)
+    // leave for later
+    return NULL;
+
+  // look for and make use of esp adjust
+  *adj_i = ret = -1;
+  if (!pp->is_stdcall && pp->argc_stack > 0)
+    ret = scan_for_esp_adjust(i + 1, opcnt,
+            pp->argc_stack * 4, &adj, &multipath);
+  if (ret >= 0) {
+    if (pp->argc_stack > adj / 4)
+      return NULL;
+    if (multipath)
+      return NULL;
+    if (ops[ret].op == OP_POP && adj != 4)
+      return NULL;
+  }
+
+  *adj_i = ret;
+  return pp;
+}
+
+static void patch_esp_adjust(struct parsed_op *po, int adj)
+{
+  ferr_assert(po, po->op == OP_ADD);
+  ferr_assert(po, IS(opr_name(po, 0), "esp"));
+  ferr_assert(po, po->operand[1].type == OPT_CONST);
+
+  // this is a bit of a hack, but deals with use of
+  // single adj for multiple calls
+  po->operand[1].val -= adj;
+  po->flags |= OPF_RMD;
+  if (po->operand[1].val == 0)
+    po->flags |= OPF_DONE;
+  ferr_assert(po, (int)po->operand[1].val >= 0);
+}
+
 static struct parsed_proto *process_call(int i, int opcnt)
 {
   struct parsed_op *po = &ops[i];
   const struct parsed_proto *pp_c;
   struct parsed_proto *pp;
   const char *tmpname;
-  int j = 0, l = 0;
+  int adj = 0, multipath = 0;
   int ret, arg;
 
   tmpname = opr_name(po, 0);
@@ -3081,13 +3139,13 @@ static struct parsed_proto *process_call(int i, int opcnt)
   if (pp == NULL)
   {
     // indirect call
-    pp_c = resolve_icall(i, opcnt, &l);
+    pp_c = resolve_icall(i, opcnt, &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);
       pp = proto_clone(pp_c);
       my_assert_not(pp, NULL);
-      if (l)
+      if (multipath)
         // not resolved just to single func
         pp->is_fptr = 1;
 
@@ -3108,18 +3166,18 @@ static struct parsed_proto *process_call(int i, int opcnt)
       my_assert_not(pp, NULL);
 
       pp->is_fptr = 1;
-      ret = scan_for_esp_adjust(i + 1, opcnt, ~0, &j, &l);
-      if (ret < 0 || j < 0) {
+      ret = scan_for_esp_adjust(i + 1, opcnt, ~0, &adj, &multipath);
+      if (ret < 0 || adj < 0) {
         if (!g_allow_regfunc)
           ferr(po, "non-__cdecl indirect call unhandled yet\n");
         pp->is_unresolved = 1;
-        j = 0;
+        adj = 0;
       }
-      j /= 4;
-      if (j > ARRAY_SIZE(pp->arg))
-        ferr(po, "esp adjust too large: %d\n", j);
+      adj /= 4;
+      if (adj > ARRAY_SIZE(pp->arg))
+        ferr(po, "esp adjust too large: %d\n", adj);
       pp->ret_type.name = strdup("int");
-      pp->argc = pp->argc_stack = j;
+      pp->argc = pp->argc_stack = adj;
       for (arg = 0; arg < pp->argc; arg++)
         pp->arg[arg].type.name = strdup("int");
     }
@@ -3130,15 +3188,17 @@ static struct parsed_proto *process_call(int i, int opcnt)
   ret = -1;
   if (!pp->is_stdcall && pp->argc_stack > 0)
     ret = scan_for_esp_adjust(i + 1, opcnt,
-            pp->argc_stack * 4, &j, &l);
+            pp->argc_stack * 4, &adj, &multipath);
   if (ret >= 0) {
     if (pp->is_vararg) {
-      if (j / 4 < pp->argc_stack)
-        ferr(po, "esp adjust is too small: %x < %x\n",
-          j, pp->argc_stack * 4);
+      if (adj / 4 < pp->argc_stack) {
+        fnote(po, "(this call)\n");
+        ferr(&ops[ret], "esp adjust is too small: %x < %x\n",
+          adj, pp->argc_stack * 4);
+      }
       // modify pp to make it have varargs as normal args
       arg = pp->argc;
-      pp->argc += j / 4 - pp->argc_stack;
+      pp->argc += adj / 4 - pp->argc_stack;
       for (; arg < pp->argc; arg++) {
         pp->arg[arg].type.name = strdup("int");
         pp->argc_stack++;
@@ -3146,29 +3206,26 @@ static struct parsed_proto *process_call(int i, int opcnt)
       if (pp->argc > ARRAY_SIZE(pp->arg))
         ferr(po, "too many args for '%s'\n", tmpname);
     }
-    if (pp->argc_stack > j / 4) {
+    if (pp->argc_stack > adj / 4) {
       fnote(po, "(this call)\n");
       ferr(&ops[ret], "stack tracking failed for '%s': %x %x\n",
-        tmpname, pp->argc_stack * 4, j);
+        tmpname, pp->argc_stack * 4, adj);
     }
 
     ops[ret].flags |= OPF_RMD;
     if (ops[ret].op == OP_POP) {
-      if (j > 4) {
+      if (adj > 4) {
         // deal with multi-pop stack adjust
-        j = pp->argc_stack;
-        while (ops[ret].op == OP_POP && j > 0 && ret < opcnt) {
-          ops[ret].flags |= OPF_RMD;
-          j--;
+        adj = pp->argc_stack;
+        while (ops[ret].op == OP_POP && adj > 0 && ret < opcnt) {
+          ops[ret].flags |= OPF_RMD | OPF_DONE;
+          adj--;
           ret++;
         }
       }
     }
-    else if (!l) {
-      // a bit of a hack, but deals with use of
-      // single adj for multiple calls
-      ops[ret].operand[1].val -= j;
-    }
+    else if (!multipath)
+      patch_esp_adjust(&ops[ret], pp->argc_stack * 4);
   }
   else if (pp->is_vararg)
     ferr(po, "missing esp_adjust for vararg func '%s'\n",
@@ -3177,6 +3234,82 @@ static struct parsed_proto *process_call(int i, int opcnt)
   return pp;
 }
 
+static int collect_call_args_early(struct parsed_op *po, int i,
+  struct parsed_proto *pp, int *regmask)
+{
+  int arg, ret;
+  int j;
+
+  for (arg = 0; arg < pp->argc; arg++)
+    if (pp->arg[arg].reg == NULL)
+      break;
+
+  // first see if it can be easily done
+  for (j = i; j > 0 && arg < pp->argc; )
+  {
+    if (g_labels[j] != NULL)
+      return -1;
+    j--;
+
+    if (ops[j].op == OP_CALL)
+      return -1;
+    else if (ops[j].op == OP_ADD && ops[j].operand[0].reg == xSP)
+      return -1;
+    else if (ops[j].op == OP_POP)
+      return -1;
+    else if (ops[j].flags & OPF_CJMP)
+      return -1;
+    else if (ops[j].op == OP_PUSH) {
+      if (ops[j].flags & (OPF_FARG|OPF_FARGNR))
+        return -1;
+      ret = scan_for_mod(&ops[j], j + 1, i, 1);
+      if (ret >= 0)
+        return -1;
+
+      if (pp->arg[arg].type.is_va_list)
+        return -1;
+
+      // next arg
+      for (arg++; arg < pp->argc; arg++)
+        if (pp->arg[arg].reg == NULL)
+          break;
+    }
+  }
+
+  if (arg < pp->argc)
+    return -1;
+
+  // now do it
+  for (arg = 0; arg < pp->argc; arg++)
+    if (pp->arg[arg].reg == NULL)
+      break;
+
+  for (j = i; j > 0 && arg < pp->argc; )
+  {
+    j--;
+
+    if (ops[j].op == OP_PUSH)
+    {
+      ops[j].p_argnext = -1;
+      ferr_assert(&ops[j], pp->arg[arg].datap == NULL);
+      pp->arg[arg].datap = &ops[j];
+
+      if (ops[j].operand[0].type == OPT_REG)
+        *regmask |= 1 << ops[j].operand[0].reg;
+
+      ops[j].flags |= OPF_RMD | OPF_DONE | OPF_FARGNR | OPF_FARG;
+      ops[j].flags &= ~OPF_RSAVE;
+
+      // next arg
+      for (arg++; arg < pp->argc; arg++)
+        if (pp->arg[arg].reg == NULL)
+          break;
+    }
+  }
+
+  return 0;
+}
+
 static int collect_call_args_r(struct parsed_op *po, int i,
   struct parsed_proto *pp, int *regmask, int *save_arg_vars,
   int *arg_grp, int arg, int magic, int need_op_saving, int may_reuse)
@@ -3270,8 +3403,7 @@ static int collect_call_args_r(struct parsed_op *po, int i,
       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 && !(ops[j].flags & OPF_RMD)
-      && ops[j].datap == NULL)
+    else if (ops[j].op == OP_POP && !(ops[j].flags & OPF_DONE))
     {
       if (pp->is_unresolved)
         break;
@@ -3353,7 +3485,7 @@ static int collect_call_args_r(struct parsed_op *po, int i,
             if (!g_func_pp->is_vararg
               || strstr(ops[k].operand[1].name, buf))
             {
-              ops[k].flags |= OPF_RMD;
+              ops[k].flags |= OPF_RMD | OPF_DONE;
               ops[j].flags |= OPF_RMD | OPF_VAPUSH;
               save_args &= ~(1 << arg);
               reg = -1;
@@ -3368,7 +3500,7 @@ static int collect_call_args_r(struct parsed_op *po, int i,
             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[k].flags |= OPF_RMD | OPF_DONE;
               ops[j].flags |= OPF_RMD;
               ops[j].p_argpass = ret + 1;
               save_args &= ~(1 << arg);
@@ -3658,7 +3790,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
     po->bt_i = -1;
     po->btj = NULL;
 
-    if (po->flags & OPF_RMD)
+    if (po->flags & (OPF_RMD|OPF_DONE))
       continue;
 
     if (po->op == OP_CALL) {
@@ -3714,7 +3846,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
       {
         if (l == i + 1 && po->op == OP_JMP) {
           // yet another alignment type..
-          po->flags |= OPF_RMD;
+          po->flags |= OPF_RMD|OPF_DONE;
           break;
         }
         add_label_ref(&g_label_refs[l], i);
@@ -3742,7 +3874,7 @@ tailcall:
 
   // pass3:
   // - remove dead labels
-  // - process calls
+  // - process trivial calls
   for (i = 0; i < opcnt; i++)
   {
     if (g_labels[i] != NULL && g_label_refs[i].i == -1) {
@@ -3751,10 +3883,46 @@ tailcall:
     }
 
     po = &ops[i];
-    if (po->flags & OPF_RMD)
+    if (po->flags & (OPF_RMD|OPF_DONE))
       continue;
 
     if (po->op == OP_CALL)
+    {
+      pp = process_call_early(i, opcnt, &j);
+      if (pp != NULL) {
+        if (!(po->flags & OPF_ATAIL))
+          // since we know the args, try to collect them
+          if (collect_call_args_early(po, i, pp, &regmask) != 0)
+            pp = NULL;
+      }
+
+      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;
+        }
+
+        if (strstr(pp->ret_type.name, "int64"))
+          need_tmp64 = 1;
+
+        po->flags |= OPF_DONE;
+      }
+    }
+  }
+
+  // pass4:
+  // - process calls
+  for (i = 0; i < opcnt; i++)
+  {
+    po = &ops[i];
+    if (po->flags & (OPF_RMD|OPF_DONE))
+      continue;
+
+    if (po->op == OP_CALL && !(po->flags & OPF_DONE))
     {
       pp = process_call(i, opcnt);
 
@@ -3769,16 +3937,17 @@ tailcall:
     }
   }
 
-  // pass4:
+  // pass5:
   // - find POPs for PUSHes, rm both
   // - scan for STD/CLD, propagate DF
   // - scan for all used registers
   // - find flag set ops for their users
   // - do unreselved calls
   // - declare indirect functions
-  for (i = 0; i < opcnt; i++) {
+  for (i = 0; i < opcnt; i++)
+  {
     po = &ops[i];
-    if (po->flags & OPF_RMD)
+    if (po->flags & (OPF_RMD|OPF_DONE))
       continue;
 
     if (po->op == OP_PUSH && (po->flags & OPF_RSAVE)) {
@@ -3830,7 +3999,7 @@ tailcall:
     }
 
     if (po->op == OP_STD) {
-      po->flags |= OPF_DF | OPF_RMD;
+      po->flags |= OPF_DF | OPF_RMD | OPF_DONE;
       scan_propagate_df(i + 1, opcnt);
     }
 
@@ -3915,6 +4084,7 @@ tailcall:
         need_tmp64 = 1;
     }
     else if (po->op == OP_CALL) {
+      // note: resolved non-reg calls are OPF_DONE already
       pp = po->pp;
       if (pp == NULL)
         ferr(po, "NULL pp\n");
@@ -4008,14 +4178,14 @@ tailcall:
         need_tmp64 = 1;
     }
     else if (po->op == OP_CLD)
-      po->flags |= OPF_RMD;
+      po->flags |= OPF_RMD | OPF_DONE;
 
     if (po->op == OP_RCL || po->op == OP_RCR || po->op == OP_XCHG) {
       need_tmp_var = 1;
     }
   }
 
-  // pass4:
+  // pass6:
   // - confirm regmask_save, it might have been reduced
   if (regmask_save != 0)
   {
@@ -5328,6 +5498,14 @@ struct func_proto_dep {
 static struct func_prototype *hg_fp;
 static int hg_fp_cnt;
 
+static struct scanned_var {
+  char name[NAMELEN];
+  enum opr_lenmod lmod;
+  unsigned int is_seeded:1;
+  unsigned int is_c_str:1;
+} *hg_vars;
+static int hg_var_cnt;
+
 static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
   int count);
 
@@ -5376,6 +5554,7 @@ static int hg_fp_cmp_id(const void *p1_, const void *p2_)
 
 static void gen_hdr(const char *funcn, int opcnt)
 {
+  int save_arg_vars[MAX_ARG_GRP] = { 0, };
   const struct parsed_proto *pp_c;
   struct parsed_proto *pp;
   struct func_prototype *fp;
@@ -5383,6 +5562,7 @@ static void gen_hdr(const char *funcn, int opcnt)
   struct parsed_data *pd;
   struct parsed_op *po;
   const char *tmpname;
+  int regmask_dummy = 0;
   int regmask_save = 0;
   int regmask_dst = 0;
   int regmask_dep = 0;
@@ -5429,7 +5609,7 @@ static void gen_hdr(const char *funcn, int opcnt)
     po->bt_i = -1;
     po->btj = NULL;
 
-    if (po->flags & OPF_RMD)
+    if (po->flags & (OPF_RMD|OPF_DONE))
       continue;
 
     if (po->op == OP_CALL) {
@@ -5501,7 +5681,7 @@ tailcall:
 
   // pass3:
   // - remove dead labels
-  // - process calls
+  // - process trivial calls
   // - handle push <const>/pop pairs
   for (i = 0; i < opcnt; i++)
   {
@@ -5511,17 +5691,30 @@ tailcall:
     }
 
     po = &ops[i];
-    if (po->flags & OPF_RMD)
+    if (po->flags & (OPF_RMD|OPF_DONE))
       continue;
 
-    if (po->op == OP_CALL) {
-      pp = process_call(i, opcnt);
+    if (po->op == OP_CALL)
+    {
+      pp = process_call_early(i, opcnt, &j);
+      if (pp != NULL) {
+        if (!(po->flags & OPF_ATAIL))
+          // since we know the args, try to collect them
+          if (collect_call_args_early(po, i, pp, &regmask_dummy) != 0)
+            pp = NULL;
+      }
 
-      if (!pp->is_unresolved && !(po->flags & OPF_ATAIL)) {
-        int regmask_dummy = 0, save_arg_vars[MAX_ARG_GRP] = { 0, };
-        // since we know the args, collect them
-        collect_call_args(po, i, pp, &regmask_dummy, save_arg_vars,
-          i + opcnt * 2);
+      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;
+        }
+
+        po->flags |= OPF_DONE;
       }
     }
     else if (po->op == OP_PUSH && po->operand[0].type == OPT_CONST) {
@@ -5530,8 +5723,39 @@ tailcall:
   }
 
   // pass4:
-  // - track saved regs
+  // - track saved regs (simple)
+  // - process calls
+  for (i = 0; i < opcnt; i++)
+  {
+    po = &ops[i];
+    if (po->flags & (OPF_RMD|OPF_DONE))
+      continue;
+
+    if (po->op == OP_PUSH && po->operand[0].type == OPT_REG)
+    {
+      ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, 0);
+      if (ret == 0) {
+        regmask_save |= 1 << po->operand[0].reg;
+        po->flags |= OPF_RMD;
+        scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, OPF_RMD);
+      }
+    }
+    else if (po->op == OP_CALL && !(po->flags & OPF_DONE))
+    {
+      pp = process_call(i, opcnt);
+
+      if (!pp->is_unresolved && !(po->flags & OPF_ATAIL)) {
+        // since we know the args, collect them
+        collect_call_args(po, i, pp, &regmask_dummy, save_arg_vars,
+          i + opcnt * 1);
+      }
+    }
+  }
+
+  // pass5:
+  // - track saved regs (part 2)
   // - try to figure out arg-regs
+  // - calculate reg deps
   for (i = 0; i < opcnt; i++)
   {
     po = &ops[i];
@@ -5540,7 +5764,8 @@ tailcall:
       /* (just calculate register deps) */;
     else if (po->flags & OPF_RMD)
       continue;
-    else if (po->op == OP_PUSH && po->operand[0].type == OPT_REG)
+    else if (po->op == OP_PUSH && po->operand[0].type == OPT_REG
+      && !(po->flags & OPF_DONE))
     {
       reg = po->operand[0].reg;
       if (reg < 0)
@@ -5548,19 +5773,12 @@ tailcall:
 
       depth = 0;
       ret = scan_for_pop(i + 1, opcnt,
-              po->operand[0].name, i + opcnt * 1, 0, &depth, 0);
+              po->operand[0].name, i + opcnt * 2, 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 * 2, 0, &depth, 1);
-        continue;
-      }
-      ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, 0);
-      if (ret == 0) {
-        regmask_save |= 1 << reg;
-        po->flags |= OPF_RMD;
-        scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, OPF_RMD);
+          po->operand[0].name, i + opcnt * 3, 0, &depth, 1);
         continue;
       }
     }
@@ -5592,7 +5810,7 @@ tailcall:
         opr.reg = xAX;
         j = -1;
         from_caller = 0;
-        ret = resolve_origin(i, &opr, i + opcnt * 3, &j, &from_caller);
+        ret = resolve_origin(i, &opr, i + opcnt * 4, &j, &from_caller);
       }
 
       if (ret == -1 && from_caller) {
@@ -5643,6 +5861,7 @@ tailcall:
 
   fp->regmask_dep = regmask_dep & ~(1 << xSP);
   fp->has_ret = has_ret;
+  // output_hdr_fp(stdout, fp, 1);
 
   gen_x_cleanup(opcnt);
 }
@@ -5675,8 +5894,9 @@ static void hg_fp_resolve_deps(struct func_prototype *fp)
 static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
   int count)
 {
-  char *p, buf[NAMELEN];
-  const char *cp;
+  const struct parsed_proto *pp;
+  char *p, namebuf[NAMELEN];
+  const char *name;
   int regmask_dep;
   int argc_stack;
   int j, arg;
@@ -5695,6 +5915,21 @@ static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
     fprintf(fout, "\n");
 #endif
 
+    p = strchr(fp->name, '@');
+    if (p != NULL) {
+      memcpy(namebuf, fp->name, p - fp->name);
+      namebuf[p - fp->name] = 0;
+      name = namebuf;
+    }
+    else
+      name = fp->name;
+    if (name[0] == '_')
+      name++;
+
+    pp = proto_parse(g_fhdr, name, 1);
+    if (pp != NULL && pp->is_include)
+      continue;
+
     regmask_dep = fp->regmask_dep;
     argc_stack = fp->argc_stack;
 
@@ -5720,17 +5955,7 @@ static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
     else
       fprintf(fout, "  __cdecl       ");
 
-    p = strchr(fp->name, '@');
-    if (p != NULL) {
-      memcpy(buf, fp->name, p - fp->name);
-      buf[p - fp->name] = 0;
-      cp = buf;
-    }
-    else
-      cp = fp->name;
-    if (cp[0] == '_')
-      cp++;
-    fprintf(fout, "%s(", cp);
+    fprintf(fout, "%s(", name);
 
     arg = 0;
     for (j = 0; j < xSP; j++) {
@@ -5755,6 +5980,14 @@ static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
 
 static void output_hdr(FILE *fout)
 {
+  static const char *lmod_c_names[] = {
+    [OPLM_UNSPEC] = "???",
+    [OPLM_BYTE]  = "uint8_t",
+    [OPLM_WORD]  = "uint16_t",
+    [OPLM_DWORD] = "uint32_t",
+    [OPLM_QWORD] = "uint64_t",
+  };
+  const struct scanned_var *var;
   int i;
 
   // resolve deps
@@ -5765,9 +5998,201 @@ static void output_hdr(FILE *fout)
   // note: messes up .proto ptr, don't use
   //qsort(hg_fp, hg_fp_cnt, sizeof(hg_fp[0]), hg_fp_cmp_id);
 
+  // output variables
+  for (i = 0; i < hg_var_cnt; i++) {
+    var = &hg_vars[i];
+
+    if (var->is_c_str)
+      fprintf(fout, "extern %-8s %s[];", "char", var->name);
+    else
+      fprintf(fout, "extern %-8s %s;",
+        lmod_c_names[var->lmod], var->name);
+
+    if (var->is_seeded)
+      fprintf(fout, " // seeded");
+    fprintf(fout, "\n");
+  }
+
+  fprintf(fout, "\n");
+
+  // output function prototypes
   output_hdr_fp(fout, hg_fp, hg_fp_cnt);
 }
 
+// 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)
+{
+  size_t i;
+
+  s = sskip(s);
+
+  i = 0;
+  if (*s == '\'') {
+    w[0] = s[0];
+    for (i = 1; i < wsize - 1; i++) {
+      if (s[i] == 0) {
+        printf("warning: missing closing quote: \"%s\"\n", s);
+        break;
+      }
+      if (s[i] == '\'')
+        break;
+      w[i] = s[i];
+    }
+  }
+
+  for (; i < wsize - 1; i++) {
+    if (s[i] == 0 || my_isblank(s[i]) || (s[i] == '=' && i > 0))
+      break;
+    w[i] = s[i];
+  }
+  w[i] = 0;
+
+  if (s[i] != 0 && !my_isblank(s[i]) && s[i] != '=')
+    printf("warning: '%s' truncated\n", w);
+
+  return s + i;
+}
+
+static void scan_variables(FILE *fasm)
+{
+  const struct parsed_proto *pp_c;
+  struct scanned_var *var;
+  char line[256] = { 0, };
+  char words[3][256];
+  char *p = NULL;
+  int wordc;
+  int l;
+
+  while (!feof(fasm))
+  {
+    // skip to next data section
+    while (my_fgets(line, sizeof(line), fasm))
+    {
+      asmln++;
+
+      p = sskip(line);
+      if (*p == 0 || *p == ';')
+        continue;
+
+      p = sskip(next_word_s(words[0], sizeof(words[0]), p));
+      if (*p == 0 || *p == ';')
+        continue;
+
+      if (*p != 's' || !IS_START(p, "segment para public"))
+        continue;
+
+      break;
+    }
+
+    if (p == NULL || !IS_START(p, "segment para public"))
+      break;
+    p = sskip(p + 19);
+
+    if (!IS_START(p, "'DATA'"))
+      continue;
+
+    // now process it
+    while (my_fgets(line, sizeof(line), fasm))
+    {
+      asmln++;
+
+      p = line;
+      if (my_isblank(*p))
+        continue;
+
+      p = sskip(p);
+      if (*p == 0 || *p == ';')
+        continue;
+
+      for (wordc = 0; wordc < ARRAY_SIZE(words); wordc++) {
+        words[wordc][0] = 0;
+        p = sskip(next_word_s(words[wordc], sizeof(words[0]), p));
+        if (*p == 0 || *p == ';') {
+          wordc++;
+          break;
+        }
+      }
+
+      if (wordc == 2 && IS(words[1], "ends"))
+        break;
+      if (wordc < 2)
+        continue;
+
+      if ((hg_var_cnt & 0xff) == 0) {
+        hg_vars = realloc(hg_vars, sizeof(hg_vars[0])
+                   * (hg_var_cnt + 0x100));
+        my_assert_not(hg_vars, NULL);
+        memset(hg_vars + hg_var_cnt, 0, sizeof(hg_vars[0]) * 0x100);
+      }
+
+      var = &hg_vars[hg_var_cnt++];
+      snprintf(var->name, sizeof(var->name), "%s", words[0]);
+
+      // maybe already in seed header?
+      pp_c = proto_parse(g_fhdr, var->name, 1);
+      if (pp_c != NULL) {
+        if (pp_c->is_func)
+          aerr("func?\n");
+        else if (pp_c->is_fptr) {
+          var->lmod = OPLM_DWORD;
+          //var->is_ptr = 1;
+        }
+        else if (!guess_lmod_from_c_type(&var->lmod, &pp_c->type))
+          aerr("unhandled C type '%s' for '%s'\n",
+            pp_c->type.name, var->name);
+
+        var->is_seeded = 1;
+        continue;
+      }
+
+      if      (IS(words[1], "dd"))
+        var->lmod = OPLM_DWORD;
+      else if (IS(words[1], "dw"))
+        var->lmod = OPLM_WORD;
+      else if (IS(words[1], "db")) {
+        var->lmod = OPLM_BYTE;
+        if (wordc >= 3 && (l = strlen(words[2])) > 4) {
+          if (words[2][0] == '\'' && IS(words[2] + l - 2, ",0"))
+            var->is_c_str = 1;
+        }
+      }
+      else if (IS(words[1], "dq"))
+        var->lmod = OPLM_QWORD;
+      //else if (IS(words[1], "dt"))
+      else
+        aerr("type '%s' not known\n", words[1]);
+    }
+  }
+
+  rewind(fasm);
+  asmln = 0;
+}
+
 static void set_label(int i, const char *name)
 {
   const char *p;
@@ -5786,26 +6211,6 @@ static void set_label(int i, const char *name)
   g_labels[i][len] = 0;
 }
 
-// '=' needs special treatment..
-static char *next_word_s(char *w, size_t wsize, char *s)
-{
-       size_t i;
-
-       s = sskip(s);
-
-       for (i = 0; i < wsize - 1; i++) {
-               if (s[i] == 0 || my_isblank(s[i]) || (s[i] == '=' && i > 0))
-                       break;
-               w[i] = s[i];
-       }
-       w[i] = 0;
-
-       if (s[i] != 0 && !my_isblank(s[i]) && s[i] != '=')
-               printf("warning: '%s' truncated\n", w);
-
-       return s + i;
-}
-
 struct chunk_item {
   char *name;
   long fptr;
@@ -5854,7 +6259,7 @@ static void scan_ahead(FILE *fasm)
   oldpos = ftell(fasm);
   oldasmln = asmln;
 
-  while (fgets(line, sizeof(line), fasm))
+  while (my_fgets(line, sizeof(line), fasm))
   {
     wordc = 0;
     asmln++;
@@ -5982,7 +6387,7 @@ int main(int argc, char *argv[])
     frlist = fopen(argv[arg], "r");
     my_assert_not(frlist, NULL);
 
-    while (fgets(line, sizeof(line), frlist)) {
+    while (my_fgets(line, sizeof(line), frlist)) {
       p = sskip(line);
       if (*p == 0 || *p == ';')
         continue;
@@ -6031,7 +6436,10 @@ int main(int argc, char *argv[])
     g_label_refs[i].next = NULL;
   }
 
-  while (fgets(line, sizeof(line), fasm))
+  if (g_header_mode)
+    scan_variables(fasm);
+
+  while (my_fgets(line, sizeof(line), fasm))
   {
     wordc = 0;
     asmln++;
@@ -6348,7 +6756,7 @@ do_pending_endp:
       }
 
       // scan for next text segment
-      while (fgets(line, sizeof(line), fasm)) {
+      while (my_fgets(line, sizeof(line), fasm)) {
         asmln++;
         p = sskip(line);
         if (*p == 0 || *p == ';')