3656 lines
105 KiB
JavaScript
3656 lines
105 KiB
JavaScript
/**
|
|
* @fileoverview Implements PCjs 8086 opcode helpers.
|
|
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
|
* @version 1.0
|
|
* Created 2012-Sep-05
|
|
*
|
|
* Copyright © 2012-2015 Jeff Parsons <Jeff@pcjs.org>
|
|
*
|
|
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
|
* at <http://jsmachines.net/> and <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 Computer.sCopyright).
|
|
*
|
|
* 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 the
|
|
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
|
* any copyright as to their contents.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
if (typeof module !== 'undefined') {
|
|
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 ADCb(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 ADCw(dst, src)
|
|
{
|
|
var w = (dst + src + this.getCarry())|0;
|
|
this.setArithResult(dst, src, w, this.dataType | 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.dataMask;
|
|
};
|
|
|
|
/**
|
|
* fnADDb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnADDb = function ADDb(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 ADDw(dst, src)
|
|
{
|
|
var w = (dst + src)|0;
|
|
this.setArithResult(dst, src, w, this.dataType | 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.dataMask;
|
|
};
|
|
|
|
/**
|
|
* fnANDb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnANDb = function ANDb(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 ANDw(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.dataType);
|
|
};
|
|
|
|
/**
|
|
* fnARPL(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnARPL = function ARPL(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 BOUND(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 << 16) >> 16;
|
|
var wLower = (this.getWord(this.regEA) << 16) >> 16;
|
|
var wUpper = (this.getWord(this.regEA + this.dataSize) << 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.
|
|
*/
|
|
this.setIP(this.opLIP - this.segCS.base);
|
|
X86.fnINT.call(this, X86.EXCEPTION.BOUND_ERR, null, 0);
|
|
}
|
|
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 BSF(dst, src)
|
|
{
|
|
var n = 0;
|
|
if (!src) {
|
|
this.setZF();
|
|
} else {
|
|
this.clearZF();
|
|
var bit = 0x1;
|
|
while (bit & this.dataMask) {
|
|
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 -= this.cycleCounts.nOpCyclesBitScan + 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 BSR(dst, src)
|
|
{
|
|
var n = 0;
|
|
if (!src) {
|
|
this.setZF();
|
|
} else {
|
|
this.clearZF();
|
|
var i = (this.dataSize == 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 -= this.cycleCounts.nOpCyclesBitScan + n * 3;
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnBT(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnBT = function BT(dst, src)
|
|
{
|
|
if (dst & (1 << (src & 0x1f))) this.setCF(); else this.clearCF();
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesBitTestR : this.cycleCounts.nOpCyclesBitTestM);
|
|
this.opFlags |= X86.OPFLAG.NOWRITE;
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnBTC(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnBTC = function BTC(dst, src)
|
|
{
|
|
var bit = 1 << (src & 0x1f);
|
|
if (dst & bit) this.setCF(); else this.clearCF();
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesBitSetR : this.cycleCounts.nOpCyclesBitSetM);
|
|
return dst ^ bit;
|
|
};
|
|
|
|
/**
|
|
* fnBTR(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnBTR = function BTR(dst, src)
|
|
{
|
|
var bit = 1 << (src & 0x1f);
|
|
if (dst & bit) this.setCF(); else this.clearCF();
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesBitSetR : this.cycleCounts.nOpCyclesBitSetM);
|
|
return dst & ~bit;
|
|
};
|
|
|
|
/**
|
|
* fnBTS(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnBTS = function BTS(dst, src)
|
|
{
|
|
var bit = 1 << (src & 0x1f);
|
|
if (dst & bit) this.setCF(); else this.clearCF();
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesBitSetR : this.cycleCounts.nOpCyclesBitSetM);
|
|
return dst | bit;
|
|
};
|
|
|
|
/**
|
|
* fnCALLw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnCALLw = function CALLw(dst, src)
|
|
{
|
|
if (DEBUG) this.printMessage("calling " + str.toHex(dst, this.dataSize << 1), this.bitsMessage, true);
|
|
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;
|
|
};
|
|
|
|
/**
|
|
* fnCALLF(off, sel)
|
|
*
|
|
* For protected-mode, this function must attempt to load the new code segment first, because if the new segment
|
|
* requires a change in privilege level, the return address must be pushed on the NEW stack, not the current stack.
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} off
|
|
* @param {number} sel
|
|
*/
|
|
X86.fnCALLF = function CALLF(off, sel)
|
|
{
|
|
if (DEBUG) this.printMessage("calling " + str.toHex(sel, 4) + ':' + str.toHex(off, this.dataSize << 1), this.bitsMessage, true);
|
|
var oldCS = this.getCS();
|
|
var oldIP = this.getIP();
|
|
if (this.setCSIP(off, sel, true) != null) {
|
|
this.pushWord(oldCS);
|
|
this.pushWord(oldIP);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* fnCALLFdw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnCALLFdw = function CALLFdw(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
return X86.fnGRPUndefined.call(this, dst, src);
|
|
}
|
|
X86.fnCALLF.call(this, dst, this.getShort(this.regEA + this.dataSize));
|
|
this.nStepCycles -= this.cycleCounts.nOpCyclesCallDM;
|
|
this.opFlags |= X86.OPFLAG.NOWRITE;
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnCMPb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number} dst unchanged
|
|
*/
|
|
X86.fnCMPb = function CMPb(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 CMPw(dst, src)
|
|
{
|
|
var w = (dst - src)|0;
|
|
this.setArithResult(dst, src, w, this.dataType | 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 DECb(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;
|
|
};
|
|
|
|
/**
|
|
* fnDECr(w)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} w
|
|
* @return {number}
|
|
*/
|
|
X86.fnDECr = function DECr(w)
|
|
{
|
|
var result = ((w & this.dataMask) - 1)|0;
|
|
this.setArithResult(w, 1, result, X86.RESULT.WORD | X86.RESULT.NOTCF, true);
|
|
this.nStepCycles -= 2; // the register form of INC takes 2 cycles on all CPUs
|
|
return (w & ~this.dataMask) | (result & this.dataMask);
|
|
};
|
|
|
|
/**
|
|
* fnDECw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnDECw = function DECw(dst, src)
|
|
{
|
|
var w = (dst - 1)|0;
|
|
this.setArithResult(dst, 1, w, X86.RESULT.WORD | X86.RESULT.NOTCF, true);
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIncR : this.cycleCounts.nOpCyclesIncM);
|
|
return w & 0xffff;
|
|
};
|
|
|
|
/**
|
|
* fnSet64(lo, hi)
|
|
*
|
|
* @param {number} lo
|
|
* @param {number} hi
|
|
*/
|
|
X86.fnSet64 = function Set64(lo, hi)
|
|
{
|
|
return [lo >>> 0, hi >>> 0];
|
|
};
|
|
|
|
/**
|
|
* fnAdd64(dst, src)
|
|
*
|
|
* Adds src to dst.
|
|
*
|
|
* @param {Array} dst is a 64-bit value
|
|
* @param {Array} src is a 64-bit value
|
|
*/
|
|
X86.fnAdd64 = function Add64(dst, src)
|
|
{
|
|
dst[0] += src[0];
|
|
dst[1] += src[1];
|
|
if (dst[0] > 0xffffffff) {
|
|
dst[0] >>>= 0; // truncate dst[0] to 32 bits AND keep it unsigned
|
|
dst[1]++;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* fnCmp64(dst, src)
|
|
*
|
|
* Compares dst to src, by computing dst - src.
|
|
*
|
|
* @param {Array} dst is a 64-bit value
|
|
* @param {Array} src is a 64-bit value
|
|
* @return {number} > 0 if dst > src, == 0 if dst == src, < 0 if dst < src
|
|
*/
|
|
X86.fnCmp64 = function Cmp64(dst, src)
|
|
{
|
|
var result = dst[1] - src[1];
|
|
if (!result) result = dst[0] - src[0];
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnSub64(dst, src)
|
|
*
|
|
* Subtracts src from dst.
|
|
*
|
|
* @param {Array} dst is a 64-bit value
|
|
* @param {Array} src is a 64-bit value
|
|
*/
|
|
X86.fnSub64 = function Sub64(dst, src)
|
|
{
|
|
dst[0] -= src[0];
|
|
dst[1] -= src[1];
|
|
if (dst[0] < 0) {
|
|
dst[0] >>>= 0; // truncate dst[0] to 32 bits AND keep it unsigned
|
|
dst[1]--;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* fnShr64(dst)
|
|
*
|
|
* Shifts dst right one bit.
|
|
*
|
|
* @param {Array} dst is a 64-bit value
|
|
*/
|
|
X86.fnShr64 = function Shr64(dst)
|
|
{
|
|
dst[0] >>>= 1;
|
|
if (dst[1] & 0x1) {
|
|
dst[0] = (dst[0] | 0x80000000) >>> 0;
|
|
}
|
|
dst[1] >>>= 1;
|
|
};
|
|
|
|
/**
|
|
* fnDIV32(dstLo, dstHi, src)
|
|
*
|
|
* This sets regMDLo to dstHi:dstLo / src, and regMDHi to dstHi:dstLo % src; all inputs are treated as unsigned.
|
|
*
|
|
* If fMDset is not set, however, then there was a divide exception (ie, the divisor was either zero or too small).
|
|
*
|
|
* Refer to: http://lxr.linux.no/linux+v2.6.22/lib/div64.c
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dstLo (low 32-bit portion of dividend)
|
|
* @param {number} dstHi (high 32-bit portion of dividend)
|
|
* @param {number} src (32-bit divisor)
|
|
*/
|
|
X86.fnDIV32 = function DIV32(dstLo, dstHi, src)
|
|
{
|
|
this.fMDSet = false;
|
|
|
|
src >>>= 0;
|
|
if (!src || src <= (dstHi >>> 0)) return;
|
|
|
|
var result = 0, bit = 1;
|
|
|
|
var div = X86.fnSet64(src, 0);
|
|
var rem = X86.fnSet64(dstLo, dstHi);
|
|
|
|
while (X86.fnCmp64(rem, div) > 0) {
|
|
X86.fnAdd64(div, div);
|
|
bit += bit;
|
|
}
|
|
do {
|
|
if (X86.fnCmp64(rem, div) >= 0) {
|
|
X86.fnSub64(rem, div);
|
|
result += bit;
|
|
}
|
|
X86.fnShr64(div);
|
|
bit >>>= 1;
|
|
} while (bit);
|
|
|
|
this.assert(result <= 0xffffffff && !rem[1]);
|
|
|
|
this.regMDLo = result; // result is the quotient, which callers expect in the low MD register
|
|
this.regMDHi = rem[0]; // rem[0] is the remainder, which callers expect in the high MD register
|
|
this.fMDSet = true;
|
|
};
|
|
|
|
/**
|
|
* fnIDIV32(dstLo, dstHi, src)
|
|
*
|
|
* This sets regMDLo to dstHi:dstLo / src, and regMDHi to dstHi:dstLo % src; all inputs are treated as signed.
|
|
*
|
|
* If fMDset is not set, however, then there was a divide exception (ie, the divisor was either zero or too small).
|
|
*
|
|
* Refer to: http://lxr.linux.no/linux+v2.6.22/lib/div64.c
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dstLo (low 32-bit portion of dividend)
|
|
* @param {number} dstHi (high 32-bit portion of dividend)
|
|
* @param {number} src (32-bit divisor)
|
|
*/
|
|
X86.fnIDIV32 = function IDIV32(dstLo, dstHi, src)
|
|
{
|
|
var fNegLo = false, fNegHi = false;
|
|
if (src < 0) {
|
|
src = -src|0;
|
|
fNegLo = !fNegLo;
|
|
}
|
|
if (dstHi < 0) {
|
|
dstLo = -dstLo|0;
|
|
dstHi = (~dstHi + (dstLo? 0 : 1))|0;
|
|
fNegHi = true;
|
|
fNegLo = !fNegLo;
|
|
}
|
|
X86.fnDIV32.call(this, dstLo, dstHi, src);
|
|
if (this.regMDLo > 0x7fffffff) this.fMDSet = false;
|
|
if (fNegLo) this.regMDLo = -this.regMDLo;
|
|
if (fNegHi) this.regMDHi = -this.regMDHi;
|
|
};
|
|
|
|
/**
|
|
* fnDIVb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst (the divisor)
|
|
* @param {number} src (null; AX is the implied src)
|
|
* @return {number} (we return dst unchanged, since it's actually AX that's modified)
|
|
*/
|
|
X86.fnDIVb = function DIVb(dst, src)
|
|
{
|
|
/*
|
|
* Detect zero divisor
|
|
*/
|
|
if (!dst) {
|
|
X86.fnDIVOverflow.call(this);
|
|
return dst;
|
|
}
|
|
|
|
/*
|
|
* Detect too-small divisor (quotient overflow)
|
|
*/
|
|
var result = ((src = this.regEAX & 0xffff) / dst);
|
|
if (result > 0xff) {
|
|
X86.fnDIVOverflow.call(this);
|
|
return dst;
|
|
}
|
|
|
|
this.fMDSet = true;
|
|
this.regMDLo = (result & 0xff) | (((src % dst) & 0xff) << 8);
|
|
|
|
/*
|
|
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
|
|
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
|
|
* However, src is technically an output, and dst is merely an input (which is why we must return
|
|
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
|
|
*/
|
|
if (DEBUG && DEBUGGER) this.traceLog('DIVb', src, dst, null, this.getPS(), this.regMDLo);
|
|
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 DIVw(dst, src)
|
|
{
|
|
if (this.dataSize == 2) {
|
|
/*
|
|
* Detect zero divisor
|
|
*/
|
|
if (!dst) {
|
|
X86.fnDIVOverflow.call(this);
|
|
return dst;
|
|
}
|
|
/*
|
|
* Detect too-small divisor (quotient overflow)
|
|
*
|
|
* WARNING: We CANNOT simply do "src = (this.regEDX << 16) | this.regEAX", because if bit 15 of DX
|
|
* is set, JavaScript will create a negative 32-bit number. So we instead use non-bit-wise operators
|
|
* to force JavaScript to create a floating-point value that won't suffer from 32-bit-math side-effects.
|
|
*/
|
|
src = (this.regEDX & 0xffff) * 0x10000 + (this.regEAX & 0xffff);
|
|
var result = (src / dst)|0;
|
|
if (result >= 0x10000) {
|
|
X86.fnDIVOverflow.call(this);
|
|
return dst;
|
|
}
|
|
this.fMDSet = true;
|
|
this.regMDLo = (result & 0xffff);
|
|
this.regMDHi = (src % dst) & 0xffff;
|
|
}
|
|
else {
|
|
X86.fnDIV32.call(this, this.regEAX, this.regEDX, dst);
|
|
if (!this.fMDSet) {
|
|
X86.fnDIVOverflow.call(this);
|
|
return dst;
|
|
}
|
|
this.regMDLo |= 0;
|
|
this.regMDHi |= 0;
|
|
}
|
|
|
|
/*
|
|
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
|
|
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
|
|
* However, src is technically an output, and dst is merely an input (which is why we must return
|
|
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
|
|
*/
|
|
if (DEBUG && DEBUGGER) {
|
|
if (this.dataSize == 2) {
|
|
this.traceLog('DIVw', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16));
|
|
} else {
|
|
this.traceLog('DIVd', src, dst, null, this.getPS(), this.regMDLo, this.regMDHi);
|
|
}
|
|
}
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesDivWR : this.cycleCounts.nOpCyclesDivWM);
|
|
this.opFlags |= X86.OPFLAG.NOWRITE;
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnESC(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number} dst unchanged
|
|
*/
|
|
X86.fnESC = function ESC(dst, src)
|
|
{
|
|
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 IDIVb(dst, src)
|
|
{
|
|
/*
|
|
* Detect zero divisor
|
|
*/
|
|
if (!dst) {
|
|
X86.fnDIVOverflow.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.fnDIVOverflow.call(this);
|
|
return dst;
|
|
}
|
|
|
|
this.fMDSet = true;
|
|
this.regMDLo = (result & 0xff) | (((src % div) & 0xff) << 8);
|
|
|
|
/*
|
|
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
|
|
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
|
|
* However, src is technically an output, and dst is merely an input (which is why we must return
|
|
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
|
|
*/
|
|
if (DEBUG && DEBUGGER) this.traceLog('IDIVb', src, dst, null, this.getPS(), this.regMDLo);
|
|
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 IDIVw(dst, src)
|
|
{
|
|
if (this.dataSize == 2) {
|
|
/*
|
|
* Detect zero divisor
|
|
*/
|
|
if (!dst) {
|
|
X86.fnDIVOverflow.call(this);
|
|
return dst;
|
|
}
|
|
|
|
/*
|
|
* Detect too-small divisor (quotient overflow)
|
|
*/
|
|
var div = ((dst << 16) >> 16);
|
|
var result = ((src = (this.regEDX << 16) | (this.regEAX & 0xffff)) / div)|0;
|
|
|
|
/*
|
|
* Note the following difference, from "AP-186: Introduction to the 80186 Microprocessor, March 1983":
|
|
*
|
|
* "The 8086 will cause a divide error whenever the absolute value of the quotient is greater then 7FFFH
|
|
* (for word operations) or if the absolute value of the quotient is greater than 7FH (for byte operations).
|
|
* The 80186 has expanded the range of negative numbers allowed as a quotient by 1 to include 8000H and 80H.
|
|
* These numbers represent the most negative numbers representable using 2's complement arithmetic (equaling
|
|
* -32768 and -128 in decimal, respectively)."
|
|
*/
|
|
if (result != ((result << 16) >> 16) || this.model == X86.MODEL_8086 && result == -32768) {
|
|
X86.fnDIVOverflow.call(this);
|
|
return dst;
|
|
}
|
|
|
|
this.fMDSet = true;
|
|
this.regMDLo = (result & 0xffff);
|
|
this.regMDHi = (src % div) & 0xffff;
|
|
}
|
|
else {
|
|
X86.fnIDIV32.call(this, this.regEAX, this.regEDX, dst);
|
|
if (!this.fMDSet) {
|
|
X86.fnDIVOverflow.call(this);
|
|
return dst;
|
|
}
|
|
this.regMDLo |= 0;
|
|
this.regMDHi |= 0;
|
|
}
|
|
|
|
/*
|
|
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
|
|
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
|
|
* However, src is technically an output, and dst is merely an input (which is why we must return
|
|
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
|
|
*/
|
|
if (DEBUG && DEBUGGER) {
|
|
if (this.dataSize == 2) {
|
|
this.traceLog('IDIVw', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16));
|
|
} else {
|
|
this.traceLog('IDIVd', src, dst, null, this.getPS(), this.regMDLo, this.regMDHi);
|
|
}
|
|
}
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIDivWR : this.cycleCounts.nOpCyclesIDivWM);
|
|
this.opFlags |= X86.OPFLAG.NOWRITE;
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* 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 IMUL8(dst, src)
|
|
{
|
|
dst = this.getIPByte();
|
|
var result = (((src << 16) >> 16) * ((dst << 24) >> 24))|0;
|
|
|
|
if (result > 32767 || result < -32768) {
|
|
this.setCF(); this.setOF();
|
|
} else {
|
|
this.clearCF(); this.clearOF();
|
|
}
|
|
|
|
result &= 0xffff;
|
|
if (DEBUG && DEBUGGER) this.traceLog('IMUL8', dst, src, null, this.getPS(), result);
|
|
|
|
/*
|
|
* NOTE: These are the cycle counts for the 80286; the 80186/80188 have slightly different values (ranges):
|
|
* 22-25 and 29-32 instead of 21 and 24, respectively. However, accurate cycle counts for the 80186/80188 is
|
|
* not super-critical. TODO: Fix this someday.
|
|
*/
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 21 : 24);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* 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)
|
|
*
|
|
* An earlier version of this function assumed it simply needed to check bit 7 of the result to determine carry,
|
|
* which was completely broken.
|
|
*
|
|
* @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 IMULb(dst, src)
|
|
{
|
|
var result = ((((src = this.regEAX) << 24) >> 24) * ((dst << 24) >> 24))|0;
|
|
|
|
this.fMDSet = true;
|
|
this.regMDLo = result & 0xffff;
|
|
|
|
if (result > 127 || result < -128) {
|
|
this.setCF(); this.setOF();
|
|
} else {
|
|
this.clearCF(); this.clearOF();
|
|
}
|
|
|
|
/*
|
|
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
|
|
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
|
|
* However, src is technically an output, and dst is merely an input (which is why we must return
|
|
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
|
|
*/
|
|
if (DEBUG && DEBUGGER) this.traceLog('IMULb', src, dst, null, this.getPS(), this.regMDLo);
|
|
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIMulBR : this.cycleCounts.nOpCyclesIMulBM);
|
|
this.opFlags |= X86.OPFLAG.NOWRITE;
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* 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 IMULn(dst, src)
|
|
{
|
|
var fOverflow, result;
|
|
dst = this.getIPWord();
|
|
if (this.dataSize == 2) {
|
|
result = (((src << 16) >> 16) * ((dst << 16) >> 16))|0;
|
|
fOverflow = (result > 32767 || result < -32768);
|
|
} else {
|
|
result = (src * dst);
|
|
fOverflow = (result > 2147483647 || result < -2147483648);
|
|
this.assert(fOverflow == (result != (result|0)));
|
|
}
|
|
|
|
if (fOverflow) {
|
|
this.setCF(); this.setOF();
|
|
} else {
|
|
this.clearCF(); this.clearOF();
|
|
}
|
|
|
|
result &= this.dataMask;
|
|
if (DEBUG && DEBUGGER) this.traceLog('IMULn', dst, src, null, this.getPS(), result);
|
|
|
|
/*
|
|
* NOTE: These are the cycle counts for the 80286; the 80186/80188 have slightly different values (ranges):
|
|
* 22-25 and 29-32 instead of 21 and 24, respectively. However, accurate cycle counts for the 80186/80188 is
|
|
* not super-critical. TODO: Fix this someday.
|
|
*/
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 21 : 24);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnIMUL32(dst, src)
|
|
*
|
|
* This sets regMDHi:regMDLo to the 64-bit result of dst * src, both of which are treated as signed.
|
|
*
|
|
* TODO: Some potential optimizations include:
|
|
*
|
|
* 1) Early outs if either parameter is zero, since the result will obviously be zero
|
|
* 2) Using "normal" JavaScript multiplication if both parameters are >= -32768 && <= 32767
|
|
*
|
|
* Refer to: http://stackoverflow.com/questions/13597364/32-bit-signed-multiplication-with-a-64-bit-result-in-javascript
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst (any 32-bit number, treated as signed)
|
|
* @param {number} src (any 32-bit number, treated as signed)
|
|
*/
|
|
X86.fnIMUL32 = function IMUL32(dst, src)
|
|
{
|
|
var fNeg = false;
|
|
if (src < 0) {
|
|
src = -src|0;
|
|
fNeg = !fNeg;
|
|
}
|
|
if (dst < 0) {
|
|
dst = -dst|0;
|
|
fNeg = !fNeg;
|
|
}
|
|
X86.fnMUL32.call(this, dst, src);
|
|
if (fNeg) {
|
|
this.regMDLo = (~this.regMDLo + 1)|0;
|
|
this.regMDHi = (~this.regMDHi + (this.regMDLo? 0 : 1))|0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* fnIMULw(dst, src)
|
|
*
|
|
* 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)
|
|
*
|
|
* An earlier version of this function assumed it simply needed to check bit 15 of the result to determine carry,
|
|
* which was completely broken.
|
|
*
|
|
* @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.fnIMULw = function IMULw(dst, src)
|
|
{
|
|
var fOverflow;
|
|
if (this.dataSize == 2) {
|
|
src = this.regEAX & 0xffff;
|
|
var result = (((src << 16) >> 16) * ((dst << 16) >> 16))|0;
|
|
this.fMDSet = true;
|
|
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();
|
|
}
|
|
|
|
/*
|
|
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
|
|
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
|
|
* However, src is technically an output, and dst is merely an input (which is why we must return
|
|
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
|
|
*/
|
|
if (DEBUG && DEBUGGER) {
|
|
if (this.dataSize == 2) {
|
|
this.traceLog('IMULw', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16));
|
|
} else {
|
|
this.traceLog('IMULd', src, dst, null, this.getPS(), this.regMDLo, this.regMDHi);
|
|
}
|
|
}
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIMulWR : this.cycleCounts.nOpCyclesIMulWM);
|
|
this.opFlags |= X86.OPFLAG.NOWRITE;
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnIMULrw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnIMULrw = function IMULrw(dst, src)
|
|
{
|
|
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? this.cycleCounts.nOpCyclesIMulR : this.cycleCounts.nOpCyclesIMulM);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnIMULrd(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnIMULrd = function IMULrd(dst, src)
|
|
{
|
|
var result = dst * src;
|
|
if (result > 2147483647 || result < -2147483648) {
|
|
this.setCF(); this.setOF();
|
|
} else {
|
|
this.clearCF(); this.clearOF();
|
|
}
|
|
result |= 0;
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIMulR : this.cycleCounts.nOpCyclesIMulM);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnINCb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnINCb = function INCb(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;
|
|
};
|
|
|
|
/**
|
|
* fnINCr(w)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} w
|
|
* @return {number}
|
|
*/
|
|
X86.fnINCr = function INCr(w)
|
|
{
|
|
var result = ((w & this.dataMask) + 1)|0;
|
|
this.setArithResult(w, 1, result, X86.RESULT.WORD | X86.RESULT.NOTCF);
|
|
this.nStepCycles -= 2; // the register form of INC takes 2 cycles on all CPUs
|
|
return (w & ~this.dataMask) | (result & this.dataMask);
|
|
};
|
|
|
|
/**
|
|
* fnINCw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnINCw = function INCw(dst, src)
|
|
{
|
|
var w = (dst + 1)|0;
|
|
this.setArithResult(dst, 1, w, X86.RESULT.WORD | X86.RESULT.NOTCF);
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesIncR : this.cycleCounts.nOpCyclesIncM);
|
|
return w & 0xffff;
|
|
};
|
|
|
|
/**
|
|
* fnINT(nIDT, nError, nCycles)
|
|
*
|
|
* NOTE: We no longer use setCSIP(), because it always loads the new CS using segCS.load(), which
|
|
* only knows how to load GDT and LDT descriptors, whereas interrupts must use setCS.loadIDT(), which
|
|
* deals exclusively with IDT descriptors.
|
|
*
|
|
* This means we must take care to replicate critical features of setCSIP(); eg, setting segCS.fCall
|
|
* before calling loadIDT(), updating LIP, and flushing the prefetch queue.
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} nIDT
|
|
* @param {number|null|undefined} nError
|
|
* @param {number} nCycles (in addition to the default of nOpCyclesInt)
|
|
*/
|
|
X86.fnINT = function INT(nIDT, nError, nCycles)
|
|
{
|
|
/*
|
|
* TODO: We assess the cycle cost up front, because otherwise, if loadIDT() fails, no cost may be assessed.
|
|
*/
|
|
this.nStepCycles -= this.cycleCounts.nOpCyclesInt + nCycles;
|
|
this.segCS.fCall = true;
|
|
var oldPS = this.getPS();
|
|
var oldCS = this.getCS();
|
|
var oldIP = this.getIP();
|
|
var addr = this.segCS.loadIDT(nIDT);
|
|
if (addr != X86.ADDR_INVALID) {
|
|
this.regLIP = addr;
|
|
if (PREFETCH) this.flushPrefetch(this.regLIP);
|
|
this.pushWord(oldPS);
|
|
this.pushWord(oldCS);
|
|
this.pushWord(oldIP);
|
|
if (nError != null) this.pushWord(nError);
|
|
this.nFault = -1;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* fnIRET()
|
|
*
|
|
* @this {X86CPU}
|
|
*/
|
|
X86.fnIRET = function IRET()
|
|
{
|
|
/*
|
|
* TODO: We assess a fixed cycle cost up front, because at the moment, switchTSS() doesn't assess anything.
|
|
*/
|
|
this.nStepCycles -= this.cycleCounts.nOpCyclesIRet;
|
|
if (this.regCR0 & X86.CR0.MSW.PE) {
|
|
if (this.regPS & X86.PS.NT) {
|
|
var addrNew = this.segTSS.base;
|
|
var sel = this.getShort(addrNew + X86.TSS.PREV_TSS);
|
|
this.segCS.switchTSS(sel, false);
|
|
return;
|
|
}
|
|
}
|
|
var cpl = this.segCS.cpl;
|
|
var newIP = this.popWord();
|
|
var newCS = this.popWord();
|
|
var newPS = this.popWord();
|
|
if (DEBUG) this.printMessage(" returning to " + str.toHex(newCS, 4) + ':' + str.toHex(newIP, this.dataSize << 1), this.bitsMessage, true);
|
|
if (this.setCSIP(newIP, newCS, false) != null) {
|
|
this.setPS(newPS, cpl);
|
|
if (this.cIntReturn) this.checkIntReturn(this.regLIP);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* fnJMPw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnJMPw = function JMPw(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 JMPFdw(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
return X86.fnGRPUndefined.call(this, dst, src);
|
|
}
|
|
this.setCSIP(dst, this.getShort(this.regEA + this.dataSize));
|
|
if (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 LAR(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.
|
|
*/
|
|
if (this.segVER.load(src, true) != X86.ADDR_INVALID) {
|
|
if (this.segVER.dpl >= this.segCS.cpl && this.segVER.dpl >= (src & X86.SEL.RPL)) {
|
|
this.setZF();
|
|
return this.segVER.acc & X86.DESC.ACC.MASK;
|
|
}
|
|
}
|
|
this.clearZF();
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnLCR0(l)
|
|
*
|
|
* This called on behalf of 80386 opcodes only (ie, MOV CR0,reg).
|
|
*
|
|
* TODO: Determine which CR0 bits, if any, cannot be modified by MOV CR0,reg.
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} l
|
|
*/
|
|
X86.fnLCR0 = function LCR0(l)
|
|
{
|
|
this.regCR0 = l;
|
|
this.setProtMode();
|
|
};
|
|
|
|
/**
|
|
* fnLDS(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnLDS = function LDS(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
X86.opUndefined.call(this);
|
|
return dst;
|
|
}
|
|
this.setDS(this.getShort(this.regEA + this.dataSize));
|
|
this.nStepCycles -= this.cycleCounts.nOpCyclesLS;
|
|
return src;
|
|
};
|
|
|
|
/**
|
|
* fnLEA(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnLEA = function LEA(dst, src)
|
|
{
|
|
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 LES(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
X86.opUndefined.call(this);
|
|
return dst;
|
|
}
|
|
this.setES(this.getShort(this.regEA + this.dataSize));
|
|
this.nStepCycles -= this.cycleCounts.nOpCyclesLS;
|
|
return src;
|
|
};
|
|
|
|
/**
|
|
* fnLFS(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnLFS = function LFS(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
X86.opUndefined.call(this);
|
|
return dst;
|
|
}
|
|
this.setFS(this.getShort(this.regEA + this.dataSize));
|
|
this.nStepCycles -= this.cycleCounts.nOpCyclesLS;
|
|
return src;
|
|
};
|
|
|
|
/**
|
|
* fnLGDT(dst, src)
|
|
*
|
|
* op=0x0F,0x01,reg=0x2 (GRP7:LGDT)
|
|
*
|
|
* The 80286 LGDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address
|
|
* (or a 32-bit address in 32-bit mode); 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.
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnLGDT = function LGDT(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
X86.opInvalid.call(this);
|
|
} else {
|
|
/*
|
|
* It shouldn't hurt to always fetch 32 bits of physical memory, which we'll then
|
|
* mask with either a 24-bit or a 32-bit mask.
|
|
*/
|
|
this.addrGDT = this.getLong(this.regEA + 2) & (this.dataMask | (this.dataMask << 8));
|
|
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 LGS(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
X86.opUndefined.call(this);
|
|
return dst;
|
|
}
|
|
this.setGS(this.getShort(this.regEA + this.dataSize));
|
|
this.nStepCycles -= this.cycleCounts.nOpCyclesLS;
|
|
return src;
|
|
};
|
|
|
|
/**
|
|
* fnLIDT(dst, src)
|
|
*
|
|
* op=0x0F,0x01,reg=0x3 (GRP7:LIDT)
|
|
*
|
|
* The 80286 LIDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address
|
|
* (or a 32-bit address in 32-bit mode); 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.
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnLIDT = function LIDT(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
X86.opInvalid.call(this);
|
|
} else {
|
|
/*
|
|
* It shouldn't hurt to always fetch 32 bits of physical memory, which we'll then
|
|
* mask with either a 24-bit or a 32-bit mask.
|
|
*/
|
|
this.addrIDT = this.getLong(this.regEA + 2) & (this.dataMask | (this.dataMask << 8));
|
|
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 LLDT(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 LMSW(dst, src)
|
|
{
|
|
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 LSL(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, true) != 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.segCS.cpl) && 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 LSS(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
X86.opUndefined.call(this);
|
|
return dst;
|
|
}
|
|
this.setSS(this.getShort(this.regEA + this.dataSize));
|
|
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 LTR(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.LDT);
|
|
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 MOV(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;
|
|
};
|
|
|
|
/**
|
|
* fnMOVX(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst (current value, ignored)
|
|
* @param {number} src (new value)
|
|
* @return {number} dst (updated value, from src)
|
|
*/
|
|
X86.fnMOVX = function MOVX(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 MOVn(dst, src)
|
|
{
|
|
this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesMovRI : this.cycleCounts.nOpCyclesMovMI);
|
|
return src;
|
|
};
|
|
|
|
/**
|
|
* fnMOVxx(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst (current value, ignored)
|
|
* @param {number} src (new value)
|
|
* @return {number} dst (src is overridden, replaced with regXX, as specified by opMOVwsr())
|
|
*/
|
|
X86.fnMOVxx = function MOVxx(dst, src)
|
|
{
|
|
return X86.fnMOV.call(this, dst, this.regXX);
|
|
};
|
|
|
|
/**
|
|
* 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 MULb(dst, src)
|
|
{
|
|
this.fMDSet = true;
|
|
this.regMDLo = ((src = this.regEAX & 0xff) * dst) & 0xffff;
|
|
|
|
if (this.regMDLo & 0xff00) {
|
|
this.setCF(); this.setOF();
|
|
} else {
|
|
this.clearCF(); this.clearOF();
|
|
}
|
|
|
|
/*
|
|
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
|
|
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
|
|
* However, src is technically an output, and dst is merely an input (which is why we must return
|
|
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
|
|
*/
|
|
if (DEBUG && DEBUGGER) this.traceLog('MULb', src, dst, null, this.getPS(), this.regMDLo);
|
|
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesMulBR : this.cycleCounts.nOpCyclesMulBM);
|
|
this.opFlags |= X86.OPFLAG.NOWRITE;
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnMUL32(dst, src)
|
|
*
|
|
* This sets regMDHi:regMDLo to the 64-bit result of dst * src, both of which are treated as unsigned.
|
|
*
|
|
* TODO: Some potential optimizations include:
|
|
*
|
|
* 1) Early outs if either parameter is zero, since the result will obviously be zero
|
|
* 2) Using "normal" JavaScript multiplication if both parameters are < 32767
|
|
*
|
|
* Refer to: http://stackoverflow.com/questions/13597364/32-bit-signed-multiplication-with-a-64-bit-result-in-javascript
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst (any 32-bit number, treated as unsigned)
|
|
* @param {number} src (any 32-bit number, treated as unsigned)
|
|
*/
|
|
X86.fnMUL32 = function MUL32(dst, src)
|
|
{
|
|
var srcLo = src & 0xffff;
|
|
var srcHi = src >>> 16;
|
|
var dstLo = dst & 0xffff;
|
|
var dstHi = dst >>> 16;
|
|
|
|
var mul00 = srcLo * dstLo;
|
|
var mul16 = ((mul00 >>> 16) + (srcHi * dstLo));
|
|
var mul32 = mul16 >>> 16;
|
|
mul16 = ((mul16 & 0xffff) + (srcLo * dstHi));
|
|
mul32 += ((mul16 >>> 16) + (srcHi * dstHi));
|
|
|
|
this.fMDSet = true;
|
|
this.regMDLo = (mul16 << 16) | (mul00 & 0xffff);
|
|
this.regMDHi = mul32|0;
|
|
};
|
|
|
|
/**
|
|
* 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 MULw(dst, src)
|
|
{
|
|
if (this.dataSize == 2) {
|
|
src = this.regEAX & 0xffff;
|
|
var result = (src * dst)|0;
|
|
this.fMDSet = true;
|
|
this.regMDLo = result & 0xffff;
|
|
this.regMDHi = (result >> 16) & 0xffff;
|
|
} else {
|
|
X86.fnMUL32.call(this, dst, this.regEAX);
|
|
}
|
|
|
|
if (this.regMDHi) {
|
|
this.setCF(); this.setOF();
|
|
} else {
|
|
this.clearCF(); this.clearOF();
|
|
}
|
|
|
|
/*
|
|
* Multiply/divide instructions specify only a single operand, which the decoders pass to us
|
|
* via the dst parameter, so we set src to the other implied operand (either AX or DX:AX).
|
|
* However, src is technically an output, and dst is merely an input (which is why we must return
|
|
* dst unchanged). So, to make traceLog() more consistent, we reverse the order of dst and src.
|
|
*/
|
|
if (DEBUG && DEBUGGER) {
|
|
if (this.dataSize == 2) {
|
|
this.traceLog('MULw', src, dst, null, this.getPS(), this.regMDLo | (this.regMDHi << 16));
|
|
} else {
|
|
this.traceLog('MULd', src, dst, null, this.getPS(), this.regMDLo, this.regMDHi);
|
|
}
|
|
}
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesMulWR : this.cycleCounts.nOpCyclesMulWM);
|
|
this.opFlags |= X86.OPFLAG.NOWRITE;
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnNEGb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnNEGb = function NEGb(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 NEGw(dst, src)
|
|
{
|
|
var w = (-dst)|0;
|
|
this.setArithResult(0, dst, w, X86.RESULT.WORD | X86.RESULT.ALL, true);
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesNegR : this.cycleCounts.nOpCyclesNegM);
|
|
return w & 0xffff;
|
|
};
|
|
|
|
/**
|
|
* fnNOTb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnNOTb = function NOTb(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 NOTw(dst, src)
|
|
{
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesNegR : this.cycleCounts.nOpCyclesNegM);
|
|
return dst ^ 0xffff;
|
|
};
|
|
|
|
/**
|
|
* fnORb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnORb = function ORb(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 ORw(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.dataType);
|
|
};
|
|
|
|
/**
|
|
* 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 POPw(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 PUSHw(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 RCLb(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('RCLb', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnRCLw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnRCLw = function RCLw(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('RCLw', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnRCLd(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnRCLd = function RCLd(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('RCLd', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnRCRb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnRCRb = function RCRb(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('RCRb', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnRCRw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnRCRw = function RCRw(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('RCRw', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnRCRd(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnRCRd = function RCRd(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('RCRd', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnRETF(n)
|
|
*
|
|
* For protected-mode, this function must be prepared to pop any arguments off the current stack AND
|
|
* whatever stack we may have switched to (setCSIP() returns true only when a stack switch has occurred).
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} n
|
|
*/
|
|
X86.fnRETF = function RETF(n)
|
|
{
|
|
var newIP = this.popWord();
|
|
var newCS = this.popWord();
|
|
if (DEBUG) this.printMessage(" returning to " + str.toHex(newCS, 4) + ':' + str.toHex(newIP, this.dataSize << 1), this.bitsMessage, true);
|
|
n <<= (this.dataSize >> 2);
|
|
if (n) this.setSP(this.getSP() + n); // TODO: optimize
|
|
if (this.setCSIP(newIP, newCS, false)) {
|
|
if (n) this.setSP(this.getSP() + n); // TODO: optimize
|
|
/*
|
|
* As per Intel documentation: "If any of [the DS or ES] registers refer to segments whose DPL is
|
|
* less than the new CPL (excluding conforming code segments), the segment register is loaded with
|
|
* the null selector."
|
|
*
|
|
* TODO: I'm not clear on whether a conforming code segment must also be marked readable, so I'm playing
|
|
* it safe and using CODE_CONFORMING instead of CODE_CONFORMING_READABLE. Also, for the record, I've not
|
|
* seen this situation occur yet (eg, in OS/2 1.0).
|
|
*/
|
|
if ((this.segDS.sel & X86.SEL.MASK) && this.segDS.dpl < this.segCS.cpl && (this.segDS.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) {
|
|
this.assert(false); // I'm not asserting this is bad, I just want to see it in action
|
|
this.segDS.load(0);
|
|
}
|
|
if ((this.segES.sel & X86.SEL.MASK) && this.segES.dpl < this.segCS.cpl && (this.segES.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) {
|
|
this.assert(false); // I'm not asserting this is bad, I just want to see it in action
|
|
this.segES.load(0);
|
|
}
|
|
}
|
|
if (n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP);
|
|
};
|
|
|
|
/**
|
|
* fnROLb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnROLb = function ROLb(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('ROLb', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnROLw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnROLw = function ROLw(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('ROLw', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnROLd(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnROLd = function ROLd(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('ROLd', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnRORb(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnRORb = function RORb(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('RORb', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnRORw(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnRORw = function RORw(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('RORw', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnRORd(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (1 or CL)
|
|
* @return {number}
|
|
*/
|
|
X86.fnRORd = function RORd(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
var count = src & this.nShiftCountMask;
|
|
if (count) {
|
|
var carry = dst << (32 - count);
|
|
result = (dst >>> count) | carry;
|
|
this.setRotateResult(result, carry, X86.RESULT.DWORD);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('RORd', dst, src, flagsIn, this.getPS(), result);
|
|
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 SARb(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 SARw(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 SARd(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 SBBb(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 SBBw(dst, src)
|
|
{
|
|
var w = (dst - src - this.getCarry())|0;
|
|
this.setArithResult(dst, src, w, this.dataType | 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.dataMask;
|
|
};
|
|
|
|
/**
|
|
* fnSETcc()
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {function(number,number)} fnSet
|
|
*/
|
|
X86.fnSETcc = function SETcc(fnSet)
|
|
{
|
|
this.opFlags |= X86.OPFLAG.NOREAD;
|
|
this.aOpModMemByte[this.getIPByte()].call(this, fnSet);
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesSetR : this.cycleCounts.nOpCyclesSetM);
|
|
};
|
|
|
|
/**
|
|
* fnSETO(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst (ignored)
|
|
* @param {number} src (ignored)
|
|
* @return {number}
|
|
*/
|
|
X86.fnSETO = function SETO(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 SETNO(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 SETC(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 SETNC(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 SETZ(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 SETNZ(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 SETBE(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 SETNBE(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 SETS(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 SETNS(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 SETP(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 SETNP(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 SETL(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 SETNL(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 SETLE(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 SETNLE(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 SGDT(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
X86.opInvalid.call(this);
|
|
} else {
|
|
/*
|
|
* We don't need to setShort() 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;
|
|
/*
|
|
* 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.
|
|
*/
|
|
var addr = this.addrGDT | (this.model == X86.MODEL_80286? 0xff000000 : 0);
|
|
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 SHLb(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('SHLb', dst, src, flagsIn, this.getPS(), result);
|
|
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 SHLw(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('SHLw', dst, src, flagsIn, this.getPS(), result);
|
|
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 SHLd(dst, src)
|
|
{
|
|
var result = dst;
|
|
var flagsIn = (DEBUG? this.getPS() : 0);
|
|
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);
|
|
}
|
|
if (DEBUG && DEBUGGER) this.traceLog('SHLd', dst, src, flagsIn, this.getPS(), result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* fnSHLDw(dst, src, count)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @param {number} count (0-31)
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHLDw = function SHLDw(dst, src, count)
|
|
{
|
|
if (count) {
|
|
if (count > 16) {
|
|
dst = src;
|
|
count -= 16;
|
|
}
|
|
var carry = dst << (count - 1);
|
|
dst = ((carry << 1) | (src >> (16 - count))) & 0xffff;
|
|
this.setLogicResult(dst, X86.RESULT.WORD, carry & X86.RESULT.WORD);
|
|
}
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnSHLDd(dst, src, count)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @param {number} count
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHLDd = function SHLDd(dst, src, count)
|
|
{
|
|
if (count) {
|
|
var carry = dst << (count - 1);
|
|
dst = (carry << 1) | (src >> (32 - count));
|
|
this.setLogicResult(dst, X86.RESULT.DWORD, carry & X86.RESULT.DWORD);
|
|
}
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnSHLDwi(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHLDwi = function SHLDwi(dst, src)
|
|
{
|
|
return X86.fnSHLDw.call(this, dst, src, this.getIPByte());
|
|
};
|
|
|
|
/**
|
|
* fnSHLDdi(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHLDdi = function SHLDdi(dst, src)
|
|
{
|
|
return X86.fnSHLDd.call(this, dst, src, this.getIPByte());
|
|
};
|
|
|
|
/**
|
|
* fnSHLDwCL(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHLDwCL = function SHLDwCL(dst, src)
|
|
{
|
|
return X86.fnSHLDw.call(this, dst, src, this.regECX & 0x1f);
|
|
};
|
|
|
|
/**
|
|
* fnSHLDdCL(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHLDdCL = function SHLDdCL(dst, src)
|
|
{
|
|
return X86.fnSHLDd.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 SHRb(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 SHRw(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 SHRd(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;
|
|
};
|
|
|
|
/**
|
|
* fnSHRDw(dst, src, count)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @param {number} count (0-31)
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHRDw = function SHRDw(dst, src, count)
|
|
{
|
|
if (count) {
|
|
if (count > 16) {
|
|
dst = src;
|
|
count -= 16;
|
|
}
|
|
var carry = dst >> (count - 1);
|
|
dst = ((carry >> 1) | (src << (16 - count))) & 0xffff;
|
|
this.setLogicResult(dst, X86.RESULT.WORD, carry & 0x1);
|
|
}
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnSHRDd(dst, src, count)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @param {number} count
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHRDd = function SHRDd(dst, src, count)
|
|
{
|
|
if (count) {
|
|
var carry = dst >> (count - 1);
|
|
dst = (carry >> 1) | (src << (32 - count));
|
|
this.setLogicResult(dst, X86.RESULT.DWORD, carry & 0x1);
|
|
}
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnSHRDwi(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHRDwi = function SHRDwi(dst, src)
|
|
{
|
|
return X86.fnSHRDw.call(this, dst, src, this.getIPByte());
|
|
};
|
|
|
|
/**
|
|
* fnSHRDdi(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHRDdi = function SHRDdi(dst, src)
|
|
{
|
|
return X86.fnSHRDd.call(this, dst, src, this.getIPByte());
|
|
};
|
|
|
|
/**
|
|
* fnSHRDwCL(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHRDwCL = function SHRDwCL(dst, src)
|
|
{
|
|
return X86.fnSHRDw.call(this, dst, src, this.regECX & 0x1f);
|
|
};
|
|
|
|
/**
|
|
* fnSHRDdCL(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnSHRDdCL = function SHRDdCL(dst, src)
|
|
{
|
|
return X86.fnSHRDd.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 SIDT(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
X86.opInvalid.call(this);
|
|
} else {
|
|
/*
|
|
* We don't need to setShort() 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;
|
|
/*
|
|
* 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 | (this.model == X86.MODEL_80286? 0xff000000 : 0);
|
|
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 SLDT(dst, src)
|
|
{
|
|
this.nStepCycles -= (2 + (this.regEA === X86.ADDR_INVALID? 0 : 1));
|
|
return this.segLDT.sel;
|
|
};
|
|
|
|
/**
|
|
* fnSMSW(dst, src)
|
|
*
|
|
* op=0x0F,0x01,reg=0x4 (GRP7:SMSW)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null)
|
|
* @return {number}
|
|
*/
|
|
X86.fnSMSW = function SMSW(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 STR(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 SUBb(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 SUBw(dst, src)
|
|
{
|
|
var w = (dst - src)|0;
|
|
this.setArithResult(dst, src, w, this.dataType | 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.dataMask;
|
|
};
|
|
|
|
/**
|
|
* fnTEST8(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null; we have to supply the source ourselves)
|
|
* @return {number}
|
|
*/
|
|
X86.fnTEST8 = function TEST8(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;
|
|
};
|
|
|
|
/**
|
|
* fnTEST16(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src (null; we have to supply the source ourselves)
|
|
* @return {number}
|
|
*/
|
|
X86.fnTEST16 = function TEST16(dst, src)
|
|
{
|
|
src = this.getIPWord();
|
|
this.setLogicResult(dst & src, X86.RESULT.WORD);
|
|
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 TESTb(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 TESTw(dst, src)
|
|
{
|
|
this.setLogicResult(dst & src, X86.RESULT.WORD);
|
|
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 VERR(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, true) != 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.segCS.cpl && 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();
|
|
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 VERW(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, true) != 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.segCS.cpl && this.segVER.dpl >= (dst & X86.SEL.RPL)) {
|
|
this.setZF();
|
|
return dst;
|
|
}
|
|
}
|
|
}
|
|
this.clearZF();
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* 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 XCHGRb(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
/*
|
|
* Decode which register was src
|
|
*/
|
|
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 & 0xff) | (dst << 8);
|
|
break;
|
|
case 0x5: // CH
|
|
this.regECX = (this.regECX & 0xff) | (dst << 8);
|
|
break;
|
|
case 0x6: // DH
|
|
this.regEDX = (this.regEDX & 0xff) | (dst << 8);
|
|
break;
|
|
case 0x7: // BH
|
|
this.regEBX = (this.regEBX & 0xff) | (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 called modEAByte()
|
|
* instead of getEAByte(), so we compensate by updating regEAWrite. However, setEAByte() 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 XCHGRw(dst, src)
|
|
{
|
|
if (this.regEA === X86.ADDR_INVALID) {
|
|
/*
|
|
* Decode which register was src
|
|
*/
|
|
switch (this.bModRM & 0x7) {
|
|
case 0x0: // AX
|
|
this.regEAX = dst;
|
|
break;
|
|
case 0x1: // CX
|
|
this.regECX = dst;
|
|
break;
|
|
case 0x2: // DX
|
|
this.regEDX = dst;
|
|
break;
|
|
case 0x3: // BX
|
|
this.regEBX = dst;
|
|
break;
|
|
case 0x4: // SP
|
|
this.setSP(dst);
|
|
break;
|
|
case 0x5: // BP
|
|
this.regEBP = dst;
|
|
break;
|
|
case 0x6: // SI
|
|
this.regESI = dst;
|
|
break;
|
|
case 0x7: // DI
|
|
this.regEDI = 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 called modEAWord()
|
|
* instead of getEAWord(), 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 XORb(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 XORw(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.dataType);
|
|
};
|
|
|
|
/**
|
|
* fnGRPFault(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnGRPFault = function GRPFault(dst, src)
|
|
{
|
|
X86.fnFault.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 GRPInvalid(dst, src)
|
|
{
|
|
X86.opInvalid.call(this);
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnGRPUndefined(dst, src)
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} dst
|
|
* @param {number} src
|
|
* @return {number}
|
|
*/
|
|
X86.fnGRPUndefined = function GRPUndefined(dst, src)
|
|
{
|
|
X86.opUndefined.call(this);
|
|
return dst;
|
|
};
|
|
|
|
/**
|
|
* fnDIVOverflow()
|
|
*
|
|
* @this {X86CPU}
|
|
*/
|
|
X86.fnDIVOverflow = function DIVOverflow()
|
|
{
|
|
this.setIP(this.opLIP - this.segCS.base);
|
|
/*
|
|
* TODO: Determine the proper cycle cost.
|
|
*/
|
|
X86.fnINT.call(this, X86.EXCEPTION.DIV_ERR, null, 2);
|
|
};
|
|
|
|
/**
|
|
* fnSrcCount1()
|
|
*
|
|
* @this {X86CPU}
|
|
* @return {number}
|
|
*/
|
|
X86.fnSrcCount1 = function SrcCount1()
|
|
{
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 2 : this.cycleCounts.nOpCyclesShift1M);
|
|
return 1;
|
|
};
|
|
|
|
/**
|
|
* fnSrcCountCL()
|
|
*
|
|
* @this {X86CPU}
|
|
* @return {number}
|
|
*/
|
|
X86.fnSrcCountCL = function SrcCountCL()
|
|
{
|
|
var count = this.regECX & 0xff;
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesShiftCR : this.cycleCounts.nOpCyclesShiftCM) + (count << this.cycleCounts.nOpCyclesShiftCS);
|
|
return count;
|
|
};
|
|
|
|
/**
|
|
* fnSrcCountN()
|
|
*
|
|
* @this {X86CPU}
|
|
* @return {number}
|
|
*/
|
|
X86.fnSrcCountN = function SrcCountN()
|
|
{
|
|
var count = this.getIPByte();
|
|
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesShiftCR : this.cycleCounts.nOpCyclesShiftCM) + (count << this.cycleCounts.nOpCyclesShiftCS);
|
|
return count;
|
|
};
|
|
|
|
/**
|
|
* fnSrcNone()
|
|
*
|
|
* @this {X86CPU}
|
|
* @return {number|null}
|
|
*/
|
|
X86.fnSrcNone = function SrcNone()
|
|
{
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* fnFault(nFault, nError, fHalt)
|
|
*
|
|
* Helper to dispatch faults.
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} nFault
|
|
* @param {number} [nError]
|
|
* @param {boolean} [fHalt] will halt the CPU if true *and* a Debugger is loaded
|
|
*/
|
|
X86.fnFault = function(nFault, nError, fHalt)
|
|
{
|
|
if (!this.aFlags.fComplete) {
|
|
this.printMessage("Fault " + str.toHexByte(nFault) + " blocked by Debugger", Messages.WARN);
|
|
this.setIP(this.opLIP - this.segCS.base);
|
|
return;
|
|
}
|
|
|
|
var fDispatch = false;
|
|
if (this.model >= X86.MODEL_80186) {
|
|
if (this.nFault < 0) {
|
|
/*
|
|
* Single-fault (error code is passed through, and the responsible instruction is restartable)
|
|
*/
|
|
this.setIP(this.opLIP - this.segCS.base);
|
|
fDispatch = true;
|
|
} else if (this.nFault != X86.EXCEPTION.DF_FAULT) {
|
|
/*
|
|
* Double-fault (error code is always zero, and the responsible instruction is not restartable)
|
|
*/
|
|
nError = 0;
|
|
nFault = X86.EXCEPTION.DF_FAULT;
|
|
fDispatch = true;
|
|
} else {
|
|
/*
|
|
* Triple-fault (usually referred to in Intel literature as a "shutdown", but at least on the 80286,
|
|
* it's actually a "reset")
|
|
*/
|
|
X86.fnFaultMessage.call(this, -1, 0, fHalt);
|
|
this.resetRegs();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (X86.fnFaultMessage.call(this, nFault, nError, fHalt)) {
|
|
fDispatch = false;
|
|
}
|
|
|
|
if (fDispatch) X86.fnINT.call(this, this.nFault = nFault, nError, 0);
|
|
|
|
/*
|
|
* Since this fault is likely being issued in the context of an instruction that hasn't finished
|
|
* executing, and since we currently don't do anything to interrupt that execution (eg, throw a
|
|
* JavaScript exception), we should shut off all further reads/writes for the current instruction.
|
|
*
|
|
* That's easy for any EA-based memory accesses: simply set both the NOREAD and NOWRITE flags.
|
|
* However, there are also direct, non-EA-based memory accesses to consider. A perfect example is
|
|
* opPUSHA(): if a GP fault occurs on any PUSH other than the last, a subsequent PUSH is likely to
|
|
* cause another fault, which we will misinterpret as a double-fault.
|
|
*
|
|
* TODO: Throw a special JavaScript exception that cpu.js must intercept and quietly ignore.
|
|
*/
|
|
this.opFlags &= ~(X86.OPFLAG.NOREAD | X86.OPFLAG.NOWRITE);
|
|
};
|
|
|
|
/**
|
|
* fnPageFault(addr, fPresent, fWrite)
|
|
*
|
|
* Helper to dispatch page faults.
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} addr
|
|
* @param {boolean} fPresent
|
|
* @param {boolean} fWrite
|
|
*/
|
|
X86.fnPageFault = function(addr, fPresent, fWrite)
|
|
{
|
|
this.regCR2 = addr;
|
|
var nError = 0;
|
|
if (fPresent) nError |= X86.PTE.PRESENT;
|
|
if (fWrite) nError |= X86.PTE.READWRITE;
|
|
if (this.segCS.cpl == 3) nError |= X86.PTE.USER;
|
|
X86.fnFault.call(this, X86.EXCEPTION.PG_FAULT, nError);
|
|
};
|
|
|
|
/**
|
|
* fnFaultMessage()
|
|
*
|
|
* Aside from giving the Debugger an opportunity to report every fault, this also gives us the ability to
|
|
* halt exception processing in tracks: return true to prevent the fault handler from being dispatched.
|
|
*
|
|
* At the moment, the only Debugger control you have over fault interception is setting MESSAGE.FAULT, which
|
|
* will display faults as they occur, and MESSAGE.HALT, which will halt after any Debugger message, including
|
|
* MESSAGE.FAULT. If you want execution to continue after halting, clear MESSAGE.FAULT and/or MESSAGE.HALT,
|
|
* or single-step over the offending instruction, which will allow the fault to be dispatched.
|
|
*
|
|
* @this {X86CPU}
|
|
* @param {number} nFault
|
|
* @param {number} [nError]
|
|
* @param {boolean} [fHalt] true if the CPU should always be halted, false if "it depends"
|
|
* @return {boolean|undefined} true to block the fault (often desirable when fHalt is true), otherwise dispatch it
|
|
*/
|
|
X86.fnFaultMessage = function(nFault, nError, fHalt)
|
|
{
|
|
var bitsMessage = Messages.FAULT;
|
|
var bOpcode = this.bus.getByteDirect(this.regLIP);
|
|
|
|
/*
|
|
* OS/2 1.0 uses an INT3 (0xCC) opcode in conjunction with an invalid IDT to trigger a triple-fault
|
|
* reset and return to real-mode, and these resets happen quite frequently during boot; for example,
|
|
* OS/2 startup messages are displayed using a series of INT 0x10 BIOS calls for each character, and
|
|
* each series of BIOS calls requires a round-trip mode switch.
|
|
*
|
|
* Since we really only want to halt on "bad" faults, not "good" (ie, intentional) faults, we take
|
|
* advantage of the fact that all 3 faults comprising the triple-fault point to an INT3 (0xCC) opcode,
|
|
* and so whenever we see that opcode, we ignore the caller's fHalt flag, and suppress FAULT messages
|
|
* unless CPU messages are also enabled.
|
|
*
|
|
* When a triple fault shows up, nFault is -1; it displays as 0xff only because we use toHexByte().
|
|
*/
|
|
if (bOpcode == X86.OPCODE.INT3) {
|
|
fHalt = false;
|
|
bitsMessage |= Messages.CPU;
|
|
}
|
|
|
|
/*
|
|
* Similarly, the PC AT ROM BIOS deliberately generates a couple of GP faults as part of the POST
|
|
* (Power-On Self Test); we don't want to ignore those, but we don't want to halt on them either. We
|
|
* detect those faults by virtue of the LIP being in the range 0x0F0000 to 0x0FFFFF.
|
|
*/
|
|
if (this.regLIP >= 0x0F0000 && this.regLIP <= 0x0FFFFF) {
|
|
fHalt = false;
|
|
}
|
|
|
|
/*
|
|
* However, the foregoing notwithstanding, if MESSAGE.HALT is enabled along with all the other required
|
|
* MESSAGE bits, then we want to halt regardless.
|
|
*/
|
|
if (this.messageEnabled(bitsMessage | Messages.HALT)) {
|
|
fHalt = true;
|
|
}
|
|
|
|
if (this.messageEnabled(bitsMessage) || fHalt) {
|
|
var sMessage = (fHalt? '\n' : '') + "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode " + str.toHexByte(bOpcode) + " at " + this.dbg.hexOffset(this.getIP(), this.getCS()) + " (%" + str.toHex(this.regLIP, 6) + ")";
|
|
var fRunning = this.aFlags.fRunning;
|
|
if (this.printMessage(sMessage, bitsMessage)) {
|
|
if (fHalt) {
|
|
/*
|
|
* By setting fHalt to fRunning (which is true while running but false while single-stepping),
|
|
* this allows a fault to be dispatched when you single-step over a faulting instruction; you can
|
|
* then continue single-stepping into the fault handler, or start running again.
|
|
*
|
|
* Note that we had to capture fRunning before calling printMessage(), because if MESSAGE.HALT
|
|
* is set, printMessage() will have already halted the CPU.
|
|
*/
|
|
fHalt = fRunning;
|
|
this.dbg.stopCPU();
|
|
}
|
|
} else {
|
|
/*
|
|
* If printMessage() returned false, then messageEnabled() must have returned false as well, which
|
|
* means that fHalt must be true. Which means we should shut the machine down.
|
|
*/
|
|
this.assert(fHalt);
|
|
this.notice(sMessage);
|
|
this.stopCPU();
|
|
}
|
|
}
|
|
return fHalt;
|
|
};
|