+static int collect_call_args(struct parsed_op *po, int i,
+ struct parsed_proto *pp, int *save_arg_vars, int arg,
+ int need_op_saving, int may_reuse)
+{
+ struct parsed_proto *pp_tmp;
+ struct label_ref *lr;
+ int need_to_save_current;
+ int ret = 0;
+ int j;
+
+ if (i < 0)
+ ferr(po, "no refs for '%s'?\n", g_labels[i]);
+
+ for (; arg < pp->argc; arg++)
+ if (pp->arg[arg].reg == NULL)
+ break;
+
+ for (j = i; j >= 0 && arg < pp->argc; )
+ {
+ if (g_labels[j][0] != 0) {
+ lr = &g_label_refs[j];
+ if (lr->next != NULL)
+ need_op_saving = 1;
+ for (; lr->next; lr = lr->next) {
+ if ((ops[lr->i].flags & (OPF_JMP|OPF_CC)) != OPF_JMP)
+ may_reuse = 1;
+ ret |= collect_call_args(po, lr->i, pp, save_arg_vars,
+ arg, need_op_saving, may_reuse);
+ }
+
+ if ((ops[lr->i].flags & (OPF_JMP|OPF_CC)) != OPF_JMP)
+ may_reuse = 1;
+ if (j > 0 && LAST_OP(j - 1)) {
+ // follow last branch in reverse
+ j = lr->i;
+ continue;
+ }
+ need_op_saving = 1;
+ ret |= collect_call_args(po, lr->i, pp, save_arg_vars,
+ arg, need_op_saving, may_reuse);
+ }
+ j--;
+
+ if (ops[j].op == OP_CALL)
+ {
+ pp_tmp = ops[j].datap;
+ if (pp_tmp == NULL)
+ ferr(po, "arg collect hit unparsed call\n");
+ if (may_reuse && pp_tmp->argc_stack > 0)
+ ferr(po, "arg collect %d/%d hit '%s' with %d stack args\n",
+ arg, pp->argc, opr_name(&ops[j], 0), pp_tmp->argc_stack);
+ }
+ else if (ops[j].op == OP_ADD && ops[j].operand[0].reg == xSP) {
+ ferr(po, "arg collect %d/%d hit esp adjust\n",
+ arg, pp->argc);
+ }
+ else if (ops[j].op == OP_POP) {
+ ferr(po, "arg collect %d/%d hit pop\n", arg, pp->argc);
+ }
+ else if ((ops[j].flags & (OPF_JMP|OPF_CC)) == (OPF_JMP|OPF_CC))
+ {
+ may_reuse = 1;
+ }
+ else if (ops[j].op == OP_PUSH && !(ops[j].flags & OPF_FARG))
+ {
+ pp->arg[arg].datap = &ops[j];
+ need_to_save_current = 0;
+ if (!need_op_saving) {
+ ret = scan_for_mod(&ops[j], j + 1, i);
+ need_to_save_current = (ret >= 0);
+ }
+ if (need_op_saving || need_to_save_current) {
+ // mark this push as one that needs operand saving
+ ops[j].flags &= ~OPF_RMD;
+ if (ops[j].argnum == 0) {
+ ops[j].argnum = arg + 1;
+ *save_arg_vars |= 1 << arg;
+ }
+ else if (ops[j].argnum < arg + 1)
+ ferr(&ops[j], "argnum conflict (%d<%d) for '%s'\n",
+ ops[j].argnum, arg + 1, pp->name);
+ }
+ else if (ops[j].argnum == 0)
+ ops[j].flags |= OPF_RMD;
+
+ // some PUSHes are reused by different calls on other branches,
+ // but that can't happen if we didn't branch, so they
+ // can be removed from future searches (handles nested calls)
+ if (!may_reuse)
+ ops[j].flags |= OPF_FARG;
+
+ // next arg
+ for (arg++; arg < pp->argc; arg++)
+ if (pp->arg[arg].reg == NULL)
+ break;
+ }
+ }
+
+ if (arg < pp->argc) {
+ ferr(po, "arg collect failed for '%s': %d/%d\n",
+ pp->name, arg, pp->argc);
+ ret = -1;
+ }
+ return ret;
+}
+