+static int cop2_is_stalling_op(int i, int *cycles)
+{
+ if (dops[i].opcode == 0x3a) { // SWC2
+ *cycles = 0;
+ return 1;
+ }
+ if (dops[i].itype == COP2 && (dops[i].opcode2 == 0 || dops[i].opcode2 == 2)) { // MFC2/CFC2
+ *cycles = 0;
+ return 1;
+ }
+ if (dops[i].itype == C2OP) {
+ *cycles = gte_cycletab[source[i] & 0x3f];
+ return 1;
+ }
+ // ... what about MTC2/CTC2/LWC2?
+ return 0;
+}
+
+#if 0
+static void log_gte_stall(int stall, u_int cycle)
+{
+ if ((u_int)stall <= 44)
+ printf("x stall %2d %u\n", stall, cycle + last_count);
+}
+
+static void emit_log_gte_stall(int i, int stall, u_int reglist)
+{
+ save_regs(reglist);
+ if (stall > 0)
+ emit_movimm(stall, 0);
+ else
+ emit_mov(HOST_TEMPREG, 0);
+ emit_addimm(HOST_CCREG, ccadj[i], 1);
+ emit_far_call(log_gte_stall);
+ restore_regs(reglist);
+}
+#endif
+
+static void cop2_do_stall_check(u_int op, int i, const struct regstat *i_regs, u_int reglist)
+{
+ int j = i, other_gte_op_cycles = -1, stall = -MAXBLOCK, cycles_passed;
+ int rtmp = reglist_find_free(reglist);
+
+ if (HACK_ENABLED(NDHACK_NO_STALLS))
+ return;
+ if (get_reg(i_regs->regmap, CCREG) != HOST_CCREG) {
+ // happens occasionally... cc evicted? Don't bother then
+ //printf("no cc %08x\n", start + i*4);
+ return;
+ }
+ if (!dops[i].bt) {
+ for (j = i - 1; j >= 0; j--) {
+ //if (dops[j].is_ds) break;
+ if (cop2_is_stalling_op(j, &other_gte_op_cycles) || dops[j].bt)
+ break;
+ if (j > 0 && ccadj[j - 1] > ccadj[j])
+ break;
+ }
+ j = max(j, 0);
+ }
+ cycles_passed = ccadj[i] - ccadj[j];
+ if (other_gte_op_cycles >= 0)
+ stall = other_gte_op_cycles - cycles_passed;
+ else if (cycles_passed >= 44)
+ stall = 0; // can't stall
+ if (stall == -MAXBLOCK && rtmp >= 0) {
+ // unknown stall, do the expensive runtime check
+ assem_debug("; cop2_do_stall_check\n");
+#if 0 // too slow
+ save_regs(reglist);
+ emit_movimm(gte_cycletab[op], 0);
+ emit_addimm(HOST_CCREG, ccadj[i], 1);
+ emit_far_call(call_gteStall);
+ restore_regs(reglist);
+#else
+ host_tempreg_acquire();
+ emit_readword(&psxRegs.gteBusyCycle, rtmp);
+ emit_addimm(rtmp, -ccadj[i], rtmp);
+ emit_sub(rtmp, HOST_CCREG, HOST_TEMPREG);
+ emit_cmpimm(HOST_TEMPREG, 44);
+ emit_cmovb_reg(rtmp, HOST_CCREG);
+ //emit_log_gte_stall(i, 0, reglist);
+ host_tempreg_release();
+#endif
+ }
+ else if (stall > 0) {
+ //emit_log_gte_stall(i, stall, reglist);
+ emit_addimm(HOST_CCREG, stall, HOST_CCREG);
+ }
+
+ // save gteBusyCycle, if needed
+ if (gte_cycletab[op] == 0)
+ return;
+ other_gte_op_cycles = -1;
+ for (j = i + 1; j < slen; j++) {
+ if (cop2_is_stalling_op(j, &other_gte_op_cycles))
+ break;
+ if (dops[j].is_jump) {
+ // check ds
+ if (j + 1 < slen && cop2_is_stalling_op(j + 1, &other_gte_op_cycles))
+ j++;
+ break;
+ }
+ }
+ if (other_gte_op_cycles >= 0)
+ // will handle stall when assembling that op
+ return;
+ cycles_passed = ccadj[min(j, slen -1)] - ccadj[i];
+ if (cycles_passed >= 44)
+ return;
+ assem_debug("; save gteBusyCycle\n");
+ host_tempreg_acquire();
+#if 0
+ emit_readword(&last_count, HOST_TEMPREG);
+ emit_add(HOST_TEMPREG, HOST_CCREG, HOST_TEMPREG);
+ emit_addimm(HOST_TEMPREG, ccadj[i], HOST_TEMPREG);
+ emit_addimm(HOST_TEMPREG, gte_cycletab[op]), HOST_TEMPREG);
+ emit_writeword(HOST_TEMPREG, &psxRegs.gteBusyCycle);
+#else
+ emit_addimm(HOST_CCREG, ccadj[i] + gte_cycletab[op], HOST_TEMPREG);
+ emit_writeword(HOST_TEMPREG, &psxRegs.gteBusyCycle);
+#endif
+ host_tempreg_release();
+}
+
+static int is_mflohi(int i)
+{
+ return (dops[i].itype == MOV && (dops[i].rs1 == HIREG || dops[i].rs1 == LOREG));
+}
+
+static int check_multdiv(int i, int *cycles)
+{
+ if (dops[i].itype != MULTDIV)
+ return 0;
+ if (dops[i].opcode2 == 0x18 || dops[i].opcode2 == 0x19) // MULT(U)
+ *cycles = 11; // approx from 7 11 14
+ else
+ *cycles = 37;
+ return 1;
+}
+
+static void multdiv_prepare_stall(int i, const struct regstat *i_regs, int ccadj_)
+{
+ int j, found = 0, c = 0;
+ if (HACK_ENABLED(NDHACK_NO_STALLS))
+ return;
+ if (get_reg(i_regs->regmap, CCREG) != HOST_CCREG) {
+ // happens occasionally... cc evicted? Don't bother then
+ return;
+ }
+ for (j = i + 1; j < slen; j++) {
+ if (dops[j].bt)
+ break;
+ if ((found = is_mflohi(j)))
+ break;
+ if (dops[j].is_jump) {
+ // check ds
+ if (j + 1 < slen && (found = is_mflohi(j + 1)))
+ j++;
+ break;
+ }
+ }
+ if (found)
+ // handle all in multdiv_do_stall()
+ return;
+ check_multdiv(i, &c);
+ assert(c > 0);
+ assem_debug("; muldiv prepare stall %d\n", c);
+ host_tempreg_acquire();
+ emit_addimm(HOST_CCREG, ccadj_ + c, HOST_TEMPREG);
+ emit_writeword(HOST_TEMPREG, &psxRegs.muldivBusyCycle);
+ host_tempreg_release();
+}
+
+static void multdiv_do_stall(int i, const struct regstat *i_regs)
+{
+ int j, known_cycles = 0;
+ u_int reglist = get_host_reglist(i_regs->regmap);
+ int rtmp = get_reg(i_regs->regmap, -1);
+ if (rtmp < 0)
+ rtmp = reglist_find_free(reglist);
+ if (HACK_ENABLED(NDHACK_NO_STALLS))
+ return;
+ if (get_reg(i_regs->regmap, CCREG) != HOST_CCREG || rtmp < 0) {
+ // happens occasionally... cc evicted? Don't bother then
+ //printf("no cc/rtmp %08x\n", start + i*4);
+ return;
+ }
+ if (!dops[i].bt) {
+ for (j = i - 1; j >= 0; j--) {
+ if (dops[j].is_ds) break;
+ if (check_multdiv(j, &known_cycles))
+ break;
+ if (is_mflohi(j))
+ // already handled by this op
+ return;
+ if (dops[j].bt || (j > 0 && ccadj[j - 1] > ccadj[j]))
+ break;
+ }
+ j = max(j, 0);
+ }
+ if (known_cycles > 0) {
+ known_cycles -= ccadj[i] - ccadj[j];
+ assem_debug("; muldiv stall resolved %d\n", known_cycles);
+ if (known_cycles > 0)
+ emit_addimm(HOST_CCREG, known_cycles, HOST_CCREG);
+ return;
+ }
+ assem_debug("; muldiv stall unresolved\n");
+ host_tempreg_acquire();
+ emit_readword(&psxRegs.muldivBusyCycle, rtmp);
+ emit_addimm(rtmp, -ccadj[i], rtmp);
+ emit_sub(rtmp, HOST_CCREG, HOST_TEMPREG);
+ emit_cmpimm(HOST_TEMPREG, 37);
+ emit_cmovb_reg(rtmp, HOST_CCREG);
+ //emit_log_gte_stall(i, 0, reglist);
+ host_tempreg_release();
+}
+