translate: hdrgen: detect strings, skip std funcs
[ia32rtools.git] / tools / translate.c
index c9487df..c7e6ce1 100644 (file)
@@ -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
@@ -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)
@@ -2543,6 +2549,8 @@ static int scan_for_esp_adjust(int i, int opcnt,
       *adj -= lmod_bytes(po, po->operand[0].lmod);
     }
     else if (po->op == OP_POP && !(po->flags & OPF_RMD)) {
+      if (po->datap != NULL) // in push/pop pair?
+        break;
       // seems like msvc only uses 'pop ecx' for stack realignment..
       if (po->operand[0].type != OPT_REG || po->operand[0].reg != xCX)
         break;
@@ -3067,13 +3075,45 @@ 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 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 +3121,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 +3148,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 +3170,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,28 +3188,28 @@ 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) {
+        adj = pp->argc_stack;
+        while (ops[ret].op == OP_POP && adj > 0 && ret < opcnt) {
           ops[ret].flags |= OPF_RMD;
-          j--;
+          adj--;
           ret++;
         }
       }
     }
-    else if (!l) {
+    else if (!multipath) {
       // a bit of a hack, but deals with use of
       // single adj for multiple calls
-      ops[ret].operand[1].val -= j;
+      ops[ret].operand[1].val -= pp->argc_stack * 4;
     }
   }
   else if (pp->is_vararg)
@@ -3177,6 +3219,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_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)
@@ -3742,7 +3860,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) {
@@ -3755,6 +3873,42 @@ tailcall:
       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) {
+            ferr_assert(&ops[j], ops[j].op == OP_ADD);
+            ops[j].operand[1].val -= pp->argc_stack * 4;
+          }
+        }
+
+        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)
+      continue;
+
+    if (po->op == OP_CALL && !(po->flags & OPF_DONE))
     {
       pp = process_call(i, opcnt);
 
@@ -3769,14 +3923,15 @@ 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)
       continue;
@@ -3915,6 +4070,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");
@@ -4015,7 +4171,7 @@ tailcall:
     }
   }
 
-  // pass4:
+  // pass6:
   // - confirm regmask_save, it might have been reduced
   if (regmask_save != 0)
   {
@@ -5332,6 +5488,7 @@ 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;
 
@@ -5383,6 +5540,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;
@@ -5390,6 +5548,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;
@@ -5508,7 +5667,7 @@ tailcall:
 
   // pass3:
   // - remove dead labels
-  // - process calls
+  // - process trivial calls
   // - handle push <const>/pop pairs
   for (i = 0; i < opcnt; i++)
   {
@@ -5521,22 +5680,55 @@ tailcall:
     if (po->flags & OPF_RMD)
       continue;
 
-    if (po->op == OP_CALL) {
+    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 != NULL) {
+        if (j >= 0) {
+          // commit esp adjust
+          ops[j].flags |= OPF_RMD;
+          if (ops[j].op != OP_POP) {
+            ferr_assert(&ops[j], ops[j].op == OP_ADD);
+            ops[j].operand[1].val -= pp->argc_stack * 4;
+          }
+        }
+
+        po->flags |= OPF_DONE;
+      }
+    }
+    else if (po->op == OP_PUSH && po->operand[0].type == OPT_CONST) {
+      scan_for_pop_const(i, opcnt);
+    }
+  }
+
+  // pass4:
+  // - process calls
+  for (i = 0; i < opcnt; i++)
+  {
+    po = &ops[i];
+    if (po->flags & OPF_RMD)
+      continue;
+
+    if (po->op == OP_CALL && !(po->flags & OPF_DONE))
+    {
       pp = process_call(i, opcnt);
 
       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);
       }
     }
-    else if (po->op == OP_PUSH && po->operand[0].type == OPT_CONST) {
-      scan_for_pop_const(i, opcnt);
-    }
   }
 
-  // pass4:
+  // pass5:
   // - track saved regs
   // - try to figure out arg-regs
   for (i = 0; i < opcnt; i++)
@@ -5650,6 +5842,7 @@ tailcall:
 
   fp->regmask_dep = regmask_dep & ~(1 << xSP);
   fp->has_ret = has_ret;
+  // output_hdr_fp(stdout, fp, 1);
 
   gen_x_cleanup(opcnt);
 }
@@ -5682,8 +5875,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;
@@ -5702,6 +5896,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;
 
@@ -5727,17 +5936,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++) {
@@ -5784,8 +5983,11 @@ static void output_hdr(FILE *fout)
   for (i = 0; i < hg_var_cnt; i++) {
     var = &hg_vars[i];
 
-    fprintf(fout, "extern %-8s %s;",
-      lmod_c_names[var->lmod], var->name);
+    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");
@@ -5822,24 +6024,39 @@ static char *my_fgets(char *s, size_t size, FILE *stream)
   return ret;
 }
 
-// '=' needs special treatment..
+// '=' needs special treatment
+// also ' quote
 static char *next_word_s(char *w, size_t wsize, char *s)
 {
-       size_t i;
+  size_t i;
+
+  s = sskip(s);
 
-       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 = 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;
+  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);
+  if (s[i] != 0 && !my_isblank(s[i]) && s[i] != '=')
+    printf("warning: '%s' truncated\n", w);
 
-       return s + i;
+  return s + i;
 }
 
 static void scan_variables(FILE *fasm)
@@ -5847,9 +6064,10 @@ static void scan_variables(FILE *fasm)
   const struct parsed_proto *pp_c;
   struct scanned_var *var;
   char line[256] = { 0, };
-  char words[2][256];
+  char words[3][256];
   char *p = NULL;
   int wordc;
+  int l;
 
   while (!feof(fasm))
   {
@@ -5903,6 +6121,8 @@ static void scan_variables(FILE *fasm)
 
       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])
@@ -5935,8 +6155,13 @@ static void scan_variables(FILE *fasm)
         var->lmod = OPLM_DWORD;
       else if (IS(words[1], "dw"))
         var->lmod = OPLM_WORD;
-      else if (IS(words[1], "db"))
+      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"))