32-bit signed/unsigned division

This commit is contained in:
Jeff Parsons 2015-04-23 16:48:25 -07:00 committed by jeffpar
commit 759a0c3541
2 changed files with 302 additions and 109 deletions

View file

@ -465,12 +465,170 @@ X86.fnDECw = function DECw(dst, src)
return w & 0xffff;
};
/**
* fnSet64(lo, hi)
*
* @param {number} lo
* @param {number} hi
*/
X86.fnSet64 = function Set64(lo, hi)
{
return [lo >>> 0, hi >>> 0];
};
/**
* fnAdd64(dst, src)
*
* Adds src to dst.
*
* @param {Array} dst is a 64-bit value
* @param {Array} src is a 64-bit value
*/
X86.fnAdd64 = function Add64(dst, src)
{
dst[0] += src[0];
dst[1] += src[1];
if (dst[0] > 0xffffffff) {
dst[0] >>>= 0; // truncate dst[0] to 32 bits AND keep it unsigned
dst[1]++;
}
};
/**
* fnCmp64(dst, src)
*
* Compares dst to src, by computing dst - src.
*
* @param {Array} dst is a 64-bit value
* @param {Array} src is a 64-bit value
* @return {number} > 0 if dst > src, == 0 if dst == src, < 0 if dst < src
*/
X86.fnCmp64 = function Cmp64(dst, src)
{
var result = dst[1] - src[1];
if (!result) result = dst[0] - src[0];
return result;
};
/**
* fnSub64(dst, src)
*
* Subtracts src from dst.
*
* @param {Array} dst is a 64-bit value
* @param {Array} src is a 64-bit value
*/
X86.fnSub64 = function Sub64(dst, src)
{
dst[0] -= src[0];
dst[1] -= src[1];
if (dst[0] < 0) {
dst[0] >>>= 0; // truncate dst[0] to 32 bits AND keep it unsigned
dst[1]--;
}
};
/**
* fnShr64(dst)
*
* Shifts dst right one bit.
*
* @param {Array} dst is a 64-bit value
*/
X86.fnShr64 = function Shr64(dst)
{
dst[0] >>>= 1;
if (dst[1] & 0x1) {
dst[0] = (dst[0] | 0x80000000) >>> 0;
}
dst[1] >>>= 1;
};
/**
* fnDIV32(dstLo, dstHi, src)
*
* This sets regMDLo to dstHi:dstLo / src, and regMDHi to dstHi:dstLo % src; all inputs are treated as unsigned.
*
* If fMDset is not set, however, then there was a divide exception (ie, the divisor was either zero or too small).
*
* Refer to: http://lxr.linux.no/linux+v2.6.22/lib/div64.c
*
* @this {X86CPU}
* @param {number} dstLo (low 32-bit portion of dividend)
* @param {number} dstHi (high 32-bit portion of dividend)
* @param {number} src (32-bit divisor)
*/
X86.fnDIV32 = function DIV32(dstLo, dstHi, src)
{
this.fMDSet = false;
src >>>= 0;
if (!src || src <= (dstHi >>> 0)) return;
var result = 0, bit = 1;
var div = X86.fnSet64(src, 0);
var rem = X86.fnSet64(dstLo, dstHi);
while (X86.fnCmp64(rem, div) > 0) {
X86.fnAdd64(div, div);
bit += bit;
}
do {
if (X86.fnCmp64(rem, div) >= 0) {
X86.fnSub64(rem, div);
result += bit;
}
X86.fnShr64(div);
bit >>>= 1;
} while (bit);
this.assert(result <= 0xffffffff && !rem[1]);
this.regMDLo = result; // result is the quotient, which callers expect in the low MD register
this.regMDHi = rem[0]; // rem[0] is the remainder, which callers expect in the high MD register
this.fMDSet = true;
};
/**
* fnIDIV32(dstLo, dstHi, src)
*
* This sets regMDLo to dstHi:dstLo / src, and regMDHi to dstHi:dstLo % src; all inputs are treated as signed.
*
* If fMDset is not set, however, then there was a divide exception (ie, the divisor was either zero or too small).
*
* Refer to: http://lxr.linux.no/linux+v2.6.22/lib/div64.c
*
* @this {X86CPU}
* @param {number} dstLo (low 32-bit portion of dividend)
* @param {number} dstHi (high 32-bit portion of dividend)
* @param {number} src (32-bit divisor)
*/
X86.fnIDIV32 = function IDIV32(dstLo, dstHi, src)
{
var fNegLo = false, fNegHi = false;
if (src < 0) {
src = -src|0;
fNegLo = !fNegLo;
}
if (dstHi < 0) {
dstLo = -dstLo|0;
dstHi = (~dstHi + (dstLo? 0 : 1))|0;
fNegHi = true;
fNegLo = !fNegLo;
}
X86.fnDIV32.call(this, dstLo, dstHi, src);
if (this.regMDLo > 0x7fffffff) this.fMDSet = false;
if (fNegLo) this.regMDLo = -this.regMDLo;
if (fNegHi) this.regMDHi = -this.regMDHi;
};
/**
* fnDIVb(dst, src)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
* @param {number} dst (the divisor)
* @param {number} src (null; AX is the implied src)
* @return {number} (we return dst unchanged, since it's actually AX that's modified)
*/
X86.fnDIVb = function DIVb(dst, src)
@ -486,14 +644,14 @@ X86.fnDIVb = function DIVb(dst, src)
/*
* Detect too-small divisor (quotient overflow)
*/
var result = ((src = this.regEAX) / dst);
var result = ((src = this.regEAX & 0xffff) / dst);
if (result > 0xff) {
X86.fnDIVOverflow.call(this);
return dst;
}
this.fMDSet = true;
this.regMDLo = (result & 0xff) | (((this.regEAX % dst) & 0xff) << 8);
this.regMDLo = (result & 0xff) | (((src % dst) & 0xff) << 8);
/*
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
@ -511,45 +669,60 @@ X86.fnDIVb = function DIVb(dst, src)
* fnDIVw(dst, src)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
* @param {number} dst (the divisor)
* @param {number} src (null; DX:AX or EDX:EAX is the implied src)
* @return {number} (we return dst unchanged, since it's actually DX:AX that's modified)
*/
X86.fnDIVw = function DIVw(dst, src)
{
/*
* Detect zero divisor
*/
if (!dst) {
X86.fnDIVOverflow.call(this);
return dst;
if (this.dataSize == 2) {
/*
* Detect zero divisor
*/
if (!dst) {
X86.fnDIVOverflow.call(this);
return dst;
}
/*
* Detect too-small divisor (quotient overflow)
*
* WARNING: We CANNOT simply do "src = (this.regEDX << 16) | this.regEAX", because if bit 15 of DX
* is set, JavaScript will create a negative 32-bit number. So we instead use non-bit-wise operators
* to force JavaScript to create a floating-point value that won't suffer from 32-bit-math side-effects.
*/
src = (this.regEDX & 0xffff) * 0x10000 + (this.regEAX & 0xffff);
var result = (src / dst)|0;
if (result >= 0x10000) {
X86.fnDIVOverflow.call(this);
return dst;
}
this.fMDSet = true;
this.regMDLo = (result & 0xffff);
this.regMDHi = (src % dst) & 0xffff;
}
/*
* Detect too-small divisor (quotient overflow)
*
* WARNING: We CANNOT simply do "src = (this.regEDX << 16) | this.regEAX", because if bit 15 of DX
* is set, JavaScript will create a negative 32-bit number. So we instead use non-bit-wise operators
* to force JavaScript to create a floating-point value that won't suffer from 32-bit-math side-effects.
*/
src = this.regEAX + this.regEDX * 0x10000;
var result = (src / dst)|0;
if (result >= 0x10000) {
X86.fnDIVOverflow.call(this);
return dst;
else {
X86.fnDIV32.call(this, this.regEAX, this.regEDX, dst);
if (!this.fMDSet) {
X86.fnDIVOverflow.call(this);
return dst;
}
this.regMDLo |= 0;
this.regMDHi |= 0;
}
this.fMDSet = true;
this.regMDLo = (result & 0xffff);
this.regMDHi = (src % dst) & 0xffff;
/*
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
* However, src is technically an output, and dst is merely an input (which is why we must return
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
*/
if (DEBUG && DEBUGGER) this.traceLog('DIVW', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16));
if (DEBUG && DEBUGGER) {
if (this.dataSize == 2) {
this.traceLog('DIVW', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16));
} else {
this.traceLog('DIVD', src, dst, null, this.getPS(), this.regMDLo, this.regMDHi);
}
}
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesDivWR : this.cycleCounts.nOpCyclesDivWM);
this.opFlags |= X86.OPFLAG.NOWRITE;
return dst;
@ -571,17 +744,9 @@ X86.fnESC = function ESC(dst, src)
/**
* fnIDIVb(dst, src)
*
* TODO: Implement the following difference, from "AP-186: Introduction to the 80186 Microprocessor, March 1983":
*
* "The 8086 will cause a divide error whenever the absolute value of the quotient is greater then 7FFFH
* (for word operations) or if the absolute value of the quotient is greater than 7FH (for byte operations).
* The 80186 has expanded the range of negative numbers allowed as a quotient by 1 to include 8000H and 80H.
* These numbers represent the most negative numbers representable using 2's complement arithmetic (equaling
* -32768 and -128 in decimal, respectively)."
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
* @param {number} dst (the divisor)
* @param {number} src (null; AX is the implied src)
* @return {number} (we return dst unchanged, since it's actually AX that's modified)
*/
X86.fnIDIVb = function IDIVb(dst, src)
@ -600,7 +765,16 @@ X86.fnIDIVb = function IDIVb(dst, src)
var div = ((dst << 24) >> 24);
var result = ((src = (this.regEAX << 16) >> 16) / div)|0;
if (result != ((result << 24) >> 24)) {
/*
* Note the following difference, from "AP-186: Introduction to the 80186 Microprocessor, March 1983":
*
* "The 8086 will cause a divide error whenever the absolute value of the quotient is greater then 7FFFH
* (for word operations) or if the absolute value of the quotient is greater than 7FH (for byte operations).
* The 80186 has expanded the range of negative numbers allowed as a quotient by 1 to include 8000H and 80H.
* These numbers represent the most negative numbers representable using 2's complement arithmetic (equaling
* -32768 and -128 in decimal, respectively)."
*/
if (result != ((result << 24) >> 24) || this.model == X86.MODEL_8086 && result == -128) {
X86.fnDIVOverflow.call(this);
return dst;
}
@ -623,51 +797,69 @@ X86.fnIDIVb = function IDIVb(dst, src)
/**
* fnIDIVw(dst, src)
*
* TODO: Implement the following difference, from "AP-186: Introduction to the 80186 Microprocessor, March 1983":
*
* "The 8086 will cause a divide error whenever the absolute value of the quotient is greater then 7FFFH
* (for word operations) or if the absolute value of the quotient is greater than 7FH (for byte operations).
* The 80186 has expanded the range of negative numbers allowed as a quotient by 1 to include 8000H and 80H.
* These numbers represent the most negative numbers representable using 2's complement arithmetic (equaling
* -32768 and -128 in decimal, respectively)."
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
* @param {number} dst (the divisor)
* @param {number} src (null; DX:AX or EDX:EAX is the implied src)
* @return {number} (we return dst unchanged, since it's actually DX:AX that's modified)
*/
X86.fnIDIVw = function IDIVw(dst, src)
{
/*
* Detect zero divisor
*/
if (!dst) {
X86.fnDIVOverflow.call(this);
return dst;
if (this.dataSize == 2) {
/*
* Detect zero divisor
*/
if (!dst) {
X86.fnDIVOverflow.call(this);
return dst;
}
/*
* Detect too-small divisor (quotient overflow)
*/
var div = ((dst << 16) >> 16);
var result = ((src = (this.regEDX << 16) | (this.regEAX & 0xffff)) / div)|0;
/*
* Note the following difference, from "AP-186: Introduction to the 80186 Microprocessor, March 1983":
*
* "The 8086 will cause a divide error whenever the absolute value of the quotient is greater then 7FFFH
* (for word operations) or if the absolute value of the quotient is greater than 7FH (for byte operations).
* The 80186 has expanded the range of negative numbers allowed as a quotient by 1 to include 8000H and 80H.
* These numbers represent the most negative numbers representable using 2's complement arithmetic (equaling
* -32768 and -128 in decimal, respectively)."
*/
if (result != ((result << 16) >> 16) || this.model == X86.MODEL_8086 && result == -32768) {
X86.fnDIVOverflow.call(this);
return dst;
}
this.fMDSet = true;
this.regMDLo = (result & 0xffff);
this.regMDHi = (src % div) & 0xffff;
}
/*
* Detect too-small divisor (quotient overflow)
*/
var div = ((dst << 16) >> 16);
var result = ((src = (this.regEDX << 16) | this.regEAX) / div)|0;
if (result != ((result << 16) >> 16)) {
X86.fnDIVOverflow.call(this);
return dst;
else {
X86.fnIDIV32.call(this, this.regEAX, this.regEDX, dst);
if (!this.fMDSet) {
X86.fnDIVOverflow.call(this);
return dst;
}
this.regMDLo |= 0;
this.regMDHi |= 0;
}
this.fMDSet = true;
this.regMDLo = (result & 0xffff);
this.regMDHi = (src % div) & 0xffff;
/*
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
* However, src is technically an output, and dst is merely an input (which is why we must return
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
*/
if (DEBUG && DEBUGGER) this.traceLog('IDIVW', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16));
if (DEBUG && DEBUGGER) {
if (this.dataSize == 2) {
this.traceLog('IDIVW', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16));
} else {
this.traceLog('IDIVD', src, dst, null, this.getPS(), this.regMDLo, this.regMDHi);
}
}
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIDivWR : this.cycleCounts.nOpCyclesIDivWM);
this.opFlags |= X86.OPFLAG.NOWRITE;
return dst;
@ -731,7 +923,7 @@ X86.fnIMUL8 = function IMUL8(dst, src)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
* @param {number} src (null; AL is the implied src)
* @return {number} (we return dst unchanged, since it's actually AX that's modified)
*/
X86.fnIMULb = function IMULb(dst, src)
@ -808,6 +1000,40 @@ X86.fnIMULn = function IMULn(dst, src)
return result;
};
/**
* fnIMUL32(dst, src)
*
* This sets regMDHi:regMDLo to the 64-bit result of dst * src, both of which are treated as signed.
*
* TODO: Some potential optimizations include:
*
* 1) Early outs if either parameter is zero, since the result will obviously be zero
* 2) Using "normal" JavaScript multiplication if both parameters are >= -32768 && <= 32767
*
* Refer to: http://stackoverflow.com/questions/13597364/32-bit-signed-multiplication-with-a-64-bit-result-in-javascript
*
* @this {X86CPU}
* @param {number} dst (any 32-bit number, treated as signed)
* @param {number} src (any 32-bit number, treated as signed)
*/
X86.fnIMUL32 = function IMUL32(dst, src)
{
var fNeg = false;
if (src < 0) {
src = -src|0;
fNeg = !fNeg;
}
if (dst < 0) {
dst = -dst|0;
fNeg = !fNeg;
}
X86.fnMUL32.call(this, dst, src);
if (fNeg) {
this.regMDLo = (~this.regMDLo + 1)|0;
this.regMDHi = (~this.regMDHi + (this.regMDLo? 0 : 1))|0;
}
};
/**
* fnIMULw(dst, src)
*
@ -1498,40 +1724,6 @@ X86.fnMUL32 = function MUL32(dst, src)
this.regMDHi = mul32|0;
};
/**
* fnIMUL32(dst, src)
*
* This sets regMDHi:regMDLo to the 64-bit result of dst * src, both of which are treated as signed.
*
* TODO: Some potential optimizations include:
*
* 1) Early outs if either parameter is zero, since the result will obviously be zero
* 2) Using "normal" JavaScript multiplication if both parameters are >= -32768 && <= 32767
*
* Refer to: http://stackoverflow.com/questions/13597364/32-bit-signed-multiplication-with-a-64-bit-result-in-javascript
*
* @this {X86CPU}
* @param {number} dst (any 32-bit number, treated as signed)
* @param {number} src (any 32-bit number, treated as signed)
*/
X86.fnIMUL32 = function IMUL32(dst, src)
{
var fNeg = false;
if (src < 0) {
src = -src|0;
fNeg = !fNeg;
}
if (dst < 0) {
dst = -dst|0;
fNeg = !fNeg;
}
X86.fnMUL32.call(this, dst, src);
if (fNeg) {
this.regMDLo = (~this.regMDLo + 1)|0;
this.regMDHi = (~this.regMDHi + (this.regMDLo? 0 : 1))|0;
}
};
/**
* fnMULw(dst, src)
*

View file

@ -6,8 +6,9 @@
bits 16
start: mov eax,0x44332211
mov edx,0x88776655
mul edx
mov ecx,0x88776655
mul ecx
div ecx
call dword 0xf000:start