pcjs/modules/pcx86/lib/x86func.js

3400 lines
98 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @fileoverview Implements PCx86 8086 opcode helpers.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright Jeff Parsons 2012-2016
*
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
*
* 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 <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see COPYRIGHT in /modules/shared/lib/defines.js).
*
* 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 str = require("../../shared/lib/strlib");
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.
*
* @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;
};