/** * @fileoverview Implements PCx86 8086 opcode helpers. * @author Jeff Parsons * @copyright © Jeff Parsons 2012-2017 * * This file is part of PCjs, a computer emulation software project at . * * PCjs is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCjs. If not, * see . * * You are required to include the above copyright notice in every modified copy of this work * and to display that copyright notice when the software starts running; see COPYRIGHT in * . * * Some PCjs files also attempt to load external resource files, such as character-image files, * ROM files, and disk image files. Those external resource files are not considered part of PCjs * for purposes of the GNU General Public License, and the author does not claim any copyright * as to their contents. */ "use strict"; if (NODE) { var Messages = require("./messages"); var X86 = require("./x86"); } /** * fnADCb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnADCb = function(dst, src) { var b = (dst + src + this.getCarry())|0; this.setArithResult(dst, src, b, X86.RESULT.BYTE | X86.RESULT.ALL); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return b & 0xff; }; /** * fnADCw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnADCw = function(dst, src) { var w = (dst + src + this.getCarry())|0; this.setArithResult(dst, src, w, this.typeData | X86.RESULT.ALL); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return w & this.maskData; }; /** * fnADDb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnADDb = function(dst, src) { var b = (dst + src)|0; this.setArithResult(dst, src, b, X86.RESULT.BYTE | X86.RESULT.ALL); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return b & 0xff; }; /** * fnADDw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnADDw = function(dst, src) { var w = (dst + src)|0; this.setArithResult(dst, src, w, this.typeData | X86.RESULT.ALL); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return w & this.maskData; }; /** * fnANDb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnANDb = function(dst, src) { var b = dst & src; this.setLogicResult(b, X86.RESULT.BYTE); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return b; }; /** * fnANDw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnANDw = function(dst, src) { this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return this.setLogicResult(dst & src, this.typeData) & this.maskData; }; /** * fnARPL(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnARPL = function(dst, src) { this.nStepCycles -= (10 + (this.regEA === X86.ADDR_INVALID? 0 : 1)); if ((dst & X86.SEL.RPL) < (src & X86.SEL.RPL)) { dst = (dst & ~X86.SEL.RPL) | (src & X86.SEL.RPL); this.setZF(); return dst; } this.clearZF(); return dst; }; /** * fnBOUND(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBOUND = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { /* * Generate UD_FAULT (INT 0x06: Invalid Opcode) if src is not a memory operand. */ X86.opInvalid.call(this); return dst; } /* * Note that BOUND performs signed comparisons, so we must transform all arguments into signed values. */ var wIndex = dst; var wLower = this.getWord(this.regEA); var wUpper = this.getWord(this.regEA + this.sizeData); if (this.sizeData == 2) { wIndex = (dst << 16) >> 16; wLower = (wLower << 16) >> 16; wUpper = (wUpper << 16) >> 16; } this.nStepCycles -= this.cycleCounts.nOpCyclesBound; if (wIndex < wLower || wIndex > wUpper) { /* * The INT 0x05 handler must be called with CS:IP pointing to the BOUND instruction. * * TODO: Determine the cycle cost when a BOUND exception is triggered, over and above nCyclesBound, * and then call X86.helpFault(X86.EXCEPTION.BR_FAULT, null, nCycles). */ X86.helpFault.call(this, X86.EXCEPTION.BR_FAULT); } this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnBSF(dst, src) * * Scan src starting at bit 0. If a set bit is found, the bit index is stored in dst and ZF is cleared; * otherwise, ZF is set and dst is unchanged. * * NOTES: Early versions of the 80386 manuals misstated how ZF was set/cleared. Also, Intel insists that * dst is undefined whenever ZF is set, but in fact, the 80386 leaves dst unchanged when that happens; * unfortunately, some early 80486s would always modify dst, so it is unsafe to rely on dst when ZF is set. * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBSF = function(dst, src) { var n = 0; if (!src) { this.setZF(); } else { this.clearZF(); var bit = 0x1; while (bit & this.maskData) { if (src & bit) { dst = n; break; } bit <<= 1; n++; // TODO: Determine if n should be incremented before the bailout for an accurate cycle count } } this.nStepCycles -= 11 + n * 3; return dst; }; /** * fnBSR(dst, src) * * Scan src starting from the highest bit. If a set bit is found, the bit index is stored in dst and ZF is * cleared; otherwise, ZF is set and dst is unchanged. * * NOTES: Early versions of the 80386 manuals misstated how ZF was set/cleared. Also, Intel insists that * dst is undefined whenever ZF is set, but in fact, the 80386 leaves dst unchanged when that happens; * unfortunately, some early 80486s would always modify dst, so it is unsafe to rely on dst when ZF is set. * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBSR = function(dst, src) { var n = 0; if (!src) { this.setZF(); } else { this.clearZF(); var i = (this.sizeData == 2? 15 : 31), bit = 1 << i; while (bit) { if (src & bit) { dst = i; break; } bit >>>= 1; n++; i--; // TODO: Determine if n should be incremented before the bailout for an accurate cycle count } } this.nStepCycles -= 11 + n * 3; return dst; }; /** * fnBT(dst, src) * * In this form of BT, src is an immediate operand (OR dst is register operand); immediate operands * are supposed to be masked with either 0xf or 0x1f for 16-bit or 32-bit operands, respectively. * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBT = function(dst, src) { var bit = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & bit) this.setCF(); else this.clearCF(); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 3 : 6); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnBTC(dst, src) * * In this form of BTC, src is an immediate operand (OR dst is register operand); immediate operands * are supposed to be masked with either 0xf or 0x1f for 16-bit or 32-bit operands, respectively. * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBTC = function(dst, src) { var bit = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & bit) this.setCF(); else this.clearCF(); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 6 : 8); return dst ^ bit; }; /** * fnBTR(dst, src) * * In this form of BTR, src is an immediate operand (OR dst is register operand); immediate operands * are supposed to be masked with either 0xf or 0x1f for 16-bit or 32-bit operands, respectively. * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBTR = function(dst, src) { var bit = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & bit) this.setCF(); else this.clearCF(); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 6 : 8); return dst & ~bit; }; /** * fnBTS(dst, src) * * In this form of BTS, src is an immediate operand (OR dst is register operand); immediate operands * are supposed to be masked with either 0xf or 0x1f for 16-bit or 32-bit operands, respectively. * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBTS = function(dst, src) { var bit = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & bit) this.setCF(); else this.clearCF(); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 6 : 8); return dst | bit; }; /** * fnBTMem(dst, src) * * In this form of BT, src is a register operand, which is NOT truncated if dst is a memory operand; * however, if dst is also a register operand, then we defer to the simpler function, fnBT(). * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBTMem = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { return X86.fnBT.call(this, dst, src); } /* * TODO: Consider a worker function that performs the following block of code for: BT, BTC, BTR, and BTS. * It's somewhat inconvenient, because it needs to provide two results: an updated src AND an updated dst. * * src is usually positive BUT can also be negative (as the IA32 spec says: "The offset operand then selects * a bit position within the range −231 to 231 − 1 for a register offset and 0 to 31 for an immediate offset.") */ var max = this.sizeData << 3; if (src >= max || src < -max) { /* * We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size, * which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to * the original EA offset. */ var i = src >> (this.sizeData == 2? 4 : 5); dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData); } /* * Now we convert src from a bit index to a bit mask. */ src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & src) this.setCF(); else this.clearCF(); /* * End of common code block */ this.nStepCycles -= 6; this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnBTCMem(dst, src) * * In this form of BTC, src is a register operand, which is NOT truncated if dst is a memory operand; * however, if dst is also a register operand, then we defer to the simpler function, fnBTC(). * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBTCMem = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { return X86.fnBTC.call(this, dst, src); } /* * src is usually positive BUT can also be negative (as the IA32 spec says: "The offset operand then selects * a bit position within the range −231 to 231 − 1 for a register offset and 0 to 31 for an immediate offset.") */ var max = this.sizeData << 3; if (src >= max || src < -max) { /* * We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size, * which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to * the original EA offset. */ var i = src >> (this.sizeData == 2? 4 : 5); dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData); } /* * Now we convert src from a bit index to a bit mask. */ src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & src) this.setCF(); else this.clearCF(); this.nStepCycles -= 8; return dst ^ src; }; /** * fnBTRMem(dst, src) * * In this form of BTR, src is a register operand, which is NOT truncated if dst is a memory operand; * however, if dst is also a register operand, then we defer to the simpler function, fnBTR(). * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBTRMem = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { return X86.fnBTR.call(this, dst, src); } /* * src is usually positive BUT can also be negative (as the IA32 spec says: "The offset operand then selects * a bit position within the range −231 to 231 − 1 for a register offset and 0 to 31 for an immediate offset.") */ var max = this.sizeData << 3; if (src >= max || src < -max) { /* * We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size, * which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to * the original EA offset. */ var i = src >> (this.sizeData == 2? 4 : 5); dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData); } /* * Now we convert src from a bit index to a bit mask. */ src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & src) this.setCF(); else this.clearCF(); this.nStepCycles -= 8; return dst & ~src; }; /** * fnBTSMem(dst, src) * * In this form of BTS, src is a register operand, which is NOT truncated if dst is a memory operand; * however, if dst is also a register operand, then we defer to the simpler function, fnBTS(). * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnBTSMem = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { return X86.fnBTS.call(this, dst, src); } /* * src is usually positive BUT can also be negative (as the IA32 spec says: "The offset operand then selects * a bit position within the range −231 to 231 − 1 for a register offset and 0 to 31 for an immediate offset.") */ var max = this.sizeData << 3; if (src >= max || src < -max) { /* * We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size, * which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to * the original EA offset. */ var i = src >> (this.sizeData == 2? 4 : 5); dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData); } /* * Now we convert src from a bit index to a bit mask. */ src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & src) this.setCF(); else this.clearCF(); this.nStepCycles -= 8; return dst | src; }; /** * fnCALLw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnCALLw = function(dst, src) { this.pushWord(this.getIP()); this.setIP(dst); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesCallWR : this.cycleCounts.nOpCyclesCallWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnCALLFdw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnCALLFdw = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { return X86.fnGRPUndefined.call(this, dst, src); } /* * Originally, we would snapshot regLSP into opLSP because helpCALLF() could trigger a segment fault, * but additionally, the stack segment could trigger either a segment fault or a page fault; indeed, * any operation that performs multiple stack modifications must take this precaution and snapshot regLSP. */ this.opLSP = this.regLSP; X86.helpCALLF.call(this, dst, this.getShort(this.regEA + this.sizeData)); this.nStepCycles -= this.cycleCounts.nOpCyclesCallDM; this.opFlags |= X86.OPFLAG.NOWRITE; this.opLSP = X86.ADDR_INVALID; return dst; }; /** * fnCMPb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} dst unchanged */ X86.fnCMPb = function(dst, src) { var b = (dst - src)|0; this.setArithResult(dst, src, b, X86.RESULT.BYTE | X86.RESULT.ALL, true); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesCompareRM) : this.cycleCounts.nOpCyclesArithRM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnCMPw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} dst unchanged */ X86.fnCMPw = function(dst, src) { var w = (dst - src)|0; this.setArithResult(dst, src, w, this.typeData | X86.RESULT.ALL, true); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesCompareRM) : this.cycleCounts.nOpCyclesArithRM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnDECb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnDECb = function(dst, src) { var b = (dst - 1)|0; this.setArithResult(dst, 1, b, X86.RESULT.BYTE | X86.RESULT.NOTCF, true); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIncR : this.cycleCounts.nOpCyclesIncM); return b & 0xff; }; /** * fnDECw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnDECw = function(dst, src) { var w = (dst - 1)|0; this.setArithResult(dst, 1, w, this.typeData | X86.RESULT.NOTCF, true); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIncR : this.cycleCounts.nOpCyclesIncM); return w & this.maskData; }; /** * fnDIVb(dst, src) * * @this {X86CPU} * @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(dst, src) { /* * Detect zero divisor */ if (!dst) { X86.helpDIVOverflow.call(this); return dst; } /* * Detect too-small divisor (quotient overflow) */ var result = ((src = this.regEAX & 0xffff) / dst); if (result > 0xff) { X86.helpDIVOverflow.call(this); return dst; } this.regMDLo = (result & 0xff) | (((src % dst) & 0xff) << 8); this.fMDSet = true; this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesDivBR : this.cycleCounts.nOpCyclesDivBM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnDIVw(dst, src) * * @this {X86CPU} * @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(dst, src) { if (this.sizeData == 2) { /* * Detect zero divisor */ if (!dst) { X86.helpDIVOverflow.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); if (result >= 0x10000) { X86.helpDIVOverflow.call(this); return dst; } this.regMDLo = (result & 0xffff); this.regMDHi = (src % dst) & 0xffff; } else { if (!X86.helpDIV32.call(this, this.regEAX, this.regEDX, dst)) { X86.helpDIVOverflow.call(this); return dst; } this.regMDLo |= 0; this.regMDHi |= 0; } this.fMDSet = true; this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesDivWR : this.cycleCounts.nOpCyclesDivWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnESC(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} dst unchanged */ X86.fnESC = function(dst, src) { if (this.fpu) { this.fpu.opFPU(this.bOpcode, this.bModRM, dst, src); } this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 2 : 8); return dst; }; /** * fnGRPFault(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnGRPFault = function(dst, src) { /* * This should NEVER be called on 8086/8088 CPUs, and yet we preset some of the handlers in aOpGrpPOPw, * aOpGrp4b, and aOpGrp4w to call it. initProcessor() DOES patch aOpGrp4b[0x07] and aOpGrp4w[0x07] to * fnGRPInvalid, but that's it. * * However, given the infrequency of this call, it's simpler to continue presetting all the handlers in * aOpGrpPOPw to their post-8086 default, and deal with the appropriate 8086 behavior here (which for now, * is to call fnGRPUndefined instead). */ if (this.model < X86.MODEL_80186) { return X86.fnGRPUndefined.call(this, dst, src); } X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return dst; }; /** * fnGRPInvalid(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnGRPInvalid = function(dst, src) { X86.opInvalid.call(this); return dst; }; /** * fnGRPUndefined(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnGRPUndefined = function(dst, src) { X86.opUndefined.call(this); return dst; }; /** * fnIDIVb(dst, src) * * @this {X86CPU} * @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(dst, src) { /* * Detect zero divisor */ if (!dst) { X86.helpDIVOverflow.call(this); return dst; } /* * Detect too-small divisor (quotient overflow) */ var div = ((dst << 24) >> 24); var result = ((src = (this.regEAX << 16) >> 16) / 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 << 24) >> 24) || this.model == X86.MODEL_8086 && result == -128) { X86.helpDIVOverflow.call(this); return dst; } this.regMDLo = (result & 0xff) | (((src % div) & 0xff) << 8); this.fMDSet = true; this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIDivBR : this.cycleCounts.nOpCyclesIDivBM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnIDIVw(dst, src) * * @this {X86CPU} * @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(dst, src) { if (this.sizeData == 2) { /* * Detect zero divisor */ if (!dst) { X86.helpDIVOverflow.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.helpDIVOverflow.call(this); return dst; } this.regMDLo = (result & 0xffff); this.regMDHi = (src % div) & 0xffff; } else { if (!X86.helpIDIV32.call(this, this.regEAX, this.regEDX, dst)) { X86.helpDIVOverflow.call(this); return dst; } this.regMDLo |= 0; this.regMDHi |= 0; } this.fMDSet = true; this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIDivWR : this.cycleCounts.nOpCyclesIDivWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnIMUL8(dst, src) * * 80286_and_80287_Programmers_Reference_Manual_1987.pdf, p.B-44 (p.254) notes that: * * "The low 16 bits of the product of a 16-bit signed multiply are the same as those of an * unsigned multiply. The three operand IMUL instruction can be used for unsigned operands as well." * * However, we still sign-extend the operands before multiplying, making it easier to range-check the result. * * (80186/80188 and up) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnIMUL8 = function(dst, src) { /* * NOTE: getIPDisp() already sign-extends the dst parameter, so fnIMULrw() needlessly sign-extends it again; * a small price to pay for a common function. */ var result = X86.fnIMULrw.call(this, this.getIPDisp(), src); /* * NOTE: The above function already accounted for the 80386 cycle count, so we are simply accounting for the * increased time on an 80286; the 80186/80188 have even larger values, but we'll worry about that another day. */ if (this.model < X86.MODEL_80386) this.nStepCycles -= 12; return result; }; /** * fnIMULn(dst, src) * * 80286_and_80287_Programmers_Reference_Manual_1987.pdf, p.B-44 (p.254) notes that: * * "The low 16 bits of the product of a 16-bit signed multiply are the same as those of an * unsigned multiply. The three operand IMUL instruction can be used for unsigned operands as well." * * However, we still sign-extend the operands before multiplying, making it easier to range-check the result. * * (80186/80188 and up) * * @this {X86CPU} * @param {number} dst (not used) * @param {number} src * @return {number} */ X86.fnIMULn = function(dst, src) { var result; dst = this.getIPWord(); if (this.sizeData == 2) { result = X86.fnIMULrw.call(this, dst, src); } else { result = X86.fnIMULrd.call(this, dst, src); } /* * NOTE: The above functions already accounted for 80386 cycle counts, so we are simply accounting for the * increased time on an 80286; the 80186/80188 have even larger values, but we'll worry about that another day. */ if (this.model < X86.MODEL_80386) this.nStepCycles -= 12; return result; }; /** * fnIMUL32(dst, src) * * This sets regMDHi:regMDLo to the 64-bit result of dst * src, both of which are treated as signed. * * @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(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; } }; /** * fnIMULb(dst, src) * * This 16-bit multiplication must indicate when the upper 8 bits are simply a sign-extension of the * lower 8 bits (carry clear) and when the upper 8 bits contain significant bits (carry set). The latter * will occur whenever a positive result is > 127 (0x007f) and whenever a negative result is < -128 * (0xff80). * * Example 1: 16 * 4 = 64 (0x0040): carry is clear * Example 2: 16 * 8 = 128 (0x0080): carry is set (the sign bit no longer fits in the lower 8 bits) * Example 3: 16 * -8 (0xf8) = -128 (0xff80): carry is clear (the sign bit *still* fits in the lower 8 bits) * Example 4: 16 * -16 (0xf0) = -256 (0xff00): carry is set (the sign bit no longer fits in the lower 8 bits) * * @this {X86CPU} * @param {number} dst * @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(dst, src) { var result = (((this.regEAX << 24) >> 24) * ((dst << 24) >> 24))|0; this.regMDLo = result & 0xffff; if (result > 127 || result < -128) { this.setCF(); this.setOF(); } else { this.clearCF(); this.clearOF(); } this.fMDSet = true; this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIMulBR : this.cycleCounts.nOpCyclesIMulBM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnIMULw(dst, src) * * regMDHi:regMDLo = dst * regEAX * * This 32-bit multiplication must indicate when the upper 16 bits are simply a sign-extension of the * lower 16 bits (carry clear) and when the upper 16 bits contain significant bits (carry set). The latter * will occur whenever a positive result is > 32767 (0x00007fff) and whenever a negative result is < -32768 * (0xffff8000). * * Example 1: 256 * 64 = 16384 (0x00004000): carry is clear * Example 2: 256 * 128 = 32768 (0x00008000): carry is set (the sign bit no longer fits in the lower 16 bits) * Example 3: 256 * -128 (0xff80) = -32768 (0xffff8000): carry is clear (the sign bit *still* fits in the lower 16 bits) * Example 4: 256 * -256 (0xff00) = -65536 (0xffff0000): carry is set (the sign bit no longer fits in the lower 16 bits) * * @this {X86CPU} * @param {number} dst * @param {number} src (null; AX or EAX is the implied src) * @return {number} (we return dst unchanged, since it's actually DX:AX or EDX:EAX that's modified) */ X86.fnIMULw = function(dst, src) { var fOverflow; if (this.sizeData == 2) { src = this.regEAX & 0xffff; var result = (((src << 16) >> 16) * ((dst << 16) >> 16))|0; this.regMDLo = result & 0xffff; this.regMDHi = (result >> 16) & 0xffff; fOverflow = (result > 32767 || result < -32768); } else { X86.fnIMUL32.call(this, dst, this.regEAX); fOverflow = (this.regMDHi != (this.regMDLo >> 31)); } if (fOverflow) { this.setCF(); this.setOF(); } else { this.clearCF(); this.clearOF(); } this.fMDSet = true; this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIMulWR : this.cycleCounts.nOpCyclesIMulWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnIMULrw(dst, src) * * This function exists for 16-bit IMUL instructions that produce a 16-bit result instead of a 32-bit result * (and don't implicitly use the accumulator). * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnIMULrw = function(dst, src) { /* * Unlike fnIMULrd() below, we can use normal JavaScript multiplication, because there's no danger of * overflowing the floating-point result and losing accuracy in the bottom 16 bits. */ var result = (((dst << 16) >> 16) * ((src << 16) >> 16))|0; if (result > 32767 || result < -32768) { this.setCF(); this.setOF(); } else { this.clearCF(); this.clearOF(); } result &= 0xffff; this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 9 : 12); return result; }; /** * fnIMULrd(dst, src) * * This function exists for 32-bit IMUL instructions that produce a 32-bit result instead of a 64-bit result * (and don't implicitly use the accumulator). * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnIMULrd = function(dst, src) { /* * The following code works, but I've stopped using it because it produces different results from an actual CPU * when overflow occurs; the bottom 32 bits of the result are still supposed to be accurate. * * And unfortunately, we cannot achieve that level of compatibility using normal JavaScript multiplication, * because the result may be too large to fit in a JavaScript floating-point variable, which means we could lose * accuracy in the bottom 32 bits, which would defeat what we're trying to achieve here. So we must use the * slower fnIMUL32() function. * * var result = dst * src; * if (result > 2147483647 || result < -2147483648) { * this.setCF(); this.setOF(); * } else { * this.clearCF(); this.clearOF(); * } * result |= 0; */ X86.fnIMUL32.call(this, dst, src); var fOverflow = (this.regMDHi != (this.regMDLo >> 31)); if (fOverflow) { this.setCF(); this.setOF(); } else { this.clearCF(); this.clearOF(); } this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 9 : 12); return this.regMDLo; }; /** * fnINCb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnINCb = function(dst, src) { var b = (dst + 1)|0; this.setArithResult(dst, 1, b, X86.RESULT.BYTE | X86.RESULT.NOTCF); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIncR : this.cycleCounts.nOpCyclesIncM); return b & 0xff; }; /** * fnINCw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnINCw = function(dst, src) { var w = (dst + 1)|0; this.setArithResult(dst, 1, w, this.typeData | X86.RESULT.NOTCF); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIncR : this.cycleCounts.nOpCyclesIncM); return w & this.maskData; }; /** * fnJMPw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnJMPw = function(dst, src) { this.setIP(dst); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesJmpWR : this.cycleCounts.nOpCyclesJmpWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnJMPFdw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnJMPFdw = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { return X86.fnGRPUndefined.call(this, dst, src); } this.setCSIP(dst, this.getShort(this.regEA + this.sizeData)); if (MAXDEBUG && this.cIntReturn) this.checkIntReturn(this.regLIP); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpDM; this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnLAR(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnLAR = function(dst, src) { this.nStepCycles -= (14 + (this.regEA === X86.ADDR_INVALID? 0 : 2)); /* * Currently, segVER.load() will return an error only if the selector is beyond the bounds of the * descriptor table or the descriptor is not for a segment. * * TODO: This instruction's 80286 documentation does not discuss conforming code segments; determine * if we need a special check for them. */ this.clearZF(); if (this.segVER.load(src) !== X86.ADDR_INVALID) { if (this.segVER.dpl >= this.nCPL && this.segVER.dpl >= (src & X86.SEL.RPL)) { this.setZF(); dst = this.segVER.acc & ~X86.DESC.ACC.BASE1623; if (this.sizeData > 2) { dst |= ((this.segVER.ext & ~X86.DESC.EXT.BASE2431) << 16); } } } return dst; }; /** * fnLDS(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnLDS = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { X86.opUndefined.call(this); return dst; } this.setDS(this.getShort(this.regEA + this.sizeData)); this.nStepCycles -= this.cycleCounts.nOpCyclesLS; return src; }; /** * fnLEA(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnLEA = function(dst, src) { /* * TODO: Until I bite the bullet and choose a truly invalid value for X86.ADDR_INVALID (eg, null), * this code must be disabled, because otherwise an instruction like "LEA ECX,[EAX-1]" will fail when * EAX is zero. And we can't have that. * if (this.regEA === X86.ADDR_INVALID) { // // TODO: After reading http://www.os2museum.com/wp/undocumented-8086-opcodes/, it seems that this // form of LEA (eg, "LEA AX,DX") simply returns the last calculated EA. Since we always reset regEA // at the start of a new instruction, we would need to preserve the previous EA if we want to mimic // that (undocumented) behavior. // // And for completeness, we would have to extend EA tracking beyond the usual ModRM instructions // (eg, XLAT, instructions that modify the stack pointer, and string instructions). Anything else? // X86.opUndefined.call(this); return dst; } */ this.nStepCycles -= this.cycleCounts.nOpCyclesLEA; return this.regEA; }; /** * fnLES(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnLES = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { X86.opUndefined.call(this); return dst; } this.setES(this.getShort(this.regEA + this.sizeData)); this.nStepCycles -= this.cycleCounts.nOpCyclesLS; return src; }; /** * fnLFS(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnLFS = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { X86.opUndefined.call(this); return dst; } this.setFS(this.getShort(this.regEA + this.sizeData)); this.nStepCycles -= this.cycleCounts.nOpCyclesLS; return src; }; /** * fnLGDT(dst, src) * * op=0x0F,0x01,reg=0x2 (GRP7:LGDT) * * The 80286 LGDT instruction assumes a 40-bit operand: a 16-bit limit followed by a 24-bit base address; * the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to * the limit, so we must fetch the remaining bits ourselves. * * The 80386 LGDT instruction assumes a 48-bit operand: a 16-bit limit followed by a 32-bit base address, * but it ignores the last 8 bits of the base address if the OPERAND size is 16 bits; we interpret that to * mean that the 24-bit base address should be zero-extended to 32 bits. * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnLGDT = function(dst, src) { /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ if (this.regEA === X86.ADDR_INVALID || I386 && (this.regPS & X86.PS.VM)) { X86.opInvalid.call(this); } else { /* * Hopefully it won't hurt to always fetch a 32-bit base address (even on an 80286), which we then * mask appropriately. */ this.addrGDT = this.getLong(this.regEA + 2) & (this.maskData | (this.maskData << 8)); /* * An idiosyncrasy of our ModRM decoders is that, if the OPERAND size is 32 bits, then it will have * fetched a 32-bit dst operand; we mask off those extra bits now. */ dst &= 0xffff; this.addrGDTLimit = this.addrGDT + dst; this.opFlags |= X86.OPFLAG.NOWRITE; this.nStepCycles -= 11; } return dst; }; /** * fnLGS(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnLGS = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { X86.opUndefined.call(this); return dst; } this.setGS(this.getShort(this.regEA + this.sizeData)); this.nStepCycles -= this.cycleCounts.nOpCyclesLS; return src; }; /** * fnLIDT(dst, src) * * op=0x0F,0x01,reg=0x3 (GRP7:LIDT) * * The 80286 LIDT instruction assumes a 40-bit operand: a 16-bit limit followed by a 24-bit base address; * the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to * the limit, so we must fetch the remaining bits ourselves. * * The 80386 LIDT instruction assumes a 48-bit operand: a 16-bit limit followed by a 32-bit base address, * but it ignores the last 8 bits of the base address if the OPERAND size is 16 bits; we interpret that to * mean that the 24-bit base address should be zero-extended to 32 bits. * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnLIDT = function(dst, src) { /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ if (this.regEA === X86.ADDR_INVALID || I386 && (this.regPS & X86.PS.VM)) { X86.opInvalid.call(this); } else { /* * Hopefully it won't hurt to always fetch a 32-bit base address (even on an 80286), which we then * mask appropriately. */ this.addrIDT = this.getLong(this.regEA + 2) & (this.maskData | (this.maskData << 8)); /* * An idiosyncrasy of our ModRM decoders is that, if the OPERAND size is 32 bits, then it will have * fetched a 32-bit dst operand; we mask off those extra bits now. */ dst &= 0xffff; this.addrIDTLimit = this.addrIDT + dst; this.opFlags |= X86.OPFLAG.NOWRITE; this.nStepCycles -= 12; } return dst; }; /** * fnLLDT(dst, src) * * op=0x0F,0x00,reg=0x2 (GRP6:LLDT) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnLLDT = function(dst, src) { this.opFlags |= X86.OPFLAG.NOWRITE; this.segLDT.load(dst); this.nStepCycles -= (17 + (this.regEA === X86.ADDR_INVALID? 0 : 2)); return dst; }; /** * fnLMSW(dst, src) * * op=0x0F,0x01,reg=0x6 (GRP7:LMSW) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnLMSW = function(dst, src) { /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ if (I386 && (this.regPS & X86.PS.VM)) { X86.opInvalid.call(this); } else { this.setMSW(dst); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 3 : 6); this.opFlags |= X86.OPFLAG.NOWRITE; } return dst; }; /** * fnLSL(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (the selector) * @return {number} */ X86.fnLSL = function(dst, src) { /* * TODO: Is this an invalid operation if regEAWrite is set? dst is required to be a register. */ this.nStepCycles -= (14 + (this.regEA === X86.ADDR_INVALID? 0 : 2)); /* * Currently, segVER.load() will return an error only if the selector is beyond the bounds of the * descriptor table or the descriptor is not for a segment. * * TODO: LSL is explicitly documented as ALSO requiring a non-null selector, so we check X86.SEL.MASK; * are there any other instructions that were, um, less explicit but also require a non-null selector? */ if ((src & X86.SEL.MASK) && this.segVER.load(src) !== X86.ADDR_INVALID) { var fConforming = ((this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) == X86.DESC.ACC.TYPE.CODE_CONFORMING); if ((fConforming || this.segVER.dpl >= this.nCPL) && this.segVER.dpl >= (src & X86.SEL.RPL)) { this.setZF(); return this.segVER.limit; } } this.clearZF(); return dst; }; /** * fnLSS(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnLSS = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { X86.opUndefined.call(this); return dst; } this.setSS(this.getShort(this.regEA + this.sizeData)); this.nStepCycles -= this.cycleCounts.nOpCyclesLS; return src; }; /** * fnLTR(dst, src) * * op=0x0F,0x00,reg=0x3 (GRP6:LTR) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnLTR = function(dst, src) { this.opFlags |= X86.OPFLAG.NOWRITE; if (this.segTSS.load(dst) !== X86.ADDR_INVALID) { this.setShort(this.segTSS.addrDesc + X86.DESC.ACC.OFFSET, this.segTSS.acc |= X86.DESC.ACC.TYPE.TSS_BUSY); this.segTSS.type |= X86.DESC.ACC.TYPE.TSS_BUSY; } this.nStepCycles -= (17 + (this.regEA === X86.ADDR_INVALID? 0 : 2)); return dst; }; /** * fnMOV(dst, src) * * @this {X86CPU} * @param {number} dst (current value, ignored) * @param {number} src (new value) * @return {number} dst (updated value, from src) */ X86.fnMOV = function(dst, src) { this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesMovRR : this.cycleCounts.nOpCyclesMovRM) : this.cycleCounts.nOpCyclesMovMR); return src; }; /** * fnMOVXb(dst, src) * * Helper for opMOVSXb() and opMOVZXb() (which also take care of updating nStepCycles, so we don't have to) * * @this {X86CPU} * @param {number} dst (current value, ignored) * @param {number} src (new value) * @return {number} dst (updated value, from src) */ X86.fnMOVXb = function(dst, src) { /* * The ModRegByte handlers update the registers in the 1st column, but we need to update those in the 2nd column. * * 000: AL -> 000: AX * 001: CL -> 001: CX * 010: DL -> 010: DX * 011: BL -> 011: BX * 100: AH -> 100: SP * 101: CH -> 101: BP * 110: DH -> 110: SI * 111: BH -> 111: DI */ var reg = (this.bModRM >> 3) & 0x7; switch(reg) { case 0x4: this.regXX = this.regEAX; break; case 0x5: this.regXX = this.regECX; break; case 0x6: this.regXX = this.regEDX; break; case 0x7: this.regXX = this.regEBX; break; } return src; }; /** * fnMOVXw(dst, src) * * Helper for opMOVSXw() and opMOVZXw() (which also take care of updating nStepCycles, so we don't have to) * * @this {X86CPU} * @param {number} dst (current value, ignored) * @param {number} src (new value) * @return {number} dst (updated value, from src) */ X86.fnMOVXw = function(dst, src) { return src; }; /** * fnMOVn(dst, src) * * @this {X86CPU} * @param {number} dst (current value, ignored) * @param {number} src (new value) * @return {number} dst (updated value, from src) */ X86.fnMOVn = function(dst, src) { this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesMovRI : this.cycleCounts.nOpCyclesMovMI); return src; }; /** * fnMOVsrw(dst, src) * * This helper saves the contents of the general-purpose register that will be overwritten, so that the caller * can restore it after moving the updated value to the correct segment register. * * @this {X86CPU} * @param {number} dst (current value, ignored) * @param {number} src (new value) * @return {number} dst (updated value, from src) */ X86.fnMOVsrw = function(dst, src) { var reg = (this.bModRM >> 3) & 0x7; switch(reg) { case 0x0: this.regXX = this.regEAX; break; case 0x2: this.regXX = this.regEDX; break; case 0x3: this.regXX = this.regEBX; break; default: if (this.model == X86.MODEL_80286 || this.model == X86.MODEL_80386 && reg != 0x4 && reg != 0x5) { X86.opInvalid.call(this); break; } switch(reg) { case 0x1: // MOV to CS is undocumented on 8086/8088/80186/80188, and invalid on 80286 and up this.regXX = this.regECX; break; case 0x4: // this form of MOV to ES is undocumented on 8086/8088/80186/80188, invalid on 80286, and uses FS starting with 80386 this.regXX = this.getSP(); break; case 0x5: // this form of MOV to CS is undocumented on 8086/8088/80186/80188, invalid on 80286, and uses GS starting with 80386 this.regXX = this.regEBP; break; case 0x6: // this form of MOV to SS is undocumented on 8086/8088/80186/80188, invalid on 80286 and up this.regXX = this.regESI; break; case 0x7: // this form of MOV to DS is undocumented on 8086/8088/80186/80188, invalid on 80286 and up this.regXX = this.regEDI; break; default: break; } break; } /* * We could just return src, but nStepCycles needs to be updated, too. */ return X86.fnMOV.call(this, dst, src); }; /** * fnMOVwsr(dst, src) * * @this {X86CPU} * @param {number} dst (current value, ignored) * @param {number} src (new value) * @return {number} dst */ X86.fnMOVwsr = function(dst, src) { var reg = (this.bModRM >> 3) & 0x7; switch (reg) { case 0x0: src = this.segES.sel; break; case 0x1: src = this.segCS.sel; break; case 0x2: src = this.segSS.sel; break; case 0x3: src = this.segDS.sel; break; case 0x4: if (I386 && this.model >= X86.MODEL_80386) { src = this.segFS.sel; break; } X86.opInvalid.call(this); src = dst; break; case 0x5: if (I386 && this.model >= X86.MODEL_80386) { src = this.segGS.sel; break; } /* falls through */ default: X86.opInvalid.call(this); src = dst; break; } /* * When a 32-bit OPERAND size is in effect, segment register writes via opMOVwsr() must write 32 bits * (zero-extended) if the destination is a register, but only 16 bits if the destination is memory, * hence the setDataSize(2) below. * * The only other caller, opMOVrc(), is not affected, because it writes only to register destinations. */ if (this.regEAWrite !== X86.ADDR_INVALID) { this.setDataSize(2); } /* * We could just return src, but nStepCycles needs to be updated, too. */ return X86.fnMOV.call(this, dst, src); }; /** * fnMULb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} (we return dst unchanged, since it's actually AX that's modified) */ X86.fnMULb = function(dst, src) { this.regMDLo = ((this.regEAX & 0xff) * dst) & 0xffff; if (this.regMDLo & 0xff00) { this.setCF(); this.setOF(); } else { this.clearCF(); this.clearOF(); } this.fMDSet = true; 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. * * The algorithm is based on the traditional "by hand" multiplication method, by treating the two inputs * (dst and src) as two 2-digit numbers, where each digit is a base-65536 digit. * * @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(dst, src) { if (!(dst & ~0xffff) && !(src & ~0xffff)) { this.regMDLo = (dst * src)|0; this.regMDHi = 0; } else { 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.regMDLo = (mul16 << 16) | (mul00 & 0xffff); this.regMDHi = mul32|0; } }; /** * fnMULw(dst, src) * * regMDHi:regMDLo = dst * regEAX * * @this {X86CPU} * @param {number} dst * @param {number} src (null; AX or EAX is the implied src) * @return {number} (we return dst unchanged, since it's actually DX:AX that's modified) */ X86.fnMULw = function(dst, src) { if (this.sizeData == 2) { src = this.regEAX & 0xffff; var result = (src * dst)|0; this.regMDLo = result & 0xffff; this.regMDHi = (result >> 16) & 0xffff; } else { X86.fnMUL32.call(this, dst, this.regEAX); if (this.stepping == X86.STEPPING_80386_B1) { if (this.regEAX == 0x0417A000 && dst == 0x00000081) { /* * Normally, the result should be 0x20FE7A000 (ie, regMDHi should be 0x2). * I'm not sure what a typical B1 stepping failure looked like, so I'll set regMDHi to 0. * * If you want a B1 stepping without this 32-bit multiplication flaw, select the B2 stepping. */ this.assert(this.regMDLo == 0x0FE7A000 && this.regMDHi == 0x00000002); this.regMDHi = 0; } } } if (this.regMDHi) { this.setCF(); this.setOF(); } else { this.clearCF(); this.clearOF(); } this.fMDSet = true; this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesMulWR : this.cycleCounts.nOpCyclesMulWM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnNEGb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnNEGb = function(dst, src) { var b = (-dst)|0; this.setArithResult(0, dst, b, X86.RESULT.BYTE | X86.RESULT.ALL, true); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesNegR : this.cycleCounts.nOpCyclesNegM); return b & 0xff; }; /** * fnNEGw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnNEGw = function(dst, src) { var w = (-dst)|0; this.setArithResult(0, dst, w, this.typeData | X86.RESULT.ALL, true); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesNegR : this.cycleCounts.nOpCyclesNegM); return w & this.maskData; }; /** * fnNOTb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnNOTb = function(dst, src) { this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesNegR : this.cycleCounts.nOpCyclesNegM); return dst ^ 0xff; }; /** * fnNOTw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnNOTw = function(dst, src) { this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesNegR : this.cycleCounts.nOpCyclesNegM); return dst ^ this.maskData; }; /** * fnORb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnORb = function(dst, src) { this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return this.setLogicResult(dst | src, X86.RESULT.BYTE); }; /** * fnORw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnORw = function(dst, src) { this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return this.setLogicResult(dst | src, this.typeData) & this.maskData; }; /** * fnPOPw(dst, src) * * @this {X86CPU} * @param {number} dst (current value, ignored) * @param {number} src (new value) * @return {number} dst (updated value, from src) */ X86.fnPOPw = function(dst, src) { this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesPopReg : this.cycleCounts.nOpCyclesPopMem); return src; }; /** * fnPUSHw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnPUSHw = function(dst, src) { var w = dst; if (this.opFlags & X86.OPFLAG.PUSHSP) { /* * This is the one case where must actually modify dst, so that the ModRM function will * not put a stale value back into the SP register. */ dst = (dst - 2) & 0xffff; /* * And on the 8086/8088, the value we just calculated also happens to be the value that must * be pushed. */ if (this.model < X86.MODEL_80286) w = dst; } this.pushWord(w); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesPushReg : this.cycleCounts.nOpCyclesPushMem); /* * The PUSH is the only write that needs to occur; dst was the source operand and does not need to be rewritten. */ this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnRCLb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnRCLb = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry = this.getCarry(); count %= 9; if (!count) { carry <<= 7; } else { result = ((dst << count) | (carry << (count - 1)) | (dst >> (9 - count))) & 0xff; carry = dst << (count - 1); } this.setRotateResult(result, carry, X86.RESULT.BYTE); } return result; }; /** * fnRCLw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnRCLw = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry = this.getCarry(); count %= 17; if (!count) { carry <<= 15; } else { result = ((dst << count) | (carry << (count - 1)) | (dst >> (17 - count))) & 0xffff; carry = dst << (count - 1); } this.setRotateResult(result, carry, X86.RESULT.WORD); } return result; }; /** * fnRCLd(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnRCLd = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; // this 32-bit-only function could mask with 0x1f directly if (count) { var carry = this.getCarry(); /* * JavaScript Alert: much like a post-8086 Intel CPU, JavaScript shift counts are mod 32, * so "dst >>> 32" is equivalent to "dst >>> 0", which doesn't shift any bits at all. To * compensate, we shift one bit less than the maximum, and then shift one bit farther. */ result = (dst << count) | (carry << (count - 1)) | ((dst >>> (32 - count)) >>> 1); carry = dst << (count - 1); this.setRotateResult(result, carry, X86.RESULT.DWORD); } return result; }; /** * fnRCRb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnRCRb = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry = this.getCarry(); count %= 9; if (!count) { carry <<= 7; } else { result = ((dst >> count) | (carry << (8 - count)) | (dst << (9 - count))) & 0xff; carry = dst << (8 - count); } this.setRotateResult(result, carry, X86.RESULT.BYTE); } return result; }; /** * fnRCRw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnRCRw = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry = this.getCarry(); count %= 17; if (!count) { carry <<= 15; } else { result = ((dst >> count) | (carry << (16 - count)) | (dst << (17 - count))) & 0xffff; carry = dst << (16 - count); } this.setRotateResult(result, carry, X86.RESULT.WORD); } return result; }; /** * fnRCRd(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnRCRd = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; // this 32-bit-only function could mask with 0x1f directly if (count) { var carry = this.getCarry(); /* * JavaScript Alert: much like a post-8086 Intel CPU, JavaScript shift counts are mod 32, * so "dst << 32" is equivalent to "dst << 0", which doesn't shift any bits at all. To * compensate, we shift one bit less than the maximum, and then shift one bit farther. */ result = (dst >>> count) | (carry << (32 - count)) | ((dst << (32 - count)) << 1); carry = dst << (32 - count); this.setRotateResult(result, carry, X86.RESULT.DWORD); } return result; }; /** * fnROLb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnROLb = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry; count &= 0x7; if (!count) { carry = dst << 7; } else { carry = dst << (count - 1); result = ((dst << count) | (dst >> (8 - count))) & 0xff; } this.setRotateResult(result, carry, X86.RESULT.BYTE); } return result; }; /** * fnROLw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnROLw = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry; count &= 0xf; if (!count) { carry = dst << 15; } else { carry = dst << (count - 1); result = ((dst << count) | (dst >> (16 - count))) & 0xffff; } this.setRotateResult(result, carry, X86.RESULT.WORD); } return result; }; /** * fnROLd(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnROLd = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry = dst << (count - 1); result = (dst << count) | (dst >>> (32 - count)); this.setRotateResult(result, carry, X86.RESULT.DWORD); } return result; }; /** * fnRORb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnRORb = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry; count &= 0x7; if (!count) { carry = dst; } else { carry = dst << (8 - count); result = ((dst >>> count) | carry) & 0xff; } this.setRotateResult(result, carry, X86.RESULT.BYTE); } return result; }; /** * fnRORw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnRORw = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry; count &= 0xf; if (!count) { carry = dst; } else { carry = dst << (16 - count); result = ((dst >>> count) | carry) & 0xffff; } this.setRotateResult(result, carry, X86.RESULT.WORD); } return result; }; /** * fnRORd(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL) * @return {number} */ X86.fnRORd = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry = dst << (32 - count); result = (dst >>> count) | carry; this.setRotateResult(result, carry, X86.RESULT.DWORD); } return result; }; /** * fnSARb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL, or an immediate byte for 80186/80188 and up) * @return {number} */ X86.fnSARb = function(dst, src) { var count = src & this.nShiftCountMask; if (count) { if (count > 9) count = 9; var carry = ((dst << 24) >> 24) >> (count - 1); dst = (carry >> 1) & 0xff; this.setLogicResult(dst, X86.RESULT.BYTE, carry & 0x1); } return dst; }; /** * fnSARw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL, or an immediate byte for 80186/80188 and up) * @return {number} */ X86.fnSARw = function(dst, src) { var count = src & this.nShiftCountMask; if (count) { if (count > 17) count = 17; var carry = ((dst << 16) >> 16) >> (count - 1); dst = (carry >> 1) & 0xffff; this.setLogicResult(dst, X86.RESULT.WORD, carry & 0x1); } return dst; }; /** * fnSARd(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL, or an immediate byte for 80186/80188 and up) * @return {number} */ X86.fnSARd = function(dst, src) { var count = src & this.nShiftCountMask; if (count) { var carry = dst >> (count - 1); dst = (carry >> 1); this.setLogicResult(dst, X86.RESULT.DWORD, carry & 0x1); } return dst; }; /** * fnSBBb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSBBb = function(dst, src) { var b = (dst - src - this.getCarry())|0; this.setArithResult(dst, src, b, X86.RESULT.BYTE | X86.RESULT.ALL, true); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return b & 0xff; }; /** * fnSBBw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSBBw = function(dst, src) { var w = (dst - src - this.getCarry())|0; this.setArithResult(dst, src, w, this.typeData | X86.RESULT.ALL, true); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return w & this.maskData; }; /** * fnSETO(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETO = function(dst, src) { return (this.getOF()? 1 : 0); }; /** * fnSETNO(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETNO = function(dst, src) { return (this.getOF()? 0 : 1); }; /** * fnSETC(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETC = function(dst, src) { return (this.getCF()? 1 : 0); }; /** * fnSETNC(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETNC = function(dst, src) { return (this.getCF()? 0 : 1); }; /** * fnSETZ(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETZ = function(dst, src) { return (this.getZF()? 1 : 0); }; /** * fnSETNZ(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETNZ = function(dst, src) { return (this.getZF()? 0 : 1); }; /** * fnSETBE(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETBE = function(dst, src) { return (this.getCF() || this.getZF()? 1 : 0); }; /** * fnSETNBE(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETNBE = function(dst, src) { return (this.getCF() || this.getZF()? 0 : 1); }; /** * fnSETS(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETS = function(dst, src) { return (this.getSF()? 1 : 0); }; /** * fnSETNS(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETNS = function(dst, src) { return (this.getSF()? 0 : 1); }; /** * fnSETP(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETP = function(dst, src) { return (this.getPF()? 1 : 0); }; /** * fnSETNP(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETNP = function(dst, src) { return (this.getPF()? 0 : 1); }; /** * fnSETL(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETL = function(dst, src) { return (!this.getSF() != !this.getOF()? 1 : 0); }; /** * fnSETNL(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETNL = function(dst, src) { return (!this.getSF() != !this.getOF()? 0 : 1); }; /** * fnSETLE(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETLE = function(dst, src) { return (this.getZF() || !this.getSF() != !this.getOF()? 1 : 0); }; /** * fnSETNLE(dst, src) * * @this {X86CPU} * @param {number} dst (ignored) * @param {number} src (ignored) * @return {number} */ X86.fnSETNLE = function(dst, src) { return (this.getZF() || !this.getSF() != !this.getOF()? 0 : 1); }; /** * fnSGDT(dst, src) * * op=0x0F,0x01,reg=0x0 (GRP7:SGDT) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnSGDT = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { X86.opInvalid.call(this); } else { /* * We don't need to set the first word of the operand, because the ModRM group decoder that calls us * does that automatically with the value we return (dst). */ dst = this.addrGDTLimit - this.addrGDT; this.assert(!(dst & ~0xffff)); var addr = this.addrGDT; if (this.model == X86.MODEL_80286) { /* * We previously left the 6th byte of the target operand "undefined". But it turns out we have to set * it to *something*, because there's processor detection in PC-DOS 7.0 (at least in the SETUP portion) * that looks like this: * * 145E:4B84 9C PUSHF * 145E:4B85 55 PUSH BP * 145E:4B86 8BEC MOV BP,SP * 145E:4B88 B80000 MOV AX,0000 * 145E:4B8B 50 PUSH AX * 145E:4B8C 9D POPF * 145E:4B8D 9C PUSHF * 145E:4B8E 58 POP AX * 145E:4B8F 2500F0 AND AX,F000 * 145E:4B92 3D00F0 CMP AX,F000 * 145E:4B95 7511 JNZ 4BA8 * 145E:4BA8 C8060000 ENTER 0006,00 * 145E:4BAC 0F0146FA SGDT [BP-06] * 145E:4BB0 807EFFFF CMP [BP-01],FF * 145E:4BB4 C9 LEAVE * 145E:4BB5 BA8603 MOV DX,0386 * 145E:4BB8 7503 JNZ 4BBD * 145E:4BBA BA8602 MOV DX,0286 * 145E:4BBD 89163004 MOV [0430],DX * 145E:4BC1 5D POP BP * 145E:4BC2 9D POPF * 145E:4BC3 CB RETF * * This code is expecting SGDT on an 80286 to set the 6th "undefined" byte to 0xFF, so that's what we do. */ addr |= (0xff000000|0); } else if (this.model >= X86.MODEL_80386) { /* * The 80386 added another wrinkle: Intel's documentation claimed that the 6th byte is either set to zero * or the high byte of the BASE field, depending on the OPERAND size; from the "INTEL 80386 PROGRAMMER'S * REFERENCE MANUAL 1986": * * The LIMIT field of the [GDTR or IDTR] register is assigned to the first word at the effective address. * If the operand-size attribute is 32 bits, the next three bytes are assigned the BASE field of the * register, and the fourth byte is written with zero. The last byte is undefined. Otherwise, if the * operand-size attribute is 16 bits, the next 4 bytes are assigned the 32-bit BASE field of the register. * * However, Intel obviously meant the reverse (ie, that the BASE field is truncated when using a 16-bit * OPERAND size, not when using a 32-bit OPERAND size). */ if (this.sizeData == 2) { /* * Thanks to Michal Necasek, we now know that the: "386 in reality does not pay attention to the operand * size (despite Intel's claims to the contrary). In fact Windows 3.11/Win32s relies on it -- at least in * some configurations, it will execute SGDT in 16-bit code and will crash if all 6 bytes aren't stored." * * Based on the above information, we no longer mask the 6th byte on the 80386 when the OPERAND size is 2. * * addr &= 0x00ffffff; */ } else { /* * When the OPERAND size is 4, our ModRM group decoder will call setLong(dst) rather than setShort(dst); * we could fix that by calling setDataSize(2), but it seems safer/simpler to set the high bits (16-31) * of dst to match the low bits (0-15) of addr, so that the caller will harmlessly rewrite what we are * already writing with the setLong() below. */ dst |= (addr << 16); } } this.setLong(this.regEA + 2, addr); this.nStepCycles -= 11; } return dst; }; /** * fnSHLb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL, or an immediate byte for 80186/80188 and up) * @return {number} */ X86.fnSHLb = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry = 0; if (count > 8) { result = 0; } else { carry = dst << (count - 1); result = (carry << 1) & 0xff; } this.setLogicResult(result, X86.RESULT.BYTE, carry & X86.RESULT.BYTE, (result ^ carry) & X86.RESULT.BYTE); } return result; }; /** * fnSHLw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL, or an immediate byte for 80186/80188 and up) * @return {number} */ X86.fnSHLw = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; if (count) { var carry = 0; if (count > 16) { result = 0; } else { carry = dst << (count - 1); result = (carry << 1) & 0xffff; } this.setLogicResult(result, X86.RESULT.WORD, carry & X86.RESULT.WORD, (result ^ carry) & X86.RESULT.WORD); } return result; }; /** * fnSHLd(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL, or an immediate byte for 80186/80188 and up) * @return {number} */ X86.fnSHLd = function(dst, src) { var result = dst; var count = src & this.nShiftCountMask; // this 32-bit-only function could mask with 0x1f directly if (count) { var carry = dst << (count - 1); result = (carry << 1); this.setLogicResult(result, X86.RESULT.DWORD, carry & X86.RESULT.DWORD, (result ^ carry) & X86.RESULT.DWORD); } return result; }; /** * fnSHLDwi(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSHLDwi = function(dst, src) { return X86.helpSHLDw.call(this, dst, src, this.getIPByte()); }; /** * fnSHLDdi(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSHLDdi = function(dst, src) { return X86.helpSHLDd.call(this, dst, src, this.getIPByte()); }; /** * fnSHLDwCL(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSHLDwCL = function(dst, src) { return X86.helpSHLDw.call(this, dst, src, this.regECX & 0x1f); }; /** * fnSHLDdCL(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSHLDdCL = function(dst, src) { return X86.helpSHLDd.call(this, dst, src, this.regECX & 0x1f); }; /** * fnSHRb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL, or an immediate byte for 80186/80188 and up) * @return {number} */ X86.fnSHRb = function(dst, src) { var count = src & this.nShiftCountMask; if (count) { var carry = (count > 8? 0 : (dst >>> (count - 1))); dst = (carry >>> 1) & 0xff; this.setLogicResult(dst, X86.RESULT.BYTE, carry & 0x1, dst & X86.RESULT.BYTE); } return dst; }; /** * fnSHRw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL, or an immediate byte for 80186/80188 and up) * @return {number} */ X86.fnSHRw = function(dst, src) { var count = src & this.nShiftCountMask; if (count) { var carry = (count > 16? 0 : (dst >>> (count - 1))); dst = (carry >>> 1) & 0xffff; this.setLogicResult(dst, X86.RESULT.WORD, carry & 0x1, dst & X86.RESULT.WORD); } return dst; }; /** * fnSHRd(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (1 or CL, or an immediate byte for 80186/80188 and up) * @return {number} */ X86.fnSHRd = function(dst, src) { var count = src & this.nShiftCountMask; if (count) { var carry = (dst >>> (count - 1)); dst = (carry >>> 1); this.setLogicResult(dst, X86.RESULT.DWORD, carry & 0x1, dst & X86.RESULT.DWORD); } return dst; }; /** * fnSHRDwi(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSHRDwi = function(dst, src) { return X86.helpSHRDw.call(this, dst, src, this.getIPByte()); }; /** * fnSHRDdi(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSHRDdi = function(dst, src) { return X86.helpSHRDd.call(this, dst, src, this.getIPByte()); }; /** * fnSHRDwCL(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSHRDwCL = function(dst, src) { return X86.helpSHRDw.call(this, dst, src, this.regECX & 0x1f); }; /** * fnSHRDdCL(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSHRDdCL = function(dst, src) { return X86.helpSHRDd.call(this, dst, src, this.regECX & 0x1f); }; /** * fnSIDT(dst, src) * * op=0x0F,0x01,reg=0x1 (GRP7:SIDT) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnSIDT = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { X86.opInvalid.call(this); } else { /* * We don't need to set the first word of the operand, because the ModRM group decoder that calls us * does that automatically with the value we return (dst). */ dst = this.addrIDTLimit - this.addrIDT; this.assert(!(dst & ~0xffff)); /* * As with SGDT, the 6th byte is technically "undefined" on an 80286, but we now set it to 0xFF, for the * same reasons discussed in SGDT (above). */ var addr = this.addrIDT; if (this.model == X86.MODEL_80286) { addr |= (0xff000000|0); } else if (this.model >= X86.MODEL_80386) { if (this.sizeData == 2) { /* * Based on the SGDT information above, we no longer mask the 6th byte when the OPERAND size is 2. * * addr &= 0x00ffffff; */ } else { dst |= (addr << 16); } } this.setLong(this.regEA + 2, addr); this.nStepCycles -= 12; } return dst; }; /** * fnSLDT(dst, src) * * op=0x0F,0x00,reg=0x0 (GRP6:SLDT) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnSLDT = function(dst, src) { this.nStepCycles -= (2 + (this.regEA === X86.ADDR_INVALID? 0 : 1)); return this.segLDT.sel; }; /** * fnSMSW(dst, src) * * TODO: I've seen a claim that SMSW can be used with an operand size override to obtain the entire CR0. * I don't dispute that, and since I don't mask the return value, that should be possible here; however, it * should still be confirmed on real hardware at some point. Note that this differs from LMSW, which is * REQUIRED to mask the source operand. * * op=0x0F,0x01,reg=0x4 (GRP7:SMSW) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnSMSW = function(dst, src) { this.nStepCycles -= (2 + (this.regEA === X86.ADDR_INVALID? 0 : 1)); return this.regCR0; }; /** * fnSTR(dst, src) * * op=0x0F,0x00,reg=0x1 (GRP6:STR) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnSTR = function(dst, src) { this.nStepCycles -= (2 + (this.regEA === X86.ADDR_INVALID? 0 : 1)); return this.segTSS.sel; }; /** * fnSUBb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSUBb = function(dst, src) { var b = (dst - src)|0; this.setArithResult(dst, src, b, X86.RESULT.BYTE | X86.RESULT.ALL, true); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return b & 0xff; }; /** * fnSUBw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnSUBw = function(dst, src) { var w = (dst - src)|0; this.setArithResult(dst, src, w, this.typeData | X86.RESULT.ALL, true); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return w & this.maskData; }; /** * fnTESTib(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null; we have to supply the source ourselves) * @return {number} */ X86.fnTESTib = function(dst, src) { src = this.getIPByte(); this.setLogicResult(dst & src, X86.RESULT.BYTE); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesTestRI : this.cycleCounts.nOpCyclesTestMI); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnTESTiw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src (null; we have to supply the source ourselves) * @return {number} */ X86.fnTESTiw = function(dst, src) { src = this.getIPWord(); this.setLogicResult(dst & src, this.typeData); this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesTestRI : this.cycleCounts.nOpCyclesTestMI); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnTESTb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnTESTb = function(dst, src) { this.setLogicResult(dst & src, X86.RESULT.BYTE); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesTestRR : this.cycleCounts.nOpCyclesTestRM) : this.cycleCounts.nOpCyclesTestRM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnTESTw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnTESTw = function(dst, src) { this.setLogicResult(dst & src, this.typeData); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesTestRR : this.cycleCounts.nOpCyclesTestRM) : this.cycleCounts.nOpCyclesTestRM); this.opFlags |= X86.OPFLAG.NOWRITE; return dst; }; /** * fnVERR(dst, src) * * op=0x0F,0x00,reg=0x4 (GRP6:VERR) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnVERR = function(dst, src) { this.opFlags |= X86.OPFLAG.NOWRITE; /* * Currently, segVER.load() will return an error only if the selector is beyond the bounds of the * descriptor table or the descriptor is not for a segment. */ this.nStepCycles -= (14 + (this.regEA === X86.ADDR_INVALID? 0 : 2)); if (this.segVER.load(dst) !== X86.ADDR_INVALID) { /* * Verify that this is a readable segment; that is, of these four combinations (code+readable, * code+nonreadable, data+writable, date+nonwritable), make sure we're not the second combination. */ if ((this.segVER.acc & (X86.DESC.ACC.TYPE.READABLE | X86.DESC.ACC.TYPE.CODE)) != X86.DESC.ACC.TYPE.CODE) { /* * For VERR, if the code segment is readable and conforming, the descriptor privilege level * (DPL) can be any value. * * Otherwise, DPL must be greater than or equal to (have less or the same privilege as) both the * current privilege level and the selector's RPL. */ if (this.segVER.dpl >= this.nCPL && this.segVER.dpl >= (dst & X86.SEL.RPL) || (this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) == X86.DESC.ACC.TYPE.CODE_CONFORMING) { this.setZF(); return dst; } } } this.clearZF(); if (DEBUG && (this.sizeData > 2 || this.sizeAddr > 2)) this.stopCPU(); return dst; }; /** * fnVERW(dst, src) * * op=0x0F,0x00,reg=0x5 (GRP6:VERW) * * @this {X86CPU} * @param {number} dst * @param {number} src (null) * @return {number} */ X86.fnVERW = function(dst, src) { this.opFlags |= X86.OPFLAG.NOWRITE; /* * Currently, segVER.load() will return an error only if the selector is beyond the bounds of the * descriptor table or the descriptor is not for a segment. */ this.nStepCycles -= (14 + (this.regEA === X86.ADDR_INVALID? 0 : 2)); if (this.segVER.load(dst) !== X86.ADDR_INVALID) { /* * Verify that this is a writable data segment */ if ((this.segVER.acc & (X86.DESC.ACC.TYPE.WRITABLE | X86.DESC.ACC.TYPE.CODE)) == X86.DESC.ACC.TYPE.WRITABLE) { /* * DPL must be greater than or equal to (have less or the same privilege as) both the current * privilege level and the selector's RPL. */ if (this.segVER.dpl >= this.nCPL && this.segVER.dpl >= (dst & X86.SEL.RPL)) { this.setZF(); return dst; } } } this.clearZF(); if (DEBUG && (this.sizeData > 2 || this.sizeAddr > 2)) this.stopCPU(); return dst; }; /** * fnIBTS(dst, src) * * As best I can determine, this function copies the specified bits from src (starting at bit 0 for CL * bits) to dst (starting at bit offset in AX). For register operands, that's simple enough. * * TODO: If dst refers to a memory location, then the bit index may refer to higher memory locations, just * like the BT/BTC/BTR/BTS instructions. For an instruction that no one was really able to use, except * as a CPU stepping discriminator, that doesn't seem worth the effort. * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnIBTS = function(dst, src) { var shift = (this.regEAX & this.maskData); var mask = ((1 << (this.regECX & 0x1f)) - 1); return (dst & ~(mask << shift)) | ((src & mask) << shift); }; /** * fnXBTS(dst, src) * * As best I can determine, this function copies the specified bits from src (starting at the bit offset * in AX, for the bit length in CL) to dst (starting at bit 0). For register operands, that's simple enough. * * TODO: If src refers to a memory location, then the bit index may refer to higher memory locations, just * like the BT/BTC/BTR/BTS instructions. For an instruction that no one was really able to use, except * as a CPU stepping discriminator, that doesn't seem worth the effort. * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnXBTS = function(dst, src) { /* * Shift src right by the bit offset in [E]AX, then apply a mask equal to the number of bits in CL, * then mask the resulting bit string with the current OPERAND size. */ return ((src >> (this.regEAX & this.maskData)) & ((1 << (this.regECX & 0x1f)) - 1)) & this.maskData; }; /** * fnXCHGrb(dst, src) * * If an instruction like "XCHG AL,AH" was a traditional "op dst,src" instruction, dst would contain AL, * src would contain AH, and we would return src, which the caller would then store in AL, and we'd be done. * * However, that's only half of what XCHG does, so THIS function must perform the other half; in the previous * example, that means storing the original AL (dst) into AH (src). * * BACKTRACK support is incomplete without also passing bti values as parameters, because the caller will * store btiAH in btiAL, but the original btiAL will be lost. Similarly, if src is a memory operand, the * caller will store btiEALo in btiAL, but again, the original btiAL will be lost. * * BACKTRACK support for memory operands could be fixed by decoding the dst register in order to determine the * corresponding bti and then temporarily storing it in btiEALo around the setEAByte() call below. Register-only * XCHGs would require a more extensive hack. For now, I'm going to live with one-way BACKTRACK support here. * * TODO: Implement full BACKTRACK support for XCHG instructions. * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnXCHGrb = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { /* * Decode which register was src */ this.assert(!(dst & ~0xff)); // confirm that dst contains only 8 bits switch (this.bModRM & 0x7) { case 0x0: // AL this.regEAX = (this.regEAX & ~0xff) | dst; break; case 0x1: // CL this.regECX = (this.regECX & ~0xff) | dst; break; case 0x2: // DL this.regEDX = (this.regEDX & ~0xff) | dst; break; case 0x3: // BL this.regEBX = (this.regEBX & ~0xff) | dst; break; case 0x4: // AH this.regEAX = (this.regEAX & ~0xff00) | (dst << 8); break; case 0x5: // CH this.regECX = (this.regECX & ~0xff00) | (dst << 8); break; case 0x6: // DH this.regEDX = (this.regEDX & ~0xff00) | (dst << 8); break; case 0x7: // BH this.regEBX = (this.regEBX & ~0xff00) | (dst << 8); break; default: break; // there IS no other case, but JavaScript inspections don't know that } this.nStepCycles -= this.cycleCounts.nOpCyclesXchgRR; } else { /* * This is a case where the ModRM decoder that's calling us didn't know it should have set regEAWrite, * so we compensate by updating regEAWrite. However, setEAWord() has since been changed to revalidate * the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary. */ this.regEAWrite = this.regEA; this.setEAByte(dst); this.nStepCycles -= this.cycleCounts.nOpCyclesXchgRM; } return src; }; /** * fnXCHGrw(dst, src) * * If an instruction like "XCHG AX,DX" was a traditional "op dst,src" instruction, dst would contain AX, * src would contain DX, and we would return src, which the caller would then store in AX, and we'd be done. * * However, that's only half of what XCHG does, so THIS function must perform the other half; in the previous * example, that means storing the original AX (dst) into DX (src). * * TODO: Implement full BACKTRACK support for XCHG instructions (see fnXCHGrb comments). * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnXCHGrw = function(dst, src) { if (this.regEA === X86.ADDR_INVALID) { /* * Decode which register was src */ this.assert(!(dst & ~this.maskData)); // confirm that dst contains only 16 or 32 bits switch (this.bModRM & 0x7) { case 0x0: // [E]AX this.regEAX = (this.regEAX & ~this.maskData) | dst; break; case 0x1: // [E]CX this.regECX = (this.regECX & ~this.maskData) | dst; break; case 0x2: // [E]DX this.regEDX = (this.regEDX & ~this.maskData) | dst; break; case 0x3: // [E]BX this.regEBX = (this.regEBX & ~this.maskData) | dst; break; case 0x4: // [E]SP this.setSP((this.getSP() & ~this.maskData) | dst); break; case 0x5: // [E]BP this.regEBP = (this.regEBX & ~this.maskData) | dst; break; case 0x6: // [E]SI this.regESI = (this.regESI & ~this.maskData) | dst; break; case 0x7: // [E]DI this.regEDI = (this.regEDI & ~this.maskData) | dst; break; default: break; // there IS no other case, but JavaScript inspections don't know that } this.nStepCycles -= this.cycleCounts.nOpCyclesXchgRR; } else { /* * This is a case where the ModRM decoder that's calling us didn't know it should have set regEAWrite, * so we compensate by updating regEAWrite. However, setEAWord() has since been changed to revalidate * the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary. */ this.regEAWrite = this.regEA; this.setEAWord(dst); this.nStepCycles -= this.cycleCounts.nOpCyclesXchgRM; } return src; }; /** * fnXORb(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnXORb = function(dst, src) { var b = dst ^ src; this.setLogicResult(b, X86.RESULT.BYTE); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return b; }; /** * fnXORw(dst, src) * * @this {X86CPU} * @param {number} dst * @param {number} src * @return {number} */ X86.fnXORw = function(dst, src) { this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesArithRR : this.cycleCounts.nOpCyclesArithRM) : this.cycleCounts.nOpCyclesArithMR); return this.setLogicResult(dst ^ src, this.typeData) & this.maskData; };