+ if (po->flags & OPF_JMP) {
+ if (po->flags & OPF_RMD)
+ continue;
+ if (po->op == OP_CALL)
+ return -1;
+
+ if (po->btj != NULL) {
+ for (j = 0; j < po->btj->count; j++) {
+ check_i(po, po->btj->d[j].bt_i);
+ ret |= scan_for_pop_const_r(po->btj->d[j].bt_i, opcnt, magic,
+ push_i, is_probe);
+ if (ret < 0)
+ return ret;
+ }
+ return ret;
+ }
+
+ check_i(po, po->bt_i);
+ if (po->flags & OPF_CJMP) {
+ ret |= scan_for_pop_const_r(po->bt_i, opcnt, magic, push_i,
+ is_probe);
+ if (ret < 0)
+ return ret;
+ }
+ else {
+ i = po->bt_i - 1;
+ }
+ continue;
+ }
+
+ if ((po->flags & (OPF_TAIL|OPF_RSAVE)) || po->op == OP_PUSH)
+ return -1;
+
+ if (g_labels[i] != NULL) {
+ // all refs must be visited
+ lr = &g_label_refs[i];
+ for (; lr != NULL; lr = lr->next) {
+ check_i(po, lr->i);
+ if (ops[lr->i].cc_scratch != magic)
+ return -1;
+ }
+ if (i > 0 && !LAST_OP(i - 1) && ops[i - 1].cc_scratch != magic)
+ return -1;
+ }
+
+ if (po->op == OP_POP)
+ {
+ if (po->flags & (OPF_RMD|OPF_DONE))
+ return -1;
+
+ if (!is_probe) {
+ po->flags |= OPF_DONE;
+ po->datap = &ops[push_i];
+ }
+ return 1;
+ }
+ }
+
+ return -1;
+}
+
+static void scan_for_pop_const(int i, int opcnt, int magic)
+{
+ int ret;
+
+ ret = scan_for_pop_const_r(i + 1, opcnt, magic, i, 1);
+ if (ret == 1) {
+ ops[i].flags |= OPF_RMD | OPF_DONE;
+ scan_for_pop_const_r(i + 1, opcnt, magic + 1, i, 0);
+ }
+}
+
+// check if all branch targets within a marked path are also marked
+// note: the path checked must not be empty or end with a branch
+static int check_path_branches(int opcnt, int magic)
+{
+ struct parsed_op *po;
+ int i, j;
+
+ for (i = 0; i < opcnt; i++) {
+ po = &ops[i];
+ if (po->cc_scratch != magic)
+ continue;
+
+ if (po->flags & OPF_JMP) {
+ if ((po->flags & OPF_RMD) || po->op == OP_CALL)
+ continue;
+
+ if (po->btj != NULL) {
+ for (j = 0; j < po->btj->count; j++) {
+ check_i(po, po->btj->d[j].bt_i);
+ if (ops[po->btj->d[j].bt_i].cc_scratch != magic)
+ return 0;
+ }
+ }
+
+ check_i(po, po->bt_i);
+ if (ops[po->bt_i].cc_scratch != magic)
+ return 0;
+ if ((po->flags & OPF_CJMP) && ops[i + 1].cc_scratch != magic)
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+// scan for multiple pushes for given pop
+static int scan_pushes_for_pop_r(int i, int magic, int pop_i,
+ int is_probe)
+{
+ int reg = ops[pop_i].operand[0].reg;
+ struct parsed_op *po;
+ struct label_ref *lr;
+ int ret = 0;
+
+ ops[i].cc_scratch = magic;
+
+ while (1)
+ {
+ if (g_labels[i] != NULL) {
+ lr = &g_label_refs[i];
+ for (; lr != NULL; lr = lr->next) {
+ check_i(&ops[i], lr->i);
+ ret |= scan_pushes_for_pop_r(lr->i, magic, pop_i, is_probe);
+ if (ret < 0)
+ return ret;
+ }
+ if (i > 0 && LAST_OP(i - 1))
+ return ret;
+ }
+
+ i--;
+ if (i < 0)
+ break;
+
+ if (ops[i].cc_scratch == magic)
+ return ret;
+ ops[i].cc_scratch = magic;
+
+ po = &ops[i];
+ if (po->op == OP_CALL)
+ return -1;
+ if ((po->flags & (OPF_TAIL|OPF_RSAVE)) || po->op == OP_POP)
+ return -1;
+
+ if (po->op == OP_PUSH)
+ {
+ if (po->datap != NULL)
+ return -1;
+ if (po->operand[0].type == OPT_REG && po->operand[0].reg == reg)
+ // leave this case for reg save/restore handlers
+ return -1;
+
+ if (!is_probe) {
+ po->flags |= OPF_PPUSH | OPF_DONE;
+ po->datap = &ops[pop_i];
+ }
+ return 1;
+ }
+ }
+
+ return -1;
+}
+
+static void scan_pushes_for_pop(int i, int opcnt, int *regmask_pp)
+{
+ int magic = i + opcnt * 14;
+ int ret;
+
+ ret = scan_pushes_for_pop_r(i, magic, i, 1);
+ if (ret == 1) {
+ ret = check_path_branches(opcnt, magic);
+ if (ret == 1) {
+ ops[i].flags |= OPF_PPUSH | OPF_DONE;
+ *regmask_pp |= 1 << ops[i].operand[0].reg;
+ scan_pushes_for_pop_r(i, magic + 1, i, 0);
+ }
+ }
+}
+
+static void scan_propagate_df(int i, int opcnt)
+{
+ struct parsed_op *po = &ops[i];
+ int j;
+
+ for (; i < opcnt; i++) {
+ po = &ops[i];
+ if (po->flags & OPF_DF)
+ return; // already resolved
+ po->flags |= OPF_DF;
+
+ if (po->op == OP_CALL)
+ ferr(po, "call with DF set?\n");
+
+ if (po->flags & OPF_JMP) {
+ if (po->btj != NULL) {
+ // jumptable
+ for (j = 0; j < po->btj->count; j++) {
+ check_i(po, po->btj->d[j].bt_i);
+ scan_propagate_df(po->btj->d[j].bt_i, opcnt);
+ }
+ return;
+ }
+
+ if (po->flags & OPF_RMD)
+ continue;
+ check_i(po, po->bt_i);
+ if (po->flags & OPF_CJMP)
+ scan_propagate_df(po->bt_i, opcnt);
+ else
+ i = po->bt_i - 1;
+ continue;
+ }
+
+ if (po->flags & OPF_TAIL)
+ break;
+
+ if (po->op == OP_CLD) {
+ po->flags |= OPF_RMD | OPF_DONE;
+ return;
+ }
+ }
+
+ ferr(po, "missing DF clear?\n");
+}
+
+// is operand 'opr' referenced by parsed_op 'po'?
+static int is_opr_referenced(const struct parsed_opr *opr,
+ const struct parsed_op *po)
+{
+ int i, mask;
+
+ if (opr->type == OPT_REG) {
+ mask = po->regmask_dst | po->regmask_src;
+ if (po->op == OP_CALL)
+ mask |= (1 << xAX) | (1 << xCX) | (1 << xDX);
+ if ((1 << opr->reg) & mask)
+ return 1;
+ else
+ return 0;
+ }
+
+ for (i = 0; i < po->operand_cnt; i++)
+ if (IS(po->operand[0].name, opr->name))
+ return 1;
+
+ return 0;
+}
+
+// is operand 'opr' read by parsed_op 'po'?
+static int is_opr_read(const struct parsed_opr *opr,
+ const struct parsed_op *po)
+{
+ if (opr->type == OPT_REG) {
+ if (po->regmask_src & (1 << opr->reg))
+ return 1;
+ else
+ return 0;
+ }
+
+ // yes I'm lazy
+ return 0;
+}
+
+// is operand 'opr' modified by parsed_op 'po'?
+static int is_opr_modified(const struct parsed_opr *opr,
+ const struct parsed_op *po)
+{
+ int mask;
+
+ if (opr->type == OPT_REG) {
+ if (po->op == OP_CALL) {
+ mask = po->regmask_dst;
+ mask |= (1 << xAX) | (1 << xCX) | (1 << xDX); // ?
+ if (mask & (1 << opr->reg))
+ return 1;
+ else
+ return 0;
+ }
+
+ if (po->regmask_dst & (1 << opr->reg))
+ return 1;
+ else
+ return 0;
+ }
+
+ return IS(po->operand[0].name, opr->name);
+}
+
+// is any operand of parsed_op 'po_test' modified by parsed_op 'po'?
+static int is_any_opr_modified(const struct parsed_op *po_test,
+ const struct parsed_op *po, int c_mode)
+{
+ int mask;
+ int i;
+
+ if ((po->flags & OPF_RMD) || !(po->flags & OPF_DATA))
+ return 0;
+
+ if (po_test->operand_cnt == 1 && po_test->operand[0].type == OPT_CONST)
+ return 0;