+ 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();
+}
+
+static void cop2_get_dreg(u_int copr,signed char tl,signed char temp)
+{
+ switch (copr) {
+ case 1:
+ case 3:
+ case 5:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ emit_readword(®_cop2d[copr],tl);
+ emit_signextend16(tl,tl);
+ emit_writeword(tl,®_cop2d[copr]); // hmh
+ break;
+ case 7:
+ case 16:
+ case 17:
+ case 18:
+ case 19:
+ emit_readword(®_cop2d[copr],tl);
+ emit_andimm(tl,0xffff,tl);
+ emit_writeword(tl,®_cop2d[copr]);
+ break;
+ case 15:
+ emit_readword(®_cop2d[14],tl); // SXY2
+ emit_writeword(tl,®_cop2d[copr]);
+ break;
+ case 28:
+ case 29:
+ c2op_mfc2_29_assemble(tl,temp);
+ break;
+ default:
+ emit_readword(®_cop2d[copr],tl);
+ break;
+ }
+}
+
+static void cop2_put_dreg(u_int copr,signed char sl,signed char temp)
+{
+ switch (copr) {
+ case 15:
+ emit_readword(®_cop2d[13],temp); // SXY1
+ emit_writeword(sl,®_cop2d[copr]);
+ emit_writeword(temp,®_cop2d[12]); // SXY0
+ emit_readword(®_cop2d[14],temp); // SXY2
+ emit_writeword(sl,®_cop2d[14]);
+ emit_writeword(temp,®_cop2d[13]); // SXY1
+ break;
+ case 28:
+ emit_andimm(sl,0x001f,temp);
+ emit_shlimm(temp,7,temp);
+ emit_writeword(temp,®_cop2d[9]);
+ emit_andimm(sl,0x03e0,temp);
+ emit_shlimm(temp,2,temp);
+ emit_writeword(temp,®_cop2d[10]);
+ emit_andimm(sl,0x7c00,temp);
+ emit_shrimm(temp,3,temp);
+ emit_writeword(temp,®_cop2d[11]);
+ emit_writeword(sl,®_cop2d[28]);
+ break;
+ case 30:
+ emit_xorsar_imm(sl,sl,31,temp);
+#if defined(HAVE_ARMV5) || defined(__aarch64__)
+ emit_clz(temp,temp);
+#else
+ emit_movs(temp,HOST_TEMPREG);
+ emit_movimm(0,temp);
+ emit_jeq((int)out+4*4);
+ emit_addpl_imm(temp,1,temp);
+ emit_lslpls_imm(HOST_TEMPREG,1,HOST_TEMPREG);
+ emit_jns((int)out-2*4);
+#endif
+ emit_writeword(sl,®_cop2d[30]);
+ emit_writeword(temp,®_cop2d[31]);
+ break;
+ case 31:
+ break;
+ default:
+ emit_writeword(sl,®_cop2d[copr]);
+ break;
+ }
+}
+
+static void c2ls_assemble(int i, const struct regstat *i_regs, int ccadj_)
+{
+ int s,tl;
+ int ar;
+ int offset;
+ int memtarget=0,c=0;
+ void *jaddr2=NULL;
+ enum stub_type type;
+ int agr=AGEN1+(i&1);
+ int offset_reg = -1;
+ int fastio_reg_override = -1;
+ u_int reglist=get_host_reglist(i_regs->regmap);
+ u_int copr=(source[i]>>16)&0x1f;
+ s=get_reg(i_regs->regmap,dops[i].rs1);
+ tl=get_reg(i_regs->regmap,FTEMP);
+ offset=imm[i];
+ assert(dops[i].rs1>0);
+ assert(tl>=0);
+
+ if(i_regs->regmap[HOST_CCREG]==CCREG)
+ reglist&=~(1<<HOST_CCREG);
+
+ // get the address
+ if (dops[i].opcode==0x3a) { // SWC2
+ ar=get_reg(i_regs->regmap,agr);
+ if(ar<0) ar=get_reg(i_regs->regmap,-1);
+ reglist|=1<<ar;
+ } else { // LWC2
+ ar=tl;
+ }
+ if(s>=0) c=(i_regs->wasconst>>s)&1;
+ memtarget=c&&(((signed int)(constmap[i][s]+offset))<(signed int)0x80000000+RAM_SIZE);
+ if (!offset&&!c&&s>=0) ar=s;
+ assert(ar>=0);
+
+ cop2_do_stall_check(0, i, i_regs, reglist);
+
+ if (dops[i].opcode==0x3a) { // SWC2
+ cop2_get_dreg(copr,tl,-1);
+ type=STOREW_STUB;
+ }
+ else
+ type=LOADW_STUB;
+
+ if(c&&!memtarget) {
+ jaddr2=out;
+ emit_jmp(0); // inline_readstub/inline_writestub?
+ }
+ else {
+ if(!c) {
+ jaddr2 = emit_fastpath_cmp_jump(i, i_regs, ar,
+ &offset_reg, &fastio_reg_override);
+ }
+ else if (ram_offset && memtarget) {
+ offset_reg = get_ro_reg(i_regs, 0);
+ }
+ switch (dops[i].opcode) {
+ case 0x32: { // LWC2
+ int a = ar;
+ if (fastio_reg_override >= 0)
+ a = fastio_reg_override;
+ do_load_word(a, tl, offset_reg);
+ break;
+ }
+ case 0x3a: { // SWC2
+ #ifdef DESTRUCTIVE_SHIFT
+ if(!offset&&!c&&s>=0) emit_mov(s,ar);
+ #endif
+ int a = ar;
+ if (fastio_reg_override >= 0)
+ a = fastio_reg_override;
+ do_store_word(a, 0, tl, offset_reg, 1);
+ break;
+ }
+ default:
+ assert(0);
+ }
+ }
+ if (fastio_reg_override == HOST_TEMPREG || offset_reg == HOST_TEMPREG)
+ host_tempreg_release();
+ if(jaddr2)
+ add_stub_r(type,jaddr2,out,i,ar,i_regs,ccadj_,reglist);
+ if(dops[i].opcode==0x3a) // SWC2
+ if(!(i_regs->waswritten&(1<<dops[i].rs1)) && !HACK_ENABLED(NDHACK_NO_SMC_CHECK)) {
+#if defined(HOST_IMM8)
+ int ir=get_reg(i_regs->regmap,INVCP);
+ assert(ir>=0);
+ emit_cmpmem_indexedsr12_reg(ir,ar,1);
+#else
+ emit_cmpmem_indexedsr12_imm(invalid_code,ar,1);
+#endif
+ #if defined(HAVE_CONDITIONAL_CALL) && !defined(DESTRUCTIVE_SHIFT)
+ emit_callne(invalidate_addr_reg[ar]);
+ #else
+ void *jaddr3 = out;
+ emit_jne(0);
+ add_stub(INVCODE_STUB,jaddr3,out,reglist|(1<<HOST_CCREG),ar,0,0,0);
+ #endif
+ }
+ if (dops[i].opcode==0x32) { // LWC2
+ host_tempreg_acquire();
+ cop2_put_dreg(copr,tl,HOST_TEMPREG);
+ host_tempreg_release();
+ }
+}
+
+static void cop2_assemble(int i, const struct regstat *i_regs)
+{
+ u_int copr = (source[i]>>11) & 0x1f;
+ signed char temp = get_reg(i_regs->regmap, -1);
+
+ if (!HACK_ENABLED(NDHACK_NO_STALLS)) {
+ u_int reglist = reglist_exclude(get_host_reglist(i_regs->regmap), temp, -1);
+ if (dops[i].opcode2 == 0 || dops[i].opcode2 == 2) { // MFC2/CFC2
+ signed char tl = get_reg(i_regs->regmap, dops[i].rt1);
+ reglist = reglist_exclude(reglist, tl, -1);
+ }
+ cop2_do_stall_check(0, i, i_regs, reglist);
+ }
+ if (dops[i].opcode2==0) { // MFC2
+ signed char tl=get_reg(i_regs->regmap,dops[i].rt1);
+ if(tl>=0&&dops[i].rt1!=0)
+ cop2_get_dreg(copr,tl,temp);
+ }
+ else if (dops[i].opcode2==4) { // MTC2
+ signed char sl=get_reg(i_regs->regmap,dops[i].rs1);
+ cop2_put_dreg(copr,sl,temp);
+ }
+ else if (dops[i].opcode2==2) // CFC2
+ {
+ signed char tl=get_reg(i_regs->regmap,dops[i].rt1);
+ if(tl>=0&&dops[i].rt1!=0)
+ emit_readword(®_cop2c[copr],tl);
+ }
+ else if (dops[i].opcode2==6) // CTC2
+ {
+ signed char sl=get_reg(i_regs->regmap,dops[i].rs1);
+ switch(copr) {
+ case 4:
+ case 12:
+ case 20:
+ case 26:
+ case 27:
+ case 29:
+ case 30:
+ emit_signextend16(sl,temp);
+ break;
+ case 31:
+ c2op_ctc2_31_assemble(sl,temp);
+ break;
+ default:
+ temp=sl;
+ break;
+ }
+ emit_writeword(temp,®_cop2c[copr]);
+ assert(sl>=0);
+ }
+}
+
+static void do_unalignedwritestub(int n)
+{
+ assem_debug("do_unalignedwritestub %x\n",start+stubs[n].a*4);
+ literal_pool(256);
+ set_jump_target(stubs[n].addr, out);
+
+ int i=stubs[n].a;
+ struct regstat *i_regs=(struct regstat *)stubs[n].c;
+ int addr=stubs[n].b;
+ u_int reglist=stubs[n].e;
+ signed char *i_regmap=i_regs->regmap;
+ int temp2=get_reg(i_regmap,FTEMP);
+ int rt;
+ rt=get_reg(i_regmap,dops[i].rs2);
+ assert(rt>=0);
+ assert(addr>=0);
+ assert(dops[i].opcode==0x2a||dops[i].opcode==0x2e); // SWL/SWR only implemented
+ reglist|=(1<<addr);
+ reglist&=~(1<<temp2);
+
+ // don't bother with it and call write handler
+ save_regs(reglist);
+ pass_args(addr,rt);
+ int cc=get_reg(i_regmap,CCREG);
+ if(cc<0)
+ emit_loadreg(CCREG,2);
+ emit_addimm(cc<0?2:cc,(int)stubs[n].d+1,2);
+ emit_far_call((dops[i].opcode==0x2a?jump_handle_swl:jump_handle_swr));
+ emit_addimm(0,-((int)stubs[n].d+1),cc<0?2:cc);
+ if(cc<0)
+ emit_storereg(CCREG,2);
+ restore_regs(reglist);
+ emit_jmp(stubs[n].retaddr); // return address
+}
+
+#ifndef multdiv_assemble
+void multdiv_assemble(int i,struct regstat *i_regs)
+{
+ printf("Need multdiv_assemble for this architecture.\n");
+ abort();
+}
+#endif
+
+static void mov_assemble(int i, const struct regstat *i_regs)
+{
+ //if(dops[i].opcode2==0x10||dops[i].opcode2==0x12) { // MFHI/MFLO
+ //if(dops[i].opcode2==0x11||dops[i].opcode2==0x13) { // MTHI/MTLO
+ if(dops[i].rt1) {
+ signed char sl,tl;
+ tl=get_reg(i_regs->regmap,dops[i].rt1);
+ //assert(tl>=0);
+ if(tl>=0) {
+ sl=get_reg(i_regs->regmap,dops[i].rs1);
+ if(sl>=0) emit_mov(sl,tl);
+ else emit_loadreg(dops[i].rs1,tl);
+ }
+ }
+ if (dops[i].rs1 == HIREG || dops[i].rs1 == LOREG) // MFHI/MFLO
+ multdiv_do_stall(i, i_regs);