diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 46590e737..83c54531c 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -1969,7 +1969,7 @@ if (DEBUGGER) { }; /** - * traceLog(prop, dst, src, flagsIn, flagsOut, result) + * traceLog(prop, dst, src, flagsIn, flagsOut, resultLo, resultHi) * * @this {Debugger} * @param {string} prop @@ -1977,15 +1977,16 @@ if (DEBUGGER) { * @param {number} src * @param {number|null} flagsIn * @param {number|null} flagsOut - * @param {number} result + * @param {number} resultLo + * @param {number} [resultHi] */ - Debugger.prototype.traceLog = function(prop, dst, src, flagsIn, flagsOut, result) + Debugger.prototype.traceLog = function(prop, dst, src, flagsIn, flagsOut, resultLo, resultHi) { if (DEBUG) { if (this.traceEnabled !== undefined && this.traceEnabled[prop]) { var trace = Debugger.TRACE[prop]; var len = (trace.size >> 2); - var s = this.hexOffset(this.cpu.opLIP - this.cpu.segCS.base, this.cpu.getCS()) + " " + Debugger.INS_NAMES[trace.ins] + "(" + str.toHex(dst, len) + "," + str.toHex(src, len) + "," + (flagsIn === null? "-" : str.toHexWord(flagsIn)) + ") " + str.toHex(result, len) + "," + (flagsOut === null? "-" : str.toHexWord(flagsOut)); + var s = this.hexOffset(this.cpu.opLIP - this.cpu.segCS.base, this.cpu.getCS()) + " " + Debugger.INS_NAMES[trace.ins] + "(" + str.toHex(dst, len) + "," + str.toHex(src, len) + "," + (flagsIn === null? "-" : str.toHexWord(flagsIn)) + ") " + str.toHex(resultLo, len) + "," + (flagsOut === null? "-" : str.toHexWord(flagsOut)); if (!this.aTraceBuffer.length) this.aTraceBuffer = new Array(Debugger.TRACE_LIMIT); this.aTraceBuffer[this.iTraceBuffer++] = s; if (this.iTraceBuffer >= this.aTraceBuffer.length) { diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 01d01c32b..58d9040ae 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -1000,7 +1000,9 @@ X86CPU.prototype.resetRegs = function() * instructions perform calculations that must be propagated to specific registers (eg, AX and/or DX), which * the ModRM decoder functions don't know about. We initialize them here mainly for documentation purposes. */ - this.regMD16 = this.regMD32 = -1; + this.fMDSet = false; // regMDHi and/or regMDLo are invalid unless fMDSet is true + this.regMDLo = this.regMDHi = 0; + this.regXX = 0; // internal register for segment register and control register moves /* * Another internal "register" we occasionally need is an interim copy of bModRM, set inside selected opcode @@ -2438,7 +2440,7 @@ X86CPU.prototype.setPS = function(regPS, cpl) }; /** - * traceLog(prop, dst, src, flagsIn, flagsOut, result) + * traceLog(prop, dst, src, flagsIn, flagsOut, resultLo, resultHi) * * @this {X86CPU} * @param {string} prop @@ -2446,12 +2448,13 @@ X86CPU.prototype.setPS = function(regPS, cpl) * @param {number} src * @param {number|null} flagsIn * @param {number|null} flagsOut - * @param {number} result + * @param {number} resultLo + * @param {number} [resultHi] */ -X86CPU.prototype.traceLog = function(prop, dst, src, flagsIn, flagsOut, result) +X86CPU.prototype.traceLog = function(prop, dst, src, flagsIn, flagsOut, resultLo, resultHi) { if (DEBUG && this.dbg) { - this.dbg.traceLog(prop, dst, src, flagsIn, flagsOut, result); + this.dbg.traceLog(prop, dst, src, flagsIn, flagsOut, resultLo, resultHi); } }; diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index 72cebce5a..8231f80f5 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -490,14 +490,15 @@ X86.fnDIVb = function DIVb(dst, src) X86.fnDIVOverflow.call(this); return dst; } - this.regMD16 = this.regEAX = (uQuotient & 0xff) | (((this.regEAX % dst) & 0xff) << 8); + this.fMDSet = true; + this.regMDLo = this.regEAX = (uQuotient & 0xff) | (((this.regEAX % dst) & 0xff) << 8); /* * 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('DIVB', src, dst, null, this.getPS(), this.regMD16); + if (DEBUG && DEBUGGER) this.traceLog('DIVB', src, dst, null, this.getPS(), this.regMDLo); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesDivBR : this.cycleCounts.nOpCyclesDivBM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; @@ -533,15 +534,16 @@ X86.fnDIVw = function DIVw(dst, src) X86.fnDIVOverflow.call(this); return dst; } - this.regMD16 = this.regEAX = (uQuotient & 0xffff); - this.regMD32 = this.regEDX = (src % dst) & 0xffff; + this.fMDSet = true; + this.regMDLo = this.regEAX = (uQuotient & 0xffff); + this.regMDHi = this.regEDX = (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.regMD16 | (this.regMD32 << 16)); + if (DEBUG && DEBUGGER) this.traceLog('DIVW', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16)); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesDivWR : this.cycleCounts.nOpCyclesDivWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; @@ -593,14 +595,15 @@ X86.fnIDIVb = function IDIVb(dst, src) X86.fnDIVOverflow.call(this); return dst; } - this.regMD16 = this.regEAX = (lQuotient & 0xff) | (((((this.regEAX << 16) >> 16) % ((dst << 24) >> 24)) & 0xff) << 8); + this.fMDSet = true; + this.regMDLo = this.regEAX = (lQuotient & 0xff) | (((((this.regEAX << 16) >> 16) % ((dst << 24) >> 24)) & 0xff) << 8); /* * 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('IDIVB', src, dst, null, this.getPS(), this.regMD16); + if (DEBUG && DEBUGGER) this.traceLog('IDIVB', src, dst, null, this.getPS(), this.regMDLo); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIDivBR : this.cycleCounts.nOpCyclesIDivBM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; @@ -641,15 +644,16 @@ X86.fnIDIVw = function IDIVw(dst, src) X86.fnDIVOverflow.call(this); return dst; } - this.regMD16 = this.regEAX = (lQuotient & 0xffff); - this.regMD32 = this.regEDX = (src % lDivisor) & 0xffff; + this.fMDSet = true; + this.regMDLo = this.regEAX = (lQuotient & 0xffff); + this.regMDHi = this.regEDX = (src % lDivisor) & 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.regMD16 | (this.regMD32 << 16)); + if (DEBUG && DEBUGGER) this.traceLog('IDIVW', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16)); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIDivWR : this.cycleCounts.nOpCyclesIDivWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; @@ -751,7 +755,8 @@ X86.fnIMULn = function IMULn(dst, src) X86.fnIMULb = function IMULb(dst, src) { var result = (((src = this.regEAX) << 24) >> 24) * ((dst << 24) >> 24); - this.regEAX = this.regMD16 = result & 0xffff; + this.fMDSet = true; + this.regEAX = this.regMDLo = result & 0xffff; if (result > 127 || result < -128) { this.setCF(); this.setOF(); } else { @@ -763,7 +768,7 @@ X86.fnIMULb = function IMULb(dst, src) * 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('IMULB', src, dst, null, this.getPS(), this.regMD16); + if (DEBUG && DEBUGGER) this.traceLog('IMULB', src, dst, null, this.getPS(), this.regMDLo); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIMulBR : this.cycleCounts.nOpCyclesIMulBM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; @@ -793,8 +798,9 @@ X86.fnIMULb = function IMULb(dst, src) X86.fnIMULw = function IMULw(dst, src) { var result = (((src = this.regEAX) << 16) >> 16) * ((dst << 16) >> 16); - this.regEAX = this.regMD16 = result & 0xffff; - this.regEDX = this.regMD32 = (result >> 16) & 0xffff; + this.fMDSet = true; + this.regEAX = this.regMDLo = result & 0xffff; + this.regEDX = this.regMDHi = (result >> 16) & 0xffff; if (result > 32767 || result < -32768) { this.setCF(); this.setOF(); } else { @@ -806,7 +812,7 @@ X86.fnIMULw = function IMULw(dst, src) * 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('IMULW', src, dst, null, this.getPS(), this.regMD16 | (this.regMD32 << 16)); + if (DEBUG && DEBUGGER) this.traceLog('IMULW', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16)); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIMulWR : this.cycleCounts.nOpCyclesIMulWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; @@ -1362,16 +1368,16 @@ X86.fnMOVn = function MOVn(dst, src) }; /** - * fnMOVMD16(dst, src) + * fnMOVxx(dst, src) * * @this {X86CPU} * @param {number} dst (current value, ignored) * @param {number} src (new value) - * @return {number} dst (src is overridden, replaced with regMD16, as specified by opMOVwsr()) + * @return {number} dst (src is overridden, replaced with regXX, as specified by opMOVwsr()) */ -X86.fnMOVMD16 = function MOVMD16(dst, src) +X86.fnMOVxx = function MOVxx(dst, src) { - return X86.fnMOV.call(this, dst, this.regMD16); + return X86.fnMOV.call(this, dst, this.regXX); }; /** @@ -1384,24 +1390,96 @@ X86.fnMOVMD16 = function MOVMD16(dst, src) */ X86.fnMULb = function MULb(dst, src) { - this.regEAX = this.regMD16 = ((src = this.regEAX & 0xff) * dst) & 0xffff; - if (this.regEAX & 0xff00) { + this.fMDSet = true; + this.regMDLo = ((src = this.regEAX & 0xff) * dst) & 0xffff; + + if (this.regMDLo & 0xff00) { this.setCF(); this.setOF(); } else { this.clearCF(); this.clearOF(); } + /* * 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('MULB', src, dst, null, this.getPS(), this.regMD16); + if (DEBUG && DEBUGGER) this.traceLog('MULB', src, dst, null, this.getPS(), this.regMDLo); + this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesMulBR : this.cycleCounts.nOpCyclesMulBM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; +/** + * fnMUL32(dst, src) + * + * This sets regMDHi:regMDLo to the 64-bit result of dst * src, both of which are treated as unsigned. + * + * 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 < 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 unsigned) + * @param {number} src (any 32-bit number, treated as unsigned) + */ +X86.fnMUL32 = function MUL32(dst, src) +{ + var srcLo = src & 0xffff; + var srcHi = src >>> 16; + var dstLo = dst & 0xffff; + var dstHi = dst >>> 16; + + var mul00 = srcLo * dstLo; + var mul16 = ((mul00 >>> 16) + (srcHi * dstLo)); + var mul32 = mul16 >>> 16; + mul16 = ((mul16 & 0xffff) + (srcLo * dstHi)); + mul32 += ((mul16 >>> 16) + (srcHi * dstHi)); + + this.fMDSet = true; + this.regMDLo = (mul16 << 16) | (mul00 & 0xffff); + this.regMDHi = mul32|0; +}; + +/** + * fnIMUL32(dst, src) + * + * This sets regMDHi:regMDLo to the 64-bit result of dst * src, either of which may be signed or unsigned. + * + * 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) * @@ -1412,21 +1490,35 @@ X86.fnMULb = function MULb(dst, src) */ X86.fnMULw = function MULw(dst, src) { - var result = (src = this.regEAX) * dst; - this.regMD16 = this.regEAX = result & 0xffff; - this.regMD32 = this.regEDX = (result >> 16) & 0xffff; - if (this.regEDX) { + if (this.dataSize == 2) { + src = this.regEAX & 0xffff; + var result = src * dst; + this.fMDSet = true; + this.regMDLo = result & 0xffff; + this.regMDHi = (result >> 16) & 0xffff; + } else { + X86.fnMUL32.call(this, dst, this.regEAX); + } + + if (this.regMDHi) { this.setCF(); this.setOF(); } else { this.clearCF(); this.clearOF(); } + /* * 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('MULW', src, dst, null, this.getPS(), this.regMD16 | (this.regMD32 << 16)); + if (DEBUG && DEBUGGER) { + if (this.dataSize == 2) { + this.traceLog('MULW', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16)); + } else { + this.traceLog('MULD', src, dst, null, this.getPS(), this.regMDLo, this.regMDHi); + } + } this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesMulWR : this.cycleCounts.nOpCyclesMulWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index d5f5b0d99..41d2122d8 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -201,8 +201,8 @@ X86.opCLTS = function CLTS() * op=0x0F,0x20 (MOV reg,creg) * * NOTE: Since the ModRM decoders deal only with general-purpose registers, we must move - * the appropriate control register into a special variable (regMD16), which our helper function - * (fnMOVMD16) will use to replace the decoder's src operand. + * the appropriate control register into a special variable (regXX), which our helper function + * (fnMOVxx) will use to replace the decoder's src operand. * * @this {X86CPU} */ @@ -221,16 +221,16 @@ X86.opMOVrc = function MOVrc() var reg = (bModRM & 0x38) >> 3; switch(reg) { case 0x0: - this.regMD16 = this.regCR0; + this.regXX = this.regCR0; break; case 0x1: - this.regMD16 = this.regCR1; + this.regXX = this.regCR1; break; case 0x2: - this.regMD16 = this.regCR2; + this.regXX = this.regCR2; break; case 0x3: - this.regMD16 = this.regCR3; + this.regXX = this.regCR3; break; default: X86.opUndefined.call(this); @@ -242,7 +242,7 @@ X86.opMOVrc = function MOVrc() * * this.opFlags |= X86.OPFLAG.NOREAD; */ - this.aOpModRegWord[bModRM].call(this, X86.fnMOVMD16); + this.aOpModRegWord[bModRM].call(this, X86.fnMOVxx); }; /** diff --git a/modules/pcjs/lib/x86ops.js b/modules/pcjs/lib/x86ops.js index ffb6678d9..2a5fe51cc 100644 --- a/modules/pcjs/lib/x86ops.js +++ b/modules/pcjs/lib/x86ops.js @@ -2057,8 +2057,8 @@ X86.opMOVrw = function MOVrw() * op=0x8C (MOV word,sreg) * * NOTE: Since the ModRM decoders deal only with general-purpose registers, we must move - * the appropriate segment register into a special variable (regMD16), which our helper function - * (fnMOVMD16) will use to replace the decoder's src operand. + * the appropriate segment register into a special variable (regXX), which our helper function + * (fnMOVxx) will use to replace the decoder's src operand. * * @this {X86CPU} */ @@ -2068,27 +2068,27 @@ X86.opMOVwsr = function MOVwsr() var reg = (bModRM & 0x38) >> 3; switch (reg) { case 0x0: - this.regMD16 = this.segES.sel; + this.regXX = this.segES.sel; break; case 0x1: - this.regMD16 = this.segCS.sel; + this.regXX = this.segCS.sel; break; case 0x2: - this.regMD16 = this.segSS.sel; + this.regXX = this.segSS.sel; break; case 0x3: - this.regMD16 = this.segDS.sel; + this.regXX = this.segDS.sel; break; case 0x4: if (I386 && this.model >= X86.MODEL_80386) { - this.regMD16 = this.segFS.sel; + this.regXX = this.segFS.sel; break; } X86.opInvalid.call(this); break; case 0x5: if (I386 && this.model >= X86.MODEL_80386) { - this.regMD16 = this.segGS.sel; + this.regXX = this.segGS.sel; break; } /* falls through */ @@ -2100,7 +2100,7 @@ X86.opMOVwsr = function MOVwsr() * Like other MOV operations, the destination does not need to be read, just written. */ this.opFlags |= X86.OPFLAG.NOREAD; - this.aOpModMemWord[bModRM].call(this, X86.fnMOVMD16); + this.aOpModMemWord[bModRM].call(this, X86.fnMOVxx); }; /** @@ -2207,8 +2207,6 @@ X86.opMOVsrw = function MOVsrw() this.setDS(this.regEDI); this.regEDI = temp; break; - default: - break; // there IS no other case, but JavaScript inspections don't know that } }; @@ -3841,7 +3839,7 @@ X86.opCMC = function CMC() * know what the target is (only the target's value), it cannot easily work around the problem. * * A simple, albeit kludgy, solution is for fnMULb to always save its result in a special - * "register" (eg, regMD16), which we will then put back into regEAX if it's been updated. + * "register" (eg, regMDLo), which we will then put back into regEAX if it's been updated. * This also relieves us from having to decode any part of the ModRM byte, so maybe it's not * such a bad work-around after all. * @@ -3851,9 +3849,9 @@ X86.opCMC = function CMC() */ X86.opGRP3b = function GRP3b() { - this.regMD16 = -1; + this.fMDSet = false; this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp3b, X86.fnSrcNone); - if (this.regMD16 >= 0) this.regEAX = this.regMD16; + if (this.fMDSet) this.regEAX = (this.regEAX & ~this.dataMask) | (this.regMDLo & this.dataMask); }; /** @@ -3869,7 +3867,7 @@ X86.opGRP3b = function GRP3b() * know what the target is (only the target's value), it cannot easily work around the problem. * * A simple, albeit kludgey, solution is for fnMULw to always save its result in a special - * "register" (eg, regMD16/regMD32), which we will then put back into regEAX/regEDX if it's been + * "register" (eg, regMDLo/regMDHi), which we will then put back into regEAX/regEDX if it's been * updated. This also relieves us from having to decode any part of the ModRM byte, so maybe * it's not such a bad work-around after all. * @@ -3877,11 +3875,11 @@ X86.opGRP3b = function GRP3b() */ X86.opGRP3w = function GRP3w() { - this.regMD16 = -1; + this.fMDSet = false; this.aOpModGrpWord[this.getIPByte()].call(this, X86.aOpGrp3w, X86.fnSrcNone); - if (this.regMD16 >= 0) { - this.regEAX = this.regMD16; - this.regEDX = this.regMD32; + if (this.fMDSet) { + this.regEAX = (this.regEAX & ~this.dataMask) | (this.regMDLo & this.dataMask); + this.regEDX = (this.regEDX & ~this.dataMask) | (this.regMDHi & this.dataMask); } }; diff --git a/tests/pc/80386/tests.nasm b/tests/pc/80386/tests.nasm index b5cc9e00e..b391489ce 100644 --- a/tests/pc/80386/tests.nasm +++ b/tests/pc/80386/tests.nasm @@ -7,7 +7,7 @@ start: mov eax,0x44332211 mov edx,0x88776655 - shrd eax,edx,4 + mul edx call dword 0xf000:start