translate: bswap, allsh* and some tweaks
[ia32rtools.git] / tools / translate.c
index dadf7a9..1ae3cf7 100644 (file)
@@ -79,6 +79,7 @@ enum op_op {
        OP_NOT,
        OP_XLAT,
        OP_CDQ,
+       OP_BSWAP,
        OP_LODS,
        OP_STOS,
        OP_MOVS,
@@ -136,9 +137,19 @@ enum op_op {
   OP_FISUB,
   OP_FIDIVR,
   OP_FISUBR,
+  OP_FCHS,
+  OP_FCOS,
+  OP_FPATAN,
+  OP_FPTAN,
+  OP_FSIN,
+  OP_FSQRT,
+  OP_FXCH,
+  OP_FYL2X,
   // mmx
   OP_EMMS,
   // pseudo-ops for lib calls
+  OPP_ALLSHL,
+  OPP_ALLSHR,
   OPP_FTOL,
   // undefined
   OP_UD2,
@@ -208,7 +219,8 @@ struct parsed_op {
 };
 
 // datap:
-// OP_CALL  - parser proto hint (str)
+// on start:  function/data type hint (sctproto)
+// after analysis:
 // (OPF_CC) - points to one of (OPF_FLAGS) that affects cc op
 // OP_PUSH  - points to OP_POP in complex push/pop graph
 // OP_POP   - points to OP_PUSH in simple push/pop pair
@@ -248,6 +260,11 @@ enum ida_func_attr {
   IDAFA_FPD      = (1 << 5),
 };
 
+enum sct_func_attr {
+  SCTFA_CLEAR_SF   = (1 << 0), // clear stack frame
+  SCTFA_CLEAR_REGS = (1 << 1), // clear registers (mask)
+};
+
 enum x87_const {
   X87_CONST_1 = 1,
   X87_CONST_2T,
@@ -278,6 +295,10 @@ static int g_sp_frame;
 static int g_stack_frame_used;
 static int g_stack_fsz;
 static int g_ida_func_attr;
+static int g_sct_func_attr;
+static int g_stack_clear_start; // in dwords
+static int g_stack_clear_len;
+static int g_regmask_init;
 static int g_skip_func;
 static int g_allow_regfunc;
 static int g_quiet_pp;
@@ -318,9 +339,13 @@ enum x86_regs {
 };
 
 #define mxAX     (1 << xAX)
+#define mxCX     (1 << xCX)
 #define mxDX     (1 << xDX)
 #define mxST0    (1 << xST0)
 #define mxST1    (1 << xST1)
+#define mxST1_0  (mxST1 | mxST0)
+#define mxST7_2  (0xfc << xST0)
+#define mxSTa    (0xff << xST0)
 
 // possible basic comparison types (without inversion)
 enum parsed_flag_op {
@@ -641,7 +666,7 @@ static char *default_cast_to(char *buf, size_t buf_size,
 {
   buf[0] = 0;
 
-  if (!opr->is_ptr)
+  if (!opr->is_ptr || strchr(opr->name, '['))
     return buf;
   if (opr->pp == NULL || opr->pp->type.name == NULL
     || opr->pp->is_fptr)
@@ -902,6 +927,7 @@ static const struct {
   { "not",  OP_NOT,    1, 1, OPF_DATA },
   { "xlat", OP_XLAT,   0, 0, OPF_DATA },
   { "cdq",  OP_CDQ,    0, 0, OPF_DATA },
+  { "bswap",OP_BSWAP,  1, 1, OPF_DATA },
   { "lodsb",OP_LODS,   0, 0, OPF_DATA },
   { "lodsw",OP_LODS,   0, 0, OPF_DATA },
   { "lodsd",OP_LODS,   0, 0, OPF_DATA },
@@ -1013,6 +1039,7 @@ static const struct {
   { "fld",    OP_FLD,    1, 1, OPF_FPUSH },
   { "fild",   OP_FILD,   1, 1, OPF_FPUSH },
   { "fld1",   OP_FLDc,   0, 0, OPF_FPUSH },
+  { "fldln2", OP_FLDc,   0, 0, OPF_FPUSH },
   { "fldz",   OP_FLDc,   0, 0, OPF_FPUSH },
   { "fstp",   OP_FST,    1, 1, OPF_FPOP },
   { "fst",    OP_FST,    1, 1, 0 },
@@ -1034,10 +1061,20 @@ static const struct {
   { "fisub",  OP_FISUB,  1, 1, 0 },
   { "fidivr", OP_FIDIVR, 1, 1, 0 },
   { "fisubr", OP_FISUBR, 1, 1, 0 },
+  { "fchs",   OP_FCHS,   0, 0, 0 },
+  { "fcos",   OP_FCOS,   0, 0, 0 },
+  { "fpatan", OP_FPATAN, 0, 0, OPF_FPOP },
+  { "fptan",  OP_FPTAN,  0, 0, OPF_FPUSH },
+  { "fsin",   OP_FSIN,   0, 0, 0 },
+  { "fsqrt",  OP_FSQRT,  0, 0, 0 },
+  { "fxch",   OP_FXCH,   1, 1, 0 },
+  { "fyl2x",  OP_FYL2X,  0, 0, OPF_FPOP },
   // mmx
   { "emms",   OP_EMMS,   0, 0, OPF_DATA },
   { "movq",   OP_MOV,    2, 2, OPF_DATA },
   // pseudo-ops for lib calls
+  { "_allshl",OPP_ALLSHL },
+  { "_allshr",OPP_ALLSHR },
   { "_ftol",  OPP_FTOL },
   // must be last
   { "ud2",    OP_UD2 },
@@ -1136,6 +1173,7 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
   case OP_INC:
   case OP_DEC:
   case OP_NEG:
+  case OP_BSWAP:
   // more below..
     op->regmask_src |= op->regmask_dst;
     break;
@@ -1324,6 +1362,8 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
     op->regmask_dst |= mxST0;
     if      (IS(words[op_w] + 3, "1"))
       op->operand[0].val = X87_CONST_1;
+    else if (IS(words[op_w] + 3, "ln2"))
+      op->operand[0].val = X87_CONST_LN2;
     else if (IS(words[op_w] + 3, "z"))
       op->operand[0].val = X87_CONST_Z;
     else
@@ -1361,10 +1401,25 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
   case OP_FISUB:
   case OP_FIDIVR:
   case OP_FISUBR:
+  case OP_FCHS:
+  case OP_FCOS:
+  case OP_FSIN:
+  case OP_FSQRT:
+  case OP_FXCH:
     op->regmask_src |= mxST0;
     op->regmask_dst |= mxST0;
     break;
 
+  case OP_FPATAN:
+  case OP_FYL2X:
+    op->regmask_src |= mxST0 | mxST1;
+    op->regmask_dst |= mxST0;
+    break;
+
+  case OP_FPTAN:
+    aerr("TODO\n");
+    break;
+
   default:
     break;
   }
@@ -2157,7 +2212,7 @@ static char *out_src_opr_u32(char *buf, size_t buf_size,
 }
 
 static char *out_src_opr_float(char *buf, size_t buf_size,
-  struct parsed_op *po, struct parsed_opr *popr)
+  struct parsed_op *po, struct parsed_opr *popr, int need_float_stack)
 {
   const char *cast = NULL;
   char tmp[256];
@@ -2167,7 +2222,15 @@ static char *out_src_opr_float(char *buf, size_t buf_size,
     if (popr->reg < xST0 || popr->reg > xST7)
       ferr(po, "bad reg: %d\n", popr->reg);
 
-    snprintf(buf, buf_size, "f_st%d", popr->reg - xST0);
+    if (need_float_stack) {
+      if (popr->reg == xST0)
+        snprintf(buf, buf_size, "f_st[f_stp & 7]");
+      else
+        snprintf(buf, buf_size, "f_st[(f_stp + %d) & 7]",
+          popr->reg - xST0);
+    }
+    else
+      snprintf(buf, buf_size, "f_st%d", popr->reg - xST0);
     break;
 
   case OPT_REGMEM:
@@ -2196,10 +2259,10 @@ static char *out_src_opr_float(char *buf, size_t buf_size,
 }
 
 static char *out_dst_opr_float(char *buf, size_t buf_size,
-  struct parsed_op *po, struct parsed_opr *popr)
+  struct parsed_op *po, struct parsed_opr *popr, int need_float_stack)
 {
   // same?
-  return out_src_opr_float(buf, buf_size, po, popr);
+  return out_src_opr_float(buf, buf_size, po, popr, need_float_stack);
 }
 
 static void out_test_for_cc(char *buf, size_t buf_size,
@@ -3450,17 +3513,31 @@ static int get_pp_arg_regmask_src(const struct parsed_proto *pp)
 
 static int get_pp_arg_regmask_dst(const struct parsed_proto *pp)
 {
+  int regmask = 0;
+  int i, reg;
+
+  if (pp->has_retreg) {
+    for (i = 0; i < pp->argc; i++) {
+      if (pp->arg[i].type.is_retreg) {
+        reg = char_array_i(regs_r32,
+                ARRAY_SIZE(regs_r32), pp->arg[i].reg);
+        ferr_assert(ops, reg >= 0);
+        regmask |= 1 << reg;
+      }
+    }
+  }
+
   if (strstr(pp->ret_type.name, "int64"))
-    return (1 << xAX) | (1 << xDX);
+    return regmask | (1 << xAX) | (1 << xDX);
   if (IS(pp->ret_type.name, "float")
    || IS(pp->ret_type.name, "double"))
   {
-    return mxST0;
+    return regmask | mxST0;
   }
   if (strcasecmp(pp->ret_type.name, "void") == 0)
-    return 0;
+    return regmask;
 
-  return mxAX;
+  return regmask | mxAX;
 }
 
 static void resolve_branches_parse_calls(int opcnt)
@@ -3472,7 +3549,9 @@ static void resolve_branches_parse_calls(int opcnt)
     unsigned int regmask_src;
     unsigned int regmask_dst;
   } pseudo_ops[] = {
-    { "__ftol", OPP_FTOL, OPF_FPOP, mxST0, mxAX | mxDX },
+    { "__allshl", OPP_ALLSHL, OPF_DATA, mxAX|mxDX|mxCX, mxAX|mxDX },
+    { "__allshr", OPP_ALLSHR, OPF_DATA, mxAX|mxDX|mxCX, mxAX|mxDX },
+    { "__ftol",   OPP_FTOL,   OPF_FPOP, mxST0, mxAX | mxDX },
   };
   const struct parsed_proto *pp_c;
   struct parsed_proto *pp;
@@ -3488,10 +3567,25 @@ static void resolve_branches_parse_calls(int opcnt)
     po->bt_i = -1;
     po->btj = NULL;
 
+    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;
+      po->pp = pp;
+    }
+
     if (po->op == OP_CALL) {
       pp = NULL;
 
-      if (po->operand[0].type == OPT_LABEL) {
+      if (po->pp != NULL)
+        pp = po->pp;
+      else if (po->operand[0].type == OPT_LABEL)
+      {
         tmpname = opr_name(po, 0);
         if (IS_START(tmpname, "loc_"))
           ferr(po, "call to loc_*\n");
@@ -3521,16 +3615,6 @@ static void resolve_branches_parse_calls(int opcnt)
           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)
@@ -3751,11 +3835,11 @@ static void scan_prologue_epilogue(int opcnt)
   {
     g_sp_frame = 1;
 
-    i++;
     do {
       for (; i < opcnt; i++)
         if (ops[i].flags & OPF_TAIL)
           break;
+
       j = i - 1;
       if (i == opcnt && (ops[j].flags & OPF_JMP)) {
         if (ops[j].bt_i != -1 || ops[j].btj != NULL)
@@ -3792,7 +3876,16 @@ static void scan_prologue_epilogue(int opcnt)
             || !IS(opr_name(&ops[j], 0), "esp")
             || ops[j].operand[1].type != OPT_CONST
             || ops[j].operand[1].val != g_stack_fsz)
+        {
+          if (ops[i].op == OP_CALL && ops[i].pp != NULL
+            && ops[i].pp->is_noreturn)
+          {
+            // noreturn tailcall with no epilogue
+            i++;
+            continue;
+          }
           ferr(&ops[j], "'add esp' expected\n");
+        }
 
         ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
         ops[j].operand[1].val = 0; // hack for stack arg scanner
@@ -3807,36 +3900,11 @@ static void scan_prologue_epilogue(int opcnt)
   }
 }
 
-static const struct parsed_proto *resolve_icall(int i, int opcnt,
-  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:
-  case OPT_LABEL:
-  case OPT_OFFSET:
-    pp = try_recover_pp(&ops[i], &ops[i].operand[0], &search_advice);
-    if (!search_advice)
-      break;
-    // fallthrough
-  default:
-    scan_for_call_type(i, &ops[i].operand[0], i + opcnt * 9, &pp,
-      pp_i, multi_src);
-    break;
-  }
-
-  return pp;
-}
-
 // find an instruction that changed opr before i op
 // *op_i must be set to -1 by the caller
-// *entry is set to 1 if one source is determined to be the caller
+// *is_caller is set to 1 if one source is determined to be g_func arg
 // returns 1 if found, *op_i is then set to origin
+// returns -1 if multiple origins are found
 static int resolve_origin(int i, const struct parsed_opr *opr,
   int magic, int *op_i, int *is_caller)
 {
@@ -4015,6 +4083,96 @@ static int try_resolve_const(int i, const struct parsed_opr *opr,
   return -1;
 }
 
+static const struct parsed_proto *resolve_icall(int i, int opcnt,
+  int *pp_i, int *multi_src)
+{
+  const struct parsed_proto *pp = NULL;
+  int search_advice = 0;
+  int offset = -1;
+  char name[256];
+  char s_reg[4];
+  int reg, len;
+  int ret;
+
+  *multi_src = 0;
+  *pp_i = -1;
+
+  switch (ops[i].operand[0].type) {
+  case OPT_REGMEM:
+    // try to resolve struct member calls
+    ret = sscanf(ops[i].operand[0].name, "%3s+%x%n",
+            s_reg, &offset, &len);
+    if (ret == 2 && len == strlen(ops[i].operand[0].name))
+    {
+      reg = char_array_i(regs_r32, ARRAY_SIZE(regs_r32), s_reg);
+      if (reg >= 0) {
+        struct parsed_opr opr = OPR_INIT(OPT_REG, OPLM_DWORD, reg);
+        int j = -1;
+        ret = resolve_origin(i, &opr, i + opcnt * 19, &j, NULL);
+        if (ret != 1)
+          break;
+        if (ops[j].op == OP_MOV && ops[j].operand[1].type == OPT_REGMEM
+          && ops[j].operand[0].lmod == OPLM_DWORD
+          && ops[j].pp == NULL) // no hint
+        {
+          // allow one simple dereference (directx)
+          reg = char_array_i(regs_r32, ARRAY_SIZE(regs_r32),
+                  ops[j].operand[1].name);
+          if (reg < 0)
+            break;
+          struct parsed_opr opr2 = OPR_INIT(OPT_REG, OPLM_DWORD, reg);
+          int k = -1;
+          ret = resolve_origin(j, &opr2, j + opcnt * 19, &k, NULL);
+          if (ret != 1)
+            break;
+          j = k;
+        }
+        if (ops[j].op != OP_MOV)
+          break;
+        if (ops[j].operand[0].lmod != OPLM_DWORD)
+          break;
+        if (ops[j].pp != NULL) {
+          // type hint in asm
+          pp = ops[j].pp;
+        }
+        else if (ops[j].operand[1].type == OPT_REGMEM) {
+          // allow 'hello[ecx]' - assume array of same type items
+          ret = sscanf(ops[j].operand[1].name, "%[^[][e%2s]",
+                  name, s_reg);
+          if (ret != 2)
+            break;
+          pp = proto_parse(g_fhdr, name, g_quiet_pp);
+        }
+        else if (ops[j].operand[1].type == OPT_LABEL)
+          pp = proto_parse(g_fhdr, ops[j].operand[1].name, g_quiet_pp);
+        else
+          break;
+        if (pp == NULL)
+          break;
+        if (pp->is_func || pp->is_fptr || !pp->type.is_struct) {
+          pp = NULL;
+          break;
+        }
+        pp = proto_lookup_struct(g_fhdr, pp->type.name, offset);
+      }
+      break;
+    }
+    // fallthrough
+  case OPT_LABEL:
+  case OPT_OFFSET:
+    pp = try_recover_pp(&ops[i], &ops[i].operand[0], &search_advice);
+    if (!search_advice)
+      break;
+    // fallthrough
+  default:
+    scan_for_call_type(i, &ops[i].operand[0], i + opcnt * 9, &pp,
+      pp_i, multi_src);
+    break;
+  }
+
+  return pp;
+}
+
 static struct parsed_proto *process_call_early(int i, int opcnt,
   int *adj_i)
 {
@@ -4201,9 +4359,11 @@ static int collect_call_args_early(struct parsed_op *po, int i,
     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 (!g_header_mode) {
+        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;
@@ -4546,7 +4706,6 @@ static void reg_use_pass(int i, int opcnt, unsigned char *cbits,
   int *regmask_init, int regmask_arg)
 {
   struct parsed_op *po;
-  unsigned int mask;
   int already_saved;
   int regmask_new;
   int regmask_op;
@@ -4646,20 +4805,15 @@ static void reg_use_pass(int i, int opcnt, unsigned char *cbits,
             po->regmask_dst &= ~(1 << xAX);
         }
       }
+
+      // not "full stack" mode and have something in stack
+      if (!(regmask_now & mxST7_2) && (regmask_now & mxST1_0))
+        ferr(po, "float stack is not empty on func call\n");
     }
 
     if (po->flags & OPF_NOREGS)
       continue;
 
-    if (po->flags & OPF_FPUSH) {
-      if (regmask_now & mxST1)
-        ferr(po, "TODO: FPUSH on active ST1\n");
-      if (regmask_now & mxST0)
-        po->flags |= OPF_FSHIFT;
-      mask = mxST0 | mxST1;
-      regmask_now = (regmask_now & ~mask) | ((regmask_now & mxST0) << 1);
-    }
-
     // if incomplete register is used, clear it on init to avoid
     // later use of uninitialized upper part in some situations
     if ((po->flags & OPF_DATA) && po->operand[0].type == OPT_REG
@@ -4679,21 +4833,6 @@ static void reg_use_pass(int i, int opcnt, unsigned char *cbits,
     if (g_bp_frame && !(po->flags & OPF_EBP_S))
       regmask_new &= ~(1 << xBP);
 
-    if (po->op == OP_CALL) {
-      // allow fastcall calls from anywhere, calee may be also sitting
-      // in some fastcall table even when it's not using reg args
-      if (regmask_new & po->regmask_src & (1 << xCX)) {
-        *regmask_init |= (1 << xCX);
-        regmask_now |= (1 << xCX);
-        regmask_new &= ~(1 << xCX);
-      }
-      if (regmask_new & po->regmask_src & (1 << xDX)) {
-        *regmask_init |= (1 << xDX);
-        regmask_now |= (1 << xDX);
-        regmask_new &= ~(1 << xDX);
-      }
-    }
-
     if (regmask_new != 0)
       fnote(po, "uninitialized reg mask: %x\n", regmask_new);
 
@@ -4707,23 +4846,39 @@ static void reg_use_pass(int i, int opcnt, unsigned char *cbits,
       }
     }
 
+    if (po->flags & OPF_FPUSH) {
+      if (regmask_now & mxST1)
+        regmask_now |= mxSTa; // switch to "full stack" mode
+      if (regmask_now & mxSTa)
+        po->flags |= OPF_FSHIFT;
+      if (!(regmask_now & mxST7_2)) {
+        regmask_now =
+          (regmask_now & ~mxST1_0) | ((regmask_now & mxST0) << 1);
+      }
+    }
+
     regmask_now |= regmask_op;
     *regmask |= regmask_now;
 
     // released regs
     if (po->flags & OPF_FPOP) {
-      mask = mxST0 | mxST1;
-      if (!(regmask_now & mask))
+      if ((regmask_now & mxSTa) == 0)
         ferr(po, "float pop on empty stack?\n");
-      if (regmask_now & mxST1)
+      if (regmask_now & (mxST7_2 | mxST1))
         po->flags |= OPF_FSHIFT;
-      regmask_now = (regmask_now & ~mask) | ((regmask_now & mxST1) >> 1);
+      if (!(regmask_now & mxST7_2)) {
+        regmask_now =
+          (regmask_now & ~mxST1_0) | ((regmask_now & mxST1) >> 1);
+      }
     }
 
     if (po->flags & OPF_TAIL) {
-      if (regmask_now & (mxST0 | mxST1))
+      if (!(regmask_now & mxST7_2) && (regmask_now & mxST1_0))
         ferr(po, "float regs on tail: %x\n", regmask_now);
-      return;
+
+      // there is support for "conditional tailcall", sort of
+      if (!(po->flags & OPF_CC))
+        return;
     }
   }
 }
@@ -4848,11 +5003,16 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
   unsigned int uval;
   int save_arg_vars[MAX_ARG_GRP] = { 0, };
   unsigned char cbits[MAX_OPS / 8];
-  int cond_vars = 0;
+  const char *float_type;
+  const char *float_st0;
+  const char *float_st1;
+  int need_float_stack = 0;
   int need_tmp_var = 0;
   int need_tmp64 = 0;
+  int cond_vars = 0;
   int had_decl = 0;
   int label_pending = 0;
+  int need_double = 0;
   int regmask_save = 0; // regs saved/restored in this func
   int regmask_arg;      // regs from this function args (fastcall, etc)
   int regmask_ret;      // regs needed on ret
@@ -4862,6 +5022,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
   int regmask = 0;      // used regs
   int pfomask = 0;
   int found = 0;
+  int dead_dst;
   int no_output;
   int i, j, l;
   int arg;
@@ -4870,6 +5031,8 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
 
   g_bp_frame = g_sp_frame = g_stack_fsz = 0;
   g_stack_frame_used = 0;
+  if (g_sct_func_attr & SCTFA_CLEAR_REGS)
+    regmask_init = g_regmask_init;
 
   g_func_pp = proto_parse(fhdr, funcn, 0);
   if (g_func_pp == NULL)
@@ -4878,17 +5041,6 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
   regmask_arg = get_pp_arg_regmask_src(g_func_pp);
   regmask_ret = get_pp_arg_regmask_dst(g_func_pp);
 
-  if (g_func_pp->has_retreg) {
-    for (arg = 0; arg < g_func_pp->argc; arg++) {
-      if (g_func_pp->arg[arg].type.is_retreg) {
-        reg = char_array_i(regs_r32,
-                ARRAY_SIZE(regs_r32), g_func_pp->arg[arg].reg);
-        ferr_assert(ops, reg >= 0);
-        regmask_ret |= 1 << reg;
-      }
-    }
-  }
-
   // pass1:
   // - resolve all branches
   // - parse calls with labels
@@ -5007,7 +5159,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
   // - find POPs for PUSHes, rm both
   // - scan for all used registers
   memset(cbits, 0, sizeof(cbits));
-  reg_use_pass(0, opcnt, cbits, 0, &regmask,
+  reg_use_pass(0, opcnt, cbits, regmask_init, &regmask,
     0, &regmask_save, &regmask_init, regmask_arg);
 
   // pass7:
@@ -5078,16 +5230,23 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         cond_vars |= 1 << PFO_C;
     }
 
-    if (po->op == OP_CMPS || po->op == OP_SCAS) {
+    switch (po->op) {
+    case OP_CMPS:
+    case OP_SCAS:
       cond_vars |= 1 << PFO_Z;
-    }
-    else if (po->op == OP_MUL
-      || (po->op == OP_IMUL && po->operand_cnt == 1))
-    {
+      break;
+
+    case OP_MUL:
       if (po->operand[0].lmod == OPLM_DWORD)
         need_tmp64 = 1;
-    }
-    else if (po->op == OP_CALL) {
+      break;
+
+    case OP_IMUL:
+      if (po->operand_cnt == 1 && po->operand[0].lmod == OPLM_DWORD)
+        need_tmp64 = 1;
+      break;
+
+    case OP_CALL:
       // note: resolved non-reg calls are OPF_DONE already
       pp = po->pp;
       ferr_assert(po, pp != NULL);
@@ -5134,27 +5293,31 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         if (pp->argc_stack > 0)
           pp->is_stdcall = 1;
       }
-    }
-    else if (po->op == OP_MOV && po->operand[0].pp != NULL
-      && po->operand[1].pp != NULL)
-    {
-      // <var> = offset <something>
-      if ((po->operand[1].pp->is_func || po->operand[1].pp->is_fptr)
-        && !IS_START(po->operand[1].name, "off_"))
+      break;
+
+    case OP_MOV:
+      if (po->operand[0].pp != NULL && po->operand[1].pp != NULL)
       {
-        if (!po->operand[0].pp->is_fptr)
-          ferr(po, "%s not declared as fptr when it should be\n",
-            po->operand[0].name);
-        if (pp_cmp_func(po->operand[0].pp, po->operand[1].pp)) {
-          pp_print(buf1, sizeof(buf1), po->operand[0].pp);
-          pp_print(buf2, sizeof(buf2), po->operand[1].pp);
-          fnote(po, "var:  %s\n", buf1);
-          fnote(po, "func: %s\n", buf2);
-          ferr(po, "^ mismatch\n");
+        // <var> = offset <something>
+        if ((po->operand[1].pp->is_func || po->operand[1].pp->is_fptr)
+          && !IS_START(po->operand[1].name, "off_"))
+        {
+          if (!po->operand[0].pp->is_fptr)
+            ferr(po, "%s not declared as fptr when it should be\n",
+              po->operand[0].name);
+          if (pp_cmp_func(po->operand[0].pp, po->operand[1].pp)) {
+            pp_print(buf1, sizeof(buf1), po->operand[0].pp);
+            pp_print(buf2, sizeof(buf2), po->operand[1].pp);
+            fnote(po, "var:  %s\n", buf1);
+            fnote(po, "func: %s\n", buf2);
+            ferr(po, "^ mismatch\n");
+          }
         }
       }
-    }
-    else if (po->op == OP_DIV || po->op == OP_IDIV) {
+      break;
+
+    case OP_DIV:
+    case OP_IDIV:
       if (po->operand[0].lmod == OPLM_DWORD) {
         // 32bit division is common, look for it
         if (po->op == OP_DIV)
@@ -5168,21 +5331,47 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
       }
       else
         need_tmp_var = 1;
-    }
-    else if (po->op == OP_CLD)
+      break;
+
+    case OP_CLD:
       po->flags |= OPF_RMD | OPF_DONE;
-    else if (po->op == OPP_FTOL) {
+      break;
+
+    case OP_RCL:
+    case OP_RCR:
+    case OP_XCHG:
+      need_tmp_var = 1;
+      break;
+
+    case OP_FLD:
+      if (po->operand[0].lmod == OPLM_QWORD)
+        need_double = 1;
+      break;
+
+    case OPP_ALLSHL:
+    case OPP_ALLSHR:
+      need_tmp64 = 1;
+      break;
+
+    case OPP_FTOL: {
       struct parsed_opr opr = OPR_INIT(OPT_REG, OPLM_DWORD, xDX);
       j = -1;
       find_next_read(i + 1, opcnt, &opr, i + opcnt * 18, &j);
       if (j == -1)
         po->flags |= OPF_32BIT;
+      break;
     }
 
-    if (po->op == OP_RCL || po->op == OP_RCR || po->op == OP_XCHG)
-      need_tmp_var = 1;
+    default:
+      break;
+    }
   }
 
+  float_type = need_double ? "double" : "float";
+  need_float_stack = !!(regmask & mxST7_2);
+  float_st0 = need_float_stack ? "f_st[f_stp & 7]" : "f_st0";
+  float_st1 = need_float_stack ? "f_st[(f_stp + 1) & 7]" : "f_st1";
+
   // output starts here
 
   // define userstack size
@@ -5339,14 +5528,21 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
     }
   }
   // ... x87
-  if (regmask_now & 0xff0000) {
-    for (reg = 16; reg < 24; reg++) {
-      if (regmask_now & (1 << reg)) {
-        fprintf(fout, "  double f_st%d", reg - 16);
-        if (regmask_init & (1 << reg))
-          fprintf(fout, " = 0");
-        fprintf(fout, ";\n");
-        had_decl = 1;
+  if (need_float_stack) {
+    fprintf(fout, "  %s f_st[8];\n", float_type);
+    fprintf(fout, "  int f_stp = 0;\n");
+    had_decl = 1;
+  }
+  else {
+    if (regmask_now & 0xff0000) {
+      for (reg = 16; reg < 24; reg++) {
+        if (regmask_now & (1 << reg)) {
+          fprintf(fout, "  %s f_st%d", float_type, reg - 16);
+          if (regmask_init & (1 << reg))
+            fprintf(fout, " = 0");
+          fprintf(fout, ";\n");
+          had_decl = 1;
+        }
       }
     }
   }
@@ -5404,6 +5600,24 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
   if (had_decl)
     fprintf(fout, "\n");
 
+  // do stack clear, if needed
+  if (g_sct_func_attr & SCTFA_CLEAR_SF) {
+    fprintf(fout, "  ");
+    if (g_stack_clear_len != 0) {
+      if (g_stack_clear_len <= 4) {
+        for (i = 0; i < g_stack_clear_len; i++)
+          fprintf(fout, "sf.d[%d] = ", g_stack_clear_start + i);
+        fprintf(fout, "0;\n");
+      }
+      else {
+        fprintf(fout, "memset(&sf[%d], 0, %d);\n",
+          g_stack_clear_start, g_stack_clear_len * 4);
+      }
+    }
+    else
+      fprintf(fout, "memset(&sf, 0, sizeof(sf));\n");
+  }
+
   if (g_func_pp->is_vararg) {
     if (g_func_pp->argc_stack == 0)
       ferr(ops, "vararg func without stack args?\n");
@@ -5599,6 +5813,12 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         strcpy(g_comment, "cdq");
         break;
 
+      case OP_BSWAP:
+        assert_operand_cnt(1);
+        out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]);
+        fprintf(fout, "  %s = __builtin_bswap32(%s);", buf1, buf1);
+        break;
+
       case OP_LODS:
         if (po->flags & OPF_REP) {
           assert_operand_cnt(3);
@@ -6230,7 +6450,11 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
               fprintf(fout, "(u32)");
           }
           else if (po->regmask_dst & mxST0) {
-            fprintf(fout, "f_st0 = ");
+            ferr_assert(po, po->flags & OPF_FPUSH);
+            if (need_float_stack)
+              fprintf(fout, "f_st[--f_stp & 7] = ");
+            else
+              fprintf(fout, "f_st0 = ");
           }
         }
 
@@ -6449,59 +6673,110 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         no_output = 1;
         break;
 
+      // pseudo ops
+      case OPP_ALLSHL:
+      case OPP_ALLSHR:
+        fprintf(fout, "  tmp64 = ((u64)edx << 32) | eax;\n");
+        fprintf(fout, "  tmp64 = (s64)tmp64 %s= LOBYTE(ecx);\n",
+          po->op == OPP_ALLSHL ? "<<" : ">>");
+        fprintf(fout, "  edx = tmp64 >> 32; eax = tmp64;");
+        strcat(g_comment, po->op == OPP_ALLSHL
+          ? " allshl" : " allshr");
+        break;
+
       // x87
       case OP_FLD:
-        if (po->flags & OPF_FSHIFT)
-          fprintf(fout, "  f_st1 = f_st0;\n");
-        if (po->operand[0].type == OPT_REG
-          && po->operand[0].reg == xST0)
-        {
-          strcat(g_comment, " fld st");
-          break;
+        if (need_float_stack) {
+          out_src_opr_float(buf1, sizeof(buf1),
+            po, &po->operand[0], 1);
+          if (po->regmask_src & mxSTa) {
+            fprintf(fout, "  f_st[(f_stp - 1) & 7] = %s; f_stp--;",
+              buf1);
+          }
+          else
+            fprintf(fout, "  f_st[--f_stp & 7] = %s;", buf1);
+        }
+        else {
+          if (po->flags & OPF_FSHIFT)
+            fprintf(fout, "  f_st1 = f_st0;");
+          if (po->operand[0].type == OPT_REG
+            && po->operand[0].reg == xST0)
+          {
+            strcat(g_comment, " fld st");
+            break;
+          }
+          fprintf(fout, "  f_st0 = %s;",
+            out_src_opr_float(buf1, sizeof(buf1),
+              po, &po->operand[0], 0));
         }
-        fprintf(fout, "  f_st0 = %s;",
-          out_src_opr_float(buf1, sizeof(buf1), po, &po->operand[0]));
         strcat(g_comment, " fld");
         break;
 
       case OP_FILD:
-        if (po->flags & OPF_FSHIFT)
-          fprintf(fout, "  f_st1 = f_st0;\n");
-        fprintf(fout, "  f_st0 = (double)%s;",
-          out_src_opr(buf1, sizeof(buf1), po, &po->operand[0],
-            lmod_cast(po, po->operand[0].lmod, 1), 0));
+        out_src_opr(buf1, sizeof(buf1), po, &po->operand[0],
+          lmod_cast(po, po->operand[0].lmod, 1), 0);
+        snprintf(buf2, sizeof(buf2), "(%s)%s", float_type, buf1);
+        if (need_float_stack) {
+          fprintf(fout, "  f_st[--f_stp & 7] = %s;", buf2);
+        }
+        else {
+          if (po->flags & OPF_FSHIFT)
+            fprintf(fout, "  f_st1 = f_st0;");
+          fprintf(fout, "  f_st0 = %s;", buf2);
+        }
         strcat(g_comment, " fild");
         break;
 
       case OP_FLDc:
-        if (po->flags & OPF_FSHIFT)
-          fprintf(fout, "  f_st1 = f_st0;\n");
-        fprintf(fout, "  f_st0 = ");
+        if (need_float_stack)
+          fprintf(fout, "  f_st[--f_stp & 7] = ");
+        else {
+          if (po->flags & OPF_FSHIFT)
+            fprintf(fout, "  f_st1 = f_st0;");
+          fprintf(fout, "  f_st0 = ");
+        }
         switch (po->operand[0].val) {
-        case X87_CONST_1: fprintf(fout, "1.0;"); break;
-        case X87_CONST_Z: fprintf(fout, "0.0;"); break;
+        case X87_CONST_1:   fprintf(fout, "1.0;"); break;
+        case X87_CONST_LN2: fprintf(fout, "0.693147180559945;"); break;
+        case X87_CONST_Z:   fprintf(fout, "0.0;"); break;
         default: ferr(po, "TODO\n"); break;
         }
         break;
 
       case OP_FST:
-        if ((po->flags & OPF_FPOP) && po->operand[0].type == OPT_REG
-          && po->operand[0].reg == xST0)
-        {
-          no_output = 1;
-          break;
+        out_dst_opr_float(buf1, sizeof(buf1), po, &po->operand[0],
+          need_float_stack);
+        dead_dst = po->operand[0].type == OPT_REG
+          && po->operand[0].reg == xST0;
+        if (need_float_stack) {
+          if (!dead_dst)
+            fprintf(fout, "  %s = f_st[f_stp & 7];", buf1);
+          if (po->flags & OPF_FSHIFT)
+            fprintf(fout, "  f_stp++;");
         }
-        fprintf(fout, "  %s = f_st0;",
-          out_dst_opr_float(buf1, sizeof(buf1), po, &po->operand[0]));
-        if (po->flags & OPF_FSHIFT)
-          fprintf(fout, "\n  f_st0 = f_st1;");
-        strcat(g_comment, " fst");
+        else {
+          if (!dead_dst)
+            fprintf(fout, "  %s = f_st0;", buf1);
+          if (po->flags & OPF_FSHIFT)
+            fprintf(fout, "  f_st0 = f_st1;");
+        }
+        if (dead_dst && !(po->flags & OPF_FSHIFT))
+          no_output = 1;
+        else
+          strcat(g_comment, " fst");
         break;
 
       case OP_FADD:
       case OP_FDIV:
       case OP_FMUL:
       case OP_FSUB:
+        out_dst_opr_float(buf1, sizeof(buf1), po, &po->operand[0],
+          need_float_stack);
+        out_src_opr_float(buf2, sizeof(buf2), po, &po->operand[1],
+          need_float_stack);
+        dead_dst = (po->flags & OPF_FPOP)
+          && po->operand[0].type == OPT_REG
+          && po->operand[0].reg == xST0;
         switch (po->op) {
         case OP_FADD: j = '+'; break;
         case OP_FDIV: j = '/'; break;
@@ -6509,27 +6784,55 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         case OP_FSUB: j = '-'; break;
         default: j = 'x'; break;
         }
-        if (po->flags & OPF_FSHIFT) {
-          fprintf(fout, "  f_st0 = f_st1 %c f_st0;", j);
+        if (need_float_stack) {
+          if (!dead_dst)
+            fprintf(fout, "  %s %c= %s;", buf1, j, buf2);
+          if (po->flags & OPF_FSHIFT)
+            fprintf(fout, "  f_stp++;");
         }
         else {
-          fprintf(fout, "  %s %c= %s;",
-           out_dst_opr_float(buf1, sizeof(buf1), po, &po->operand[0]),
-           j,
-           out_src_opr_float(buf2, sizeof(buf2), po, &po->operand[1]));
+          if (po->flags & OPF_FSHIFT) {
+            // note: assumes only 2 regs handled
+            if (!dead_dst)
+              fprintf(fout, "  f_st0 = f_st1 %c f_st0;", j);
+            else
+              fprintf(fout, "  f_st0 = f_st1;");
+          }
+          else if (!dead_dst)
+            fprintf(fout, "  %s %c= %s;", buf1, j, buf2);
         }
+        no_output = (dead_dst && !(po->flags & OPF_FSHIFT));
         break;
 
       case OP_FDIVR:
       case OP_FSUBR:
-        if (po->flags & OPF_FSHIFT)
-          snprintf(buf1, sizeof(buf1), "f_st0");
-        else
-          out_dst_opr_float(buf1, sizeof(buf1), po, &po->operand[0]);
-        fprintf(fout, "  %s = %s %c %s;", buf1,
-          out_src_opr_float(buf2, sizeof(buf2), po, &po->operand[1]),
-          po->op == OP_FDIVR ? '/' : '-',
-          out_src_opr_float(buf3, sizeof(buf3), po, &po->operand[0]));
+        out_dst_opr_float(buf1, sizeof(buf1), po, &po->operand[0],
+          need_float_stack);
+        out_src_opr_float(buf2, sizeof(buf2), po, &po->operand[1],
+          need_float_stack);
+        out_src_opr_float(buf3, sizeof(buf3), po, &po->operand[0],
+          need_float_stack);
+        dead_dst = (po->flags & OPF_FPOP)
+          && po->operand[0].type == OPT_REG
+          && po->operand[0].reg == xST0;
+        j = po->op == OP_FDIVR ? '/' : '-';
+        if (need_float_stack) {
+          if (!dead_dst)
+            fprintf(fout, "  %s = %s %c %s;", buf1, buf2, j, buf3);
+          if (po->flags & OPF_FSHIFT)
+            fprintf(fout, "  f_stp++;");
+        }
+        else {
+          if (po->flags & OPF_FSHIFT) {
+            if (!dead_dst)
+              fprintf(fout, "  f_st0 = f_st0 %c f_st1;", j);
+            else
+              fprintf(fout, "  f_st0 = f_st1;");
+          }
+          else if (!dead_dst)
+            fprintf(fout, "  %s = %s %c %s;", buf1, buf2, j, buf3);
+        }
+        no_output = (dead_dst && !(po->flags & OPF_FSHIFT));
         break;
 
       case OP_FIADD:
@@ -6543,23 +6846,86 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
         case OP_FISUB: j = '-'; break;
         default: j = 'x'; break;
         }
-        fprintf(fout, "  f_st0 %c= (double)%s;", j,
+        fprintf(fout, "  %s %c= (%s)%s;", float_st0,
+          j, float_type,
           out_src_opr(buf1, sizeof(buf1), po, &po->operand[0],
             lmod_cast(po, po->operand[0].lmod, 1), 0));
         break;
 
       case OP_FIDIVR:
       case OP_FISUBR:
-        fprintf(fout, "  f_st0 = %s %c f_st0;",
-          out_src_opr_float(buf2, sizeof(buf2), po, &po->operand[1]),
-          po->op == OP_FIDIVR ? '/' : '-');
+        fprintf(fout, "  %s = %s %c %s;", float_st0,
+          out_src_opr_float(buf2, sizeof(buf2), po, &po->operand[1],
+            need_float_stack),
+          po->op == OP_FIDIVR ? '/' : '-', float_st0);
+        break;
+
+      case OP_FCHS:
+        fprintf(fout, "  %s = -%s;", float_st0, float_st0);
+        break;
+
+      case OP_FCOS:
+        fprintf(fout, "  %s = cos%s(%s);", float_st0,
+          need_double ? "" : "f", float_st0);
+        break;
+
+      case OP_FPATAN:
+        if (need_float_stack) {
+          fprintf(fout, "  %s = atan%s(%s / %s);", float_st1,
+            need_double ? "" : "f", float_st1, float_st0);
+          fprintf(fout, " f_stp++;");
+        }
+        else {
+          fprintf(fout, "  f_st0 = atan%s(f_st1 / f_st0);",
+            need_double ? "" : "f");
+        }
+        break;
+
+      case OP_FYL2X:
+        if (need_float_stack) {
+          fprintf(fout, "  %s = %s * log2%s(%s);", float_st1,
+            float_st1, need_double ? "" : "f", float_st0);
+          fprintf(fout, " f_stp++;");
+        }
+        else {
+          fprintf(fout, "  f_st0 = f_st1 * log2%s(f_st0);",
+            need_double ? "" : "f");
+        }
+        break;
+
+      case OP_FSIN:
+        fprintf(fout, "  %s = sin%s(%s);", float_st0,
+          need_double ? "" : "f", float_st0);
+        break;
+
+      case OP_FSQRT:
+        fprintf(fout, "  %s = sqrt%s(%s);", float_st0,
+          need_double ? "" : "f", float_st0);
+        break;
+
+      case OP_FXCH:
+        dead_dst = po->operand[0].type == OPT_REG
+          && po->operand[0].reg == xST0;
+        if (!dead_dst) {
+          out_src_opr_float(buf1, sizeof(buf1), po, &po->operand[0],
+            need_float_stack);
+          fprintf(fout, "  { %s t = %s; %s = %s; %s = t; }", float_type,
+            float_st0, float_st0, buf1, buf1);
+          strcat(g_comment, " fxch");
+        }
+        else
+          no_output = 1;
         break;
 
       case OPP_FTOL:
         ferr_assert(po, po->flags & OPF_32BIT);
-        fprintf(fout, "  eax = (s32)f_st0;");
-        if (po->flags & OPF_FSHIFT)
-          fprintf(fout, "\n  f_st0 = f_st1;");
+        fprintf(fout, "  eax = (s32)%s;", float_st0);
+        if (po->flags & OPF_FSHIFT) {
+          if (need_float_stack)
+            fprintf(fout, " f_stp++;");
+          else
+            fprintf(fout, " f_st0 = f_st1;");
+        }
         strcat(g_comment, " ftol");
         break;
 
@@ -6682,10 +7048,13 @@ static struct scanned_var {
 } *hg_vars;
 static int hg_var_cnt;
 
+static char **hg_refs;
+static int hg_ref_cnt;
+
 static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
   int count);
 
-struct func_prototype *hg_fp_add(const char *funcn)
+static struct func_prototype *hg_fp_add(const char *funcn)
 {
   struct func_prototype *fp;
 
@@ -6747,6 +7116,19 @@ static int hg_fp_cmp_id(const void *p1_, const void *p2_)
 }
 #endif
 
+static void hg_ref_add(const char *name)
+{
+  if ((hg_ref_cnt & 0xff) == 0) {
+    hg_refs = realloc(hg_refs, sizeof(hg_refs[0]) * (hg_ref_cnt + 0x100));
+    my_assert_not(hg_refs, NULL);
+    memset(hg_refs + hg_ref_cnt, 0, sizeof(hg_refs[0]) * 0x100);
+  }
+
+  hg_refs[hg_ref_cnt] = strdup(name);
+  my_assert_not(hg_refs[hg_ref_cnt], NULL);
+  hg_ref_cnt++;
+}
+
 // recursive register dep pass
 // - track saved regs (part 2)
 // - try to figure out arg-regs
@@ -7060,7 +7442,7 @@ static void gen_hdr(const char *funcn, int opcnt)
       fp->argc_stack--;
   }
 
-  fp->regmask_dep = regmask_dep & ~(1 << xSP);
+  fp->regmask_dep = regmask_dep & ~((1 << xSP) | mxSTa);
   fp->has_ret = has_ret;
 #if 0
   printf("// has_ret %d, regmask_dep %x\n",
@@ -7101,6 +7483,24 @@ static void hg_fp_resolve_deps(struct func_prototype *fp)
   }
 }
 
+// make all thiscall/edx arg functions referenced from .data fastcall
+static void do_func_refs_from_data(void)
+{
+  struct func_prototype *fp, fp_s;
+  int i;
+
+  for (i = 0; i < hg_ref_cnt; i++) {
+    strcpy(fp_s.name, hg_refs[i]);
+    fp = bsearch(&fp_s, hg_fp, hg_fp_cnt,
+      sizeof(hg_fp[0]), hg_fp_cmp_name);
+    if (fp == NULL)
+      continue;
+
+    if (fp->argc_stack != 0 && (fp->regmask_dep & (mxCX | mxDX)))
+      fp->regmask_dep |= mxCX | mxDX;
+  }
+}
+
 static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
   int count)
 {
@@ -7108,7 +7508,7 @@ static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
   char *p, namebuf[NAMELEN];
   const char *name;
   int regmask_dep;
-  int argc_stack;
+  int argc_normal;
   int j, arg;
 
   for (; count > 0; count--, fp++) {
@@ -7146,18 +7546,25 @@ static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
     }
 
     regmask_dep = fp->regmask_dep;
-    argc_stack = fp->argc_stack;
+    argc_normal = fp->argc_stack;
 
     fprintf(fout, "%-5s", fp->pp ? fp->pp->ret_type.name :
       (fp->has_ret ? "int" : "void"));
-    if (regmask_dep && (fp->is_stdcall || argc_stack == 0)
-      && (regmask_dep & ~((1 << xCX) | (1 << xDX))) == 0)
+    if (regmask_dep && (fp->is_stdcall || fp->argc_stack > 0)
+      && (regmask_dep & ~mxCX) == 0)
+    {
+      fprintf(fout, "/*__thiscall*/  ");
+      argc_normal++;
+      regmask_dep = 0;
+    }
+    else if (regmask_dep && (fp->is_stdcall || fp->argc_stack == 0)
+      && (regmask_dep & ~(mxCX | mxDX)) == 0)
     {
       fprintf(fout, "  __fastcall    ");
-      if (!(regmask_dep & (1 << xDX)) && argc_stack == 0)
-        argc_stack = 1;
+      if (!(regmask_dep & (1 << xDX)) && fp->argc_stack == 0)
+        argc_normal = 1;
       else
-        argc_stack += 2;
+        argc_normal += 2;
       regmask_dep = 0;
     }
     else if (regmask_dep && !fp->is_stdcall) {
@@ -7187,7 +7594,7 @@ static void output_hdr_fp(FILE *fout, const struct func_prototype *fp,
       }
     }
 
-    for (j = 0; j < argc_stack; j++) {
+    for (j = 0; j < argc_normal; j++) {
       arg++;
       if (arg != 1)
         fprintf(fout, ", ");
@@ -7239,6 +7646,9 @@ static void output_hdr(FILE *fout)
   for (i = 0; i < hg_fp_cnt; i++)
     hg_fp_resolve_deps(&hg_fp[i]);
 
+  // adjust functions referenced from data segment
+  do_func_refs_from_data();
+
   // note: messes up .proto ptr, don't use
   //qsort(hg_fp, hg_fp_cnt, sizeof(hg_fp[0]), hg_fp_cmp_id);
 
@@ -7282,7 +7692,7 @@ static char *next_word_s(char *w, size_t wsize, char *s)
   s = sskip(s);
 
   i = 0;
-  if (*s == '\'') {
+  if (*s == '\'' && s[1] != '\r' && s[1] != '\n') {
     w[0] = s[0];
     for (i = 1; i < wsize - 1; i++) {
       if (s[i] == 0) {
@@ -7308,11 +7718,83 @@ static char *next_word_s(char *w, size_t wsize, char *s)
   return s + i;
 }
 
-static void scan_variables(FILE *fasm)
+static int cmpstringp(const void *p1, const void *p2)
+{
+  return strcmp(*(char * const *)p1, *(char * const *)p2);
+}
+
+static int is_xref_needed(char *p, char **rlist, int rlist_len)
+{
+  char *p2;
+
+  p = sskip(p);
+  if (strstr(p, "..."))
+    // unable to determine, assume needed
+    return 1;
+
+  if (*p == '.') // .text, .data, ...
+    // ref from other data or non-function -> no
+    return 0;
+
+  p2 = strpbrk(p, "+:\r\n\x18");
+  if (p2 != NULL)
+    *p2 = 0;
+  if (bsearch(&p, rlist, rlist_len, sizeof(rlist[0]), cmpstringp))
+    // referenced from removed code
+    return 0;
+
+  return 1;
+}
+
+static int ida_xrefs_show_need(FILE *fasm, char *p,
+  char **rlist, int rlist_len)
+{
+  int found_need = 0;
+  char line[256];
+  long pos;
+
+  p = strrchr(p, ';');
+  if (p != NULL && *p == ';' && IS_START(p + 2, "DATA XREF: ")) {
+    p += 13;
+    if (is_xref_needed(p, rlist, rlist_len))
+      return 1;
+  }
+
+  pos = ftell(fasm);
+  while (1)
+  {
+    if (!my_fgets(line, sizeof(line), fasm))
+      break;
+    // non-first line is always indented
+    if (!my_isblank(line[0]))
+      break;
+
+    // should be no content, just comment
+    p = sskip(line);
+    if (*p != ';')
+      break;
+
+    p = strrchr(p, ';');
+    p += 2;
+    // it's printed once, but no harm to check again
+    if (IS_START(p, "DATA XREF: "))
+      p += 11;
+
+    if (is_xref_needed(p, rlist, rlist_len)) {
+      found_need = 1;
+      break;
+    }
+  }
+  fseek(fasm, pos, SEEK_SET);
+  return found_need;
+}
+
+static void scan_variables(FILE *fasm, char **rlist, int rlist_len)
 {
   struct scanned_var *var;
   char line[256] = { 0, };
-  char words[3][256];
+  char words[4][256];
+  int no_identifier;
   char *p = NULL;
   int wordc;
   int l;
@@ -7351,8 +7833,7 @@ static void scan_variables(FILE *fasm)
       asmln++;
 
       p = line;
-      if (my_isblank(*p))
-        continue;
+      no_identifier = my_isblank(*p);
 
       p = sskip(p);
       if (*p == 0 || *p == ';')
@@ -7372,11 +7853,21 @@ static void scan_variables(FILE *fasm)
       if (wordc < 2)
         continue;
 
+      if (no_identifier) {
+        if (wordc >= 3 && IS(words[0], "dd") && IS(words[1], "offset"))
+          hg_ref_add(words[2]);
+        continue;
+      }
+
       if (IS_START(words[0], "__IMPORT_DESCRIPTOR_")) {
         // when this starts, we don't need anything from this section
         break;
       }
 
+      // check refs comment(s)
+      if (!ida_xrefs_show_need(fasm, p, rlist, rlist_len))
+        continue;
+
       if ((hg_var_cnt & 0xff) == 0) {
         hg_vars = realloc(hg_vars, sizeof(hg_vars[0])
                    * (hg_var_cnt + 0x100));
@@ -7404,8 +7895,11 @@ static void scan_variables(FILE *fasm)
         continue;
       }
 
-      if      (IS(words[1], "dd"))
+      if      (IS(words[1], "dd")) {
         var->lmod = OPLM_DWORD;
+        if (wordc >= 4 && IS(words[2], "offset"))
+          hg_ref_add(words[3]);
+      }
       else if (IS(words[1], "dw"))
         var->lmod = OPLM_WORD;
       else if (IS(words[1], "db")) {
@@ -7475,11 +7969,6 @@ static int cmp_chunks(const void *p1, const void *p2)
   return strcmp(c1->name, c2->name);
 }
 
-static int cmpstringp(const void *p1, const void *p2)
-{
-  return strcmp(*(char * const *)p1, *(char * const *)p2);
-}
-
 static void scan_ahead(FILE *fasm)
 {
   char words[2][256];
@@ -7677,7 +8166,7 @@ int main(int argc, char *argv[])
   }
 
   if (g_header_mode)
-    scan_variables(fasm);
+    scan_variables(fasm, rlist, rlist_len);
 
   while (my_fgets(line, sizeof(line), fasm))
   {
@@ -7731,6 +8220,46 @@ int main(int argc, char *argv[])
           }
         }
       }
+      else if (p[2] == 's' && IS_START(p, "; sctattr:"))
+      {
+        static const char *attrs[] = {
+          "clear_sf",
+          "clear_regmask",
+        };
+
+        // parse manual attribute-list comment
+        g_sct_func_attr = 0;
+        p = sskip(p + 10);
+
+        for (; *p != 0; p = sskip(p)) {
+          for (i = 0; i < ARRAY_SIZE(attrs); i++) {
+            if (!strncmp(p, attrs[i], strlen(attrs[i]))) {
+              g_sct_func_attr |= 1 << i;
+              p += strlen(attrs[i]);
+              break;
+            }
+          }
+          if (*p == '=') {
+            j = ret = 0;
+            if (i == 0)
+              // clear_sf=start,len (in dwords)
+              ret = sscanf(p, "=%d,%d%n", &g_stack_clear_start,
+                      &g_stack_clear_len, &j);
+            else if (i == 1)
+              // clear_regmask=<mask>
+              ret = sscanf(p, "=%x%n", &g_regmask_init, &j) + 1;
+            if (ret < 2) {
+              anote("unparsed attr value: %s\n", p);
+              break;
+            }
+            p += j;
+          }
+          else if (i == ARRAY_SIZE(attrs)) {
+            anote("unparsed sct attr: %s\n", p);
+            break;
+          }
+        }
+      }
       else if (p[2] == 'S' && IS_START(p, "; START OF FUNCTION CHUNK FOR "))
       {
         p += 30;
@@ -7899,6 +8428,10 @@ do_pending_endp:
       pending_endp = 0;
       in_func = 0;
       g_ida_func_attr = 0;
+      g_sct_func_attr = 0;
+      g_stack_clear_start = 0;
+      g_stack_clear_len = 0;
+      g_regmask_init = 0;
       skip_warned = 0;
       g_skip_func = 0;
       g_func[0] = 0;
@@ -8065,11 +8598,8 @@ do_pending_endp:
 
     parse_op(&ops[pi], words, wordc);
 
-    if (sctproto != NULL) {
-      if (ops[pi].op == OP_CALL || ops[pi].op == OP_JMP)
-        ops[pi].datap = sctproto;
-      sctproto = NULL;
-    }
+    ops[pi].datap = sctproto;
+    sctproto = NULL;
     pi++;
   }