diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 8b9105252..68d961f5e 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -426,6 +426,7 @@ X86CPU.CYCLES_80286 = { }; X86CPU.CYCLES_80386 = { + nOpCyclesBitScan: 11, nOpCyclesBitSetR: 6, nOpCyclesBitSetM: 8, nOpCyclesBitSetMExtra: 5, // extra cycle cost for non-immediate BTC/BTR/BTS opcodes @@ -434,6 +435,8 @@ X86CPU.CYCLES_80386 = { nOpCyclesBitTestMExtra: 6, // extra cycle cost for non-immediate BT opcode nOpCyclesIMulR: 9, nOpCyclesIMulM: 12, + nOpCyclesMovXR: 3, + nOpCyclesMovXM: 6, nOpCyclesSetR: 4, nOpCyclesSetM: 5, nOpCyclesShiftDR: 3, @@ -770,8 +773,11 @@ X86CPU.prototype.initProcessor = function() this.OPFLAG_NOINTR_8086 = 0; // used with instructions that should *not* set NOINTR on an 80286 (eg, non-SS segment loads) - this.aOps0F = X86.aOps0F; - this.aOps[0x0F] = X86.op0F; + this.aOps[0x0F] = X86.op0F; + this.aOps0F = X86.aOps0F.slice(); + for (var i = 0; i < this.aOps0F.length; i++) { + if (!this.aOps0F[i]) this.aOps0F[i] = X86.opUndefined; + } this.aOps[X86.OPCODE.PUSHSP] = X86.opPUSHSP; // 0x54 this.aOps[X86.OPCODE.ARPL] = X86.opARPL; // 0x63 @@ -781,7 +787,6 @@ X86CPU.prototype.initProcessor = function() this.aOps[X86.OPCODE.GS] = X86.opGS; // 0x65 this.aOps[X86.OPCODE.OS] = X86.opOS; // 0x66 this.aOps[X86.OPCODE.AS] = X86.opAS; // 0x67 - this.aOps0F = X86.aOps0F.slice(); for (bOpcode in X86.aOps0F386) { this.aOps0F[+bOpcode] = X86.aOps0F386[bOpcode]; } diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index dff09492a..de708181f 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -204,6 +204,76 @@ X86.fnBOUND = function BOUND(dst, src) 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) +{ + if (!src) { + this.setZF(); + } else { + this.clearZF(); + var i = 0, bit = 0x1; + while (bit & this.dataMask) { + if (src & bit) { + dst = i; + break; + } + bit <<= 1; + i++; + } + } + this.nStepCycles -= X86CPU.CYCLES_80386.nOpCyclesBitScan + i * 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) +{ + if (!src) { + this.setZF(); + } else { + this.clearZF(); + var i = (this.dataSize == 2? 15 : 31), j = i, bit = 1 << i; + while (bit) { + if (src & bit) { + dst = i; + break; + } + bit >>>= 1; + i--; + } + } + this.nStepCycles -= X86CPU.CYCLES_80386.nOpCyclesBitScan + (j - i) * 3; + return dst; +}; + /** * fnBT(dst, src) * @@ -1052,6 +1122,25 @@ X86.fnLES = function LES(dst, src) 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 + 2)); + this.nStepCycles -= this.CYCLES.nOpCyclesLS; + return src; +}; + /** * fnLGDT(dst, src) * @@ -1079,6 +1168,25 @@ X86.fnLGDT = function LGDT(dst, src) 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 + 2)); + this.nStepCycles -= this.CYCLES.nOpCyclesLS; + return src; +}; + /** * fnLIDT(dst, src) * @@ -1173,6 +1281,25 @@ X86.fnLSL = function LSL(dst, src) 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 + 2)); + this.nStepCycles -= this.CYCLES.nOpCyclesLS; + return src; +}; + /** * fnLTR(dst, src) * @@ -1208,6 +1335,19 @@ X86.fnMOV = function MOV(dst, src) 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) * diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index c239c45f4..94f96c2d7 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -219,7 +219,7 @@ X86.opMOVrcr = function MOVrcr() } */ var reg = (bModRM & 0x38) >> 3; - switch (reg) { + switch(reg) { case 0x0: this.regMD16 = this.regCR0; break; @@ -288,7 +288,7 @@ X86.opMOVcrr = function MOVcrr() return; } this.aOpModRegWord[bModRM].call(this, X86.fnMOV); - switch (reg) { + switch(reg) { case 0x0: reg = this.regEAX; this.regEAX = temp; @@ -958,6 +958,20 @@ X86.opIMUL = function IMUL() this.aOpModRegWord[this.getIPByte()].call(this, this.dataSize == 2? X86.fnIMULrw : X86.fnIMULrd); }; +/** + * opLSS() + * + * op=0x0F,0xB2 (LSS reg,word) + * + * This is like a "MOV reg,rm" operation, but it also loads SS from the next word. + * + * @this {X86CPU} + */ +X86.opLSS = function LSS() +{ + this.aOpModRegWord[this.getIPByte()].call(this, X86.fnLSS); +}; + /** * opBTR() * @@ -971,6 +985,146 @@ X86.opBTR = function BTR() if (this.regEA !== X86.ADDR_INVALID) this.nStepCycles -= X86CPU.CYCLES_80386.nOpCyclesBitSetMExtra; }; +/** + * opLFS() + * + * op=0x0F,0xB4 (LFS reg,word) + * + * This is like a "MOV reg,rm" operation, but it also loads FS from the next word. + * + * @this {X86CPU} + */ +X86.opLFS = function LFS() +{ + this.aOpModRegWord[this.getIPByte()].call(this, X86.fnLFS); +}; + +/** + * opLGS() + * + * op=0x0F,0xB5 (LGS reg,word) + * + * This is like a "MOV reg,rm" operation, but it also loads GS from the next word. + * + * @this {X86CPU} + */ +X86.opLGS = function LGS() +{ + this.aOpModRegWord[this.getIPByte()].call(this, X86.fnLGS); +}; + +/** + * opMOVZXb() + * + * op=0x0F,0xB6 (MOVZX reg,byte) + * + * @this {X86CPU} + */ +X86.opMOVZXb = function MOVZXb() +{ + /* + * The ModRegByte handlers update the registers in the 1st column, but we need to update those in the 2nd column. + * + * 000: AL -> 000: AX + * 001: CL -> 001: CX + * 010: DL -> 010: DX + * 011: BL -> 011: BX + * 100: AH -> 100: SP + * 101: CH -> 101: BP + * 110: DH -> 110: SI + * 111: BH -> 111: DI + */ + var temp; + var bModRM = this.getIPByte(); + var reg = (bModRM & 0x38) >> 3; + switch(reg) { + case 0x4: + temp = this.regEAX; + break; + case 0x5: + temp = this.regECX; + break; + case 0x6: + temp = this.regEDX; + break; + case 0x7: + temp = this.regEBX; + break; + } + this.aOpModRegByte[bModRM].call(this, X86.fnMOVX); + switch(reg) { + case 0x0: + this.regEAX = (this.regEAX & ~this.dataMask) | (this.regEAX & 0xff); + break; + case 0x1: + this.regECX = (this.regECX & ~this.dataMask) | (this.regECX & 0xff); + break; + case 0x2: + this.regEDX = (this.regEDX & ~this.dataMask) | (this.regEDX & 0xff); + break; + case 0x3: + this.regEBX = (this.regEBX & ~this.dataMask) | (this.regEBX & 0xff); + break; + case 0x4: + this.regESP = (this.regESP & ~this.dataMask) | ((this.regEAX >> 8) & 0xff); + this.regEAX = temp; + break; + case 0x5: + this.regEBP = (this.regEBP & ~this.dataMask) | ((this.regECX >> 8) & 0xff); + this.regECX = temp; + break; + case 0x6: + this.regESI = (this.regESI & ~this.dataMask) | ((this.regEDX >> 8) & 0xff); + this.regEDX = temp; + break; + case 0x7: + this.regEDI = (this.regEDI & ~this.dataMask) | ((this.regEBX >> 8) & 0xff); + this.regEBX = temp; + break; + } + this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? X86CPU.CYCLES_80386.nOpCyclesMovXR : X86CPU.CYCLES_80386.nOpCyclesMovXM); +}; + +/** + * opMOVZXw() + * + * op=0x0F,0xB7 (MOVZX reg,word) + * + * @this {X86CPU} + */ +X86.opMOVZXw = function MOVZXw() +{ + var bModRM = this.getIPByte(); + this.aOpModRegWord[bModRM].call(this, X86.fnMOVX); + switch((bModRM & 0x38) >> 3) { + case 0x0: + this.regEAX = (this.regEAX & 0xffff); + break; + case 0x1: + this.regECX = (this.regECX & 0xffff); + break; + case 0x2: + this.regEDX = (this.regEDX & 0xffff); + break; + case 0x3: + this.regEBX = (this.regEBX & 0xffff); + break; + case 0x4: + this.regESP = (this.regESP & 0xffff); + break; + case 0x5: + this.regEBP = (this.regEBP & 0xffff); + break; + case 0x6: + this.regESI = (this.regESI & 0xffff); + break; + case 0x7: + this.regEDI = (this.regEDI & 0xffff); + break; + } + this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? X86CPU.CYCLES_80386.nOpCyclesMovXR : X86CPU.CYCLES_80386.nOpCyclesMovXM); +}; + /** * op=0x0F,0xBA (GRP8 mem/reg) (80386 and up) * @@ -994,6 +1148,142 @@ X86.opBTC = function BTC() if (this.regEA !== X86.ADDR_INVALID) this.nStepCycles -= X86CPU.CYCLES_80386.nOpCyclesBitSetMExtra; }; +/** + * opBSF() + * + * op=0x0F,0xBB (BSF reg,mem/reg) + * + * @this {X86CPU} + */ +X86.opBSF = function BSF() +{ + this.aOpModRegWord[this.getIPByte()].call(this, X86.fnBSF); +}; + +/** + * opBSR() + * + * op=0x0F,0xBC (BSR reg,mem/reg) + * + * @this {X86CPU} + */ +X86.opBSR = function BSR() +{ + this.aOpModRegWord[this.getIPByte()].call(this, X86.fnBSR); +}; + +/** + * opMOVSXb() + * + * op=0x0F,0xBE (MOVSX reg,byte) + * + * @this {X86CPU} + */ +X86.opMOVSXb = function MOVSXb() +{ + /* + * The ModRegByte handlers update the registers in the 1st column, but we need to update those in the 2nd column. + * + * 000: AL -> 000: AX + * 001: CL -> 001: CX + * 010: DL -> 010: DX + * 011: BL -> 011: BX + * 100: AH -> 100: SP + * 101: CH -> 101: BP + * 110: DH -> 110: SI + * 111: BH -> 111: DI + */ + var temp; + var bModRM = this.getIPByte(); + var reg = (bModRM & 0x38) >> 3; + switch(reg) { + case 0x4: + temp = this.regEAX; + break; + case 0x5: + temp = this.regECX; + break; + case 0x6: + temp = this.regEDX; + break; + case 0x7: + temp = this.regEBX; + break; + } + this.aOpModRegByte[bModRM].call(this, X86.fnMOVX); + switch(reg) { + case 0x0: + this.regEAX = (this.regEAX & ~this.dataMask) | ((((this.regEAX & 0xff) << 24) >> 24) & this.dataMask); + break; + case 0x1: + this.regECX = (this.regECX & ~this.dataMask) | ((((this.regECX & 0xff) << 24) >> 24) & this.dataMask); + break; + case 0x2: + this.regEDX = (this.regEDX & ~this.dataMask) | ((((this.regEDX & 0xff) << 24) >> 24) & this.dataMask); + break; + case 0x3: + this.regEBX = (this.regEBX & ~this.dataMask) | ((((this.regEBX & 0xff) << 24) >> 24) & this.dataMask); + break; + case 0x4: + this.regESP = (this.regESP & ~this.dataMask) | (((this.regEAX << 16) >> 24) & this.dataMask); + this.regEAX = temp; + break; + case 0x5: + this.regEBP = (this.regEBP & ~this.dataMask) | (((this.regECX << 16) >> 24) & this.dataMask); + this.regECX = temp; + break; + case 0x6: + this.regESI = (this.regESI & ~this.dataMask) | (((this.regEDX << 16) >> 24) & this.dataMask); + this.regEDX = temp; + break; + case 0x7: + this.regEDI = (this.regEDI & ~this.dataMask) | (((this.regEBX << 16) >> 24) & this.dataMask); + this.regEBX = temp; + break; + } + this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? X86CPU.CYCLES_80386.nOpCyclesMovXR : X86CPU.CYCLES_80386.nOpCyclesMovXM); +}; + +/** + * opMOVSXw() + * + * op=0x0F,0xBF (MOVSX reg,word) + * + * @this {X86CPU} + */ +X86.opMOVSXw = function MOVSXw() +{ + var bModRM = this.getIPByte(); + this.aOpModRegWord[bModRM].call(this, X86.fnMOVX); + switch((bModRM & 0x38) >> 3) { + case 0x0: + this.regEAX = ((this.regEAX << 16) >> 16); + break; + case 0x1: + this.regECX = ((this.regECX << 16) >> 16); + break; + case 0x2: + this.regEDX = ((this.regEDX << 16) >> 16); + break; + case 0x3: + this.regEBX = ((this.regEBX << 16) >> 16); + break; + case 0x4: + this.regESP = ((this.regESP << 16) >> 16); + break; + case 0x5: + this.regEBP = ((this.regEBP << 16) >> 16); + break; + case 0x6: + this.regESI = ((this.regESI << 16) >> 16); + break; + case 0x7: + this.regEDI = ((this.regEDI << 16) >> 16); + break; + } + this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? X86CPU.CYCLES_80386.nOpCyclesMovXR : X86CPU.CYCLES_80386.nOpCyclesMovXM); +}; + X86.aOps0F = new Array(256); X86.aOps0F[0x00] = X86.opGRP6; @@ -1009,10 +1299,6 @@ X86.aOps0F[0x06] = X86.opCLTS; */ X86.aOps0F[0x0B] = X86.opInvalid; -for (var i = 0; i < X86.aOps0F.length; i++) { - if (!X86.aOps0F[i]) X86.aOps0F[i] = X86.opUndefined; -} - if (I386) { X86.aOps0F386 = []; X86.aOps0F386[0x20] = X86.opMOVrcr; @@ -1060,9 +1346,17 @@ if (I386) { X86.aOps0F386[0xAC] = X86.opSHRDn; X86.aOps0F386[0xAD] = X86.opSHRDcl; X86.aOps0F386[0xAF] = X86.opIMUL; + X86.aOps0F386[0xB2] = X86.opLSS; X86.aOps0F386[0xB3] = X86.opBTR; + X86.aOps0F386[0xB4] = X86.opLFS; + X86.aOps0F386[0xB5] = X86.opLGS; + X86.aOps0F386[0xB6] = X86.opMOVZXb; + X86.aOps0F386[0xB7] = X86.opMOVZXw; X86.aOps0F386[0xBA] = X86.opGRP8; X86.aOps0F386[0xBB] = X86.opBTC; + X86.aOps0F386[0xBC] = X86.opBSF; + X86.aOps0F386[0xBE] = X86.opMOVSXb; + X86.aOps0F386[0xBF] = X86.opMOVSXw; } /* diff --git a/modules/pcjs/lib/x86opxx.js b/modules/pcjs/lib/x86opxx.js index 4a044844a..097b94403 100644 --- a/modules/pcjs/lib/x86opxx.js +++ b/modules/pcjs/lib/x86opxx.js @@ -3318,26 +3318,24 @@ X86.opRET = function RET() /** * op=0xC4 (LES reg,word) * + * This is like a "MOV reg,rm" operation, but it also loads ES from the next word. + * * @this {X86CPU} */ X86.opLES = function LES() { - /* - * This is like a "MOV reg,rm" operation, but it also loads ES from the next word. - */ this.aOpModRegWord[this.getIPByte()].call(this, X86.fnLES); }; /** * op=0xC5 (LDS reg,word) * + * This is like a "MOV reg,rm" operation, but it also loads DS from the next word. + * * @this {X86CPU} */ X86.opLDS = function LDS() { - /* - * This is like a "MOV reg,rm" operation, but it also loads DS from the next word. - */ this.aOpModRegWord[this.getIPByte()].call(this, X86.fnLDS); };