Reicast Dynarec discussion


ptitSeb

Serial Porter
Joined
Aug 15, 2012
Messages
9,330
Age
51
Location
France, near Lyon
Let's discuss about reicast dynarec.
 
I'll focus here on individual implementations of opcode. Most opcode are individual SH4 opcode, but there are a few higher level opcode too.
 
So, there are emiter and opcode.
 
Emiter syntax is quite simple, and is generaly:
XXX(op1, op2, op3, UpdateFlags, ShiftOp, op4, Condition).
But most of the latest (Shift, Update, etc...) can be optionnal. So we can have


MOV(r0, r1);
SUB(r0, r1, r2, true, S_LSR, 1, CC_MI);

I can add emiter when some are missing. Emiter are in core/arm_emitter/E_*.h
 
In the opcode, r0-r4 are scratch register, free to used
rd is the register that will return 32bit value
rd2 is the upper part for 64bit register return
r1, rs2 and rs3 are the parameters registers.
Also, be aware that rd, rd2, rs1, rs2 and rs3 are not always distinct.

Here is setpeq


case shop_setpeq:
{
EOR(r1, reg.mapg(op->rs1), reg.mapg(op->rs2));
MOVW(reg.mapg(op->rd), 0);
 
TST(r1, 0xFF000000);
TST(r1, 0x00FF0000, CC_NE);
TST(r1, 0x0000FF00, CC_NE);
TST(r1, 0x000000FF, CC_NE);
MOVW(reg.mapg(op->rd), 1, CC_EQ);
}
break;

And here is sbc


case shop_sbc:
//printf("sbc: r%d r%d r%d r%d r%d\n",reg.mapg(op->rd),reg.mapg(op->rd2),reg.mapg(op->rs1),reg.mapg(op->rs2), reg.mapg(op->rs3));
{
bool use_r0 = false;
if (reg.mapg(op->rd2)==reg.mapg(op->rs1) || reg.mapg(op->rd2)==reg.mapg(op->rs2))
use_r0 = true;
EOR((use_r0)?r0:reg.mapg(op->rd2),reg.mapg(op->rs3),1)
LSR((use_r0)?r0:reg.mapg(op->rd2),(use_r0)?r0:reg.mapg(op->rd2),1,true); //C=rs3, rd2=0
SBC(reg.mapg(op->rd), reg.mapg(op->rs1), reg.mapg(op->rs2), true);
MOV((use_r0)?r0:reg.mapg(op->rd2), 1, CC_CC);
if (use_r0)
MOV(reg.mapg(op->rd2), r0);
}
break;

the new shld


case shop_shld:
//printf("shld: r%d r%d r%d\n",reg.mapg(op->rd),reg.mapg(op->rs1),reg.mapg(op->rs2));
{
verify(!op->rs2.is_imm());
AND(r0, reg.mapg(op->rs2), 0x8000001F, true);
RSB(r0, r0, 0x80000020, CC_MI);
LSR(reg.mapg(op->rd), reg.mapg(op->rs1), r0, CC_MI);
LSL(reg.mapg(op->rd), reg.mapg(op->rs1), r0, CC_PL);
//MOV(reg.mapg(op->rd), reg.mapg(op->rs1), S_LSL, r0, CC_PL);
//MOV(reg.mapg(op->rd), reg.mapg(op->rs1), S_LSR, r0, CC_MI);
}
break;

And the Div (that are HLE functions)


case shop_div32u:
// Doesn't work
// algo from new arm dynarec from mupen64plus
//printf("div32u: r%d r%d r%d r%d\n",reg.mapg(op->rd2),reg.mapg(op->rd),reg.mapg(op->rs1),reg.mapg(op->rs2));
{
// remainder = r0, quotient = r1, HOST_TEMPREG = r2, copy de rs1 = r3, copy de rs2 = r4
MOV(r3, reg.mapg(op->rs1));
MOV(r4, reg.mapg(op->rs2), true);
MOV(r0, reg.mapg(op->rs1)); // dividend = d1 , divisor = d2
MVN(r1, 0);
B(10*4-8, CC_EQ);
CLZ(r2, r4);
MOV(r1, 1<<31);
LSL(r4, r4, r2);
LSR(r1, r1, r2);
CMP(r0, r4);
SUB(r0, r0, r4, CC_CS);
ADC(r1, r1, r1, true);
MOV(r4, r4, S_LSR, 1, CC_CC);
B(-4*4-8, CC_CC);
MOV(reg.mapg(op->rd), r1);
MOV(reg.mapg(op->rd2), r0);
}
break;
case shop_div32s:
//printf("div32s r%d, r%d, r%d, r%d\n", reg.mapg(op->rd2),reg.mapg(op->rd),reg.mapg(op->rs1),reg.mapg(op->rs2));
// algo from dynarec from pcsxrearmed
// remainder = r0, quotient = r1, HOST_TEMPREG = r2, copy de rs1 = r3, copy de rs2 = r4
{
MOV(r3, reg.mapg(op->rs1));
MOV(r4, reg.mapg(op->rs2));
MOV(r0, reg.mapg(op->rs1), true);
MVN(r1, 0);
RSB(r1, r1, 0, CC_MI); // .. quotient and ..
RSB(r0, r0, 0, CC_MI); // .. remainder for div0 case (will be negated back after jump)
MOV(r2, reg.mapg(op->rs2), true);
B(13*4-8, CC_EQ); // Division by zero
RSB(r2, r2, 0, true, CC_MI);
CLZ(r1, r2);
LSL(r2, r2, r1);
ORR(r1, r1, 1<<31);
LSR(r1, r1, r1);
CMP(r0, r2);
SUB(r0, r0, r2, CC_CS);
ADC(r1, r1, r1, true);
MOV(r2, r2, S_LSR, 1);
B(-4*4-8, CC_CC); // -4
TEQ(r3, r4, S_LSL, CC_AL);
RSB(reg.mapg(op->rd), r1, 0, CC_MI);
MOV(reg.mapg(op->rd), r1, CC_PL);
TST(r3, r3);
RSB(reg.mapg(op->rd2), r0, 0, CC_MI);
MOV(reg.mapg(op->rd2), r0, CC_PL);
}
break;


Note that the div32s (at least) is not correct. I have weird car behavour on Daytona with this one activated. *EDIT* fixed, the UpdateFlags should be in third line, not second. Updated the code snipet also. *EDIT2* No, it's not fixed.
 
Last edited by a moderator:
SBC needs to invert the carry before doing the subtraction. I think this should work:

Code:
case shop_sbc:
//printf("sbc: r%d r%d r%d r%d\n",reg.mapg(op->rd),reg.mapg(op->rs1),reg.mapg(op->rs2), reg.mapg(op->rs3));
{
EOR(reg.mapg(op->rd2),reg.mapg(op->rs3),1)
LSR(reg.mapg(op->rd2),reg.mapg(op->rd2),1,true); //C=~rs3, rd2=0
SBC(reg.mapg(op->rd), reg.mapg(op->rs1), reg.mapg(op->rs2), true);
MOV(reg.mapg(op->rd2), 1, CC_CC); //rd2=~C
}
break;
 
SBC needs to invert the carry before doing the subtraction. I think this should work:


case shop_sbc:
//printf("sbc: r%d r%d r%d r%d\n",reg.mapg(op->rd),reg.mapg(op->rs1),reg.mapg(op->rs2), reg.mapg(op->rs3));
{
EOR(reg.mapg(op->rd2),reg.mapg(op->rs3),1)
LSR(reg.mapg(op->rd2),reg.mapg(op->rd2),1,true); //C=~rs3, rd2=0
SBC(reg.mapg(op->rd), reg.mapg(op->rs1), reg.mapg(op->rs2), true);
MOV(reg.mapg(op->rd2), 1, CC_CC); //rd2=~C
}
break;
Ok thanks. 

I'll update 1st post with that.

I also added some test in case rd2 == rs1 or rs2
 
SBC needs to invert the carry before doing the subtraction. I think this should work:


case shop_sbc:
//printf("sbc: r%d r%d r%d r%d\n",reg.mapg(op->rd),reg.mapg(op->rs1),reg.mapg(op->rs2), reg.mapg(op->rs3));
{
EOR(reg.mapg(op->rd2),reg.mapg(op->rs3),1)
LSR(reg.mapg(op->rd2),reg.mapg(op->rd2),1,true); //C=~rs3, rd2=0
SBC(reg.mapg(op->rd), reg.mapg(op->rs1), reg.mapg(op->rs2), true);
MOV(reg.mapg(op->rd2), 1, CC_CC); //rd2=~C
}
break;
Ok thanks. 

I'll update 1st post with that.

I also added some test in case rd2 == rs1 or rs2
I think that's completely unnecessary, rd2 is the T flag and rs1/rs2 are general purpose registers. Or at least, I don't think any other commands that use the T flag check for that.

Are the division ops actually supposed to return a remainder? I'd find that kind of odd, since the SuperH division routine itself doesn't do so. *EDIT* Actually, I guess the input dividend register's final value is related to the remainder. I'll look at the implementation a bit more fully.

And in the signed division routine at least, you don't need to copy the source registers to r3 and r4 because they are treated as read-only throughout the routine.

Edit: Hmm, maybe the signed division problem is with the S_LSL here:

Code:
TEQ(r3, r4, S_LSL, CC_AL);
 
Last edited by a moderator:
@calc84maniac

About the rd2 beeing the T flag, yes you are right. Maybe I can safely assume rd2 will always be different than rs1/rs2. And I can probably also assume that rd2 == rs3 here too.

About the TEQ, I used a default Emiter... But maybe it's safer to create a new one, having just TEQ(reg, reg, Condition).
 
Hmm, I think it would be a good idea for 16-bit signed multiplication to be implemented with the SMULBB instruction. Unfortunately, there's no unsigned equivalent, though.

Edit: I wonder if it would be worth it to have a division routine with unrolled iterations (with some possibly skipped using CLZ combined with ADD PC). I feel this would be feasible because the detected SuperH code in question is an unrolled loop itself, containing 65 instructions, which means the size of the recompiled code isn't an issue (unlike an actual DIV instruction on other processors which can be spammed willy-nilly, this is likely used once inside a subroutine).

Edit2: Here's an example of unsigned division using this method (written by yours truly):

Code:
    CLZ(r2, reg.mapg(op->rs1));
    MOV(r0, reg.mapg(op->rs1));
    ADD(r2, r2, r2, S_LSL, 1);
    MOV(r1, 0);
    ADD(pc, pc, r2, S_LSL, 2);
    NOP();
    for(int i = 31; i >= 0; i--) {
        CMP(reg.mapg(op->rs2), r0, (i ? S_LSR : S_LSL), i);
        SUB(r0, r0, reg.mapg(op->rs2), S_LSL, i, CC_LS);
        ORR(r1, r1, 1 << i, CC_LS);
    }
    MOV(reg.mapg(op->rd), r1);
    MOV(reg.mapg(op->rd2), r0);
 
Last edited by a moderator:
About SMULBB, yes, that will probably be a bit more effective, but I have to create the emiter for this one. I'll look into it.

For the division, I can try your new method, no problem.

I'll try to push the code tonight on the github, so you can have fresh sources (and emiter sources too).
 
Oh, due to how the emitters seem to work, I need to specially handle LSR 0 as LSL 0 (otherwise it gets emitted as LSR 32). I've edited the routine to take this into account.
 
Back
Top