8087 FPU opcodes are more or less complete now, but much testing remains (including exception handling)

This commit is contained in:
Jeff Parsons 2015-11-30 20:03:47 -08:00
commit 11fe7aa9b8

View file

@ -111,7 +111,7 @@ function X86FPU(parmsFPU)
/*
* Used for "long-real" (LR) 64-bit floating-point operations. We also use intTmpLR as temporary storage
* for all "word-integer" (WI or INT16), "short-integer" (SI or INT32) and "long-integer" (LI or INT64) values,
* since it's just large enough to accommodate all three sizes of integers.
* since it's just large enough to accommodate all three integer sizes.
*/
this.regTmpLR = new Float64Array(1);
this.intTmpLR = new Int32Array(this.regTmpLR.buffer);
@ -205,7 +205,7 @@ X86FPU.F2XM1 = function()
X86FPU.FABS = function()
{
/*
* NOTE: This could be implemented more efficiently by simply clearing the sign bit of ST(0).
* TODO: This could be implemented more efficiently by simply clearing the sign bit of ST(0).
*/
this.setST(0, Math.abs(this.getST(0)));
};
@ -269,7 +269,14 @@ X86FPU.FADDPsti = function()
*/
X86FPU.FBLDpd = function()
{
this.opUnimplemented();
var a = this.getTRFromEA();
/*
* a[0] contains the 8 least-significant BCD digits, a[1] contains the next 8, and a[2] contains
* the next 2 (bit 15 of a[2] is the sign bit, and bits 8-14 of a[2] are unused).
*/
var v = this.decodeBCD(a[0], 8) + this.decodeBCD(a[1], 8) * 100000000 + this.decodeBCD(a[2], 2) * 10000000000000000;
if (a[2] & 0x8000) v = -v;
this.pushValue(v);
};
/**
@ -279,7 +286,19 @@ X86FPU.FBLDpd = function()
*/
X86FPU.FBSTPpd = function()
{
this.opUnimplemented();
var v = this.roundInteger(this.popValue());
if (v != null) {
/*
* intTmpTR[0] will contain the 8 least-significant BCD digits, intTmpTR[1] will contain the next 8,
* and intTmpTR[2] will contain the next 2 (bit 15 of intTmpTR[2] will be the sign bit, and bits 8-14 of
* intTmpTR[2] will be unused).
*/
this.intTmpTR[0] = this.encodeBCD(v, 8);
this.intTmpTR[1] = this.encodeBCD(v / 100000000, 8);
this.intTmpTR[2] = this.encodeBCD(v / 10000000000000000, 2);
if (v < 0) this.intTmpTR[2] |= 0x8000;
this.setEAFromTR();
}
};
/**
@ -290,7 +309,7 @@ X86FPU.FBSTPpd = function()
X86FPU.FCHS = function()
{
/*
* NOTE: This could be implemented more efficiently by simply inverting the sign bit of ST(0).
* TODO: This could be implemented more efficiently by simply inverting the sign bit of ST(0).
*/
this.setST(0, -this.getST(0));
};
@ -402,8 +421,8 @@ X86FPU.FCOMPst = function()
/**
* FCOMP8087()
*
* NOTE: This is used with encodings (0xDC,0xD8-0xDF and 0xDE,0xD0-0xD7) that were valid for the 8087 and 80287
* but may no longer be valid as of the 80387.
* NOTE: This is used with encodings (0xDC,0xD8-0xDF and 0xDE,0xD0-0xD7) that were valid for the 8087
* and 80287 but may no longer be valid as of the 80387.
*
* TODO: Determine if this form subtracted the operands in the same order, or if it requires an FCOMPsti(),
* which, like the other *sti() functions, uses ST(0) as the second operand rather than the first.
@ -606,7 +625,7 @@ X86FPU.FFREEP8087 = function()
*/
X86FPU.FIADD16 = function()
{
this.setST(0, this.getST(0) + this.getWIFromEA());
this.setST(0, this.doAdd(this.getST(0), this.getWIFromEA()));
};
/**
@ -616,7 +635,7 @@ X86FPU.FIADD16 = function()
*/
X86FPU.FIADD32 = function()
{
this.setST(0, this.getST(0) + this.getSIFromEA());
this.setST(0, this.doAdd(this.getST(0), this.getSIFromEA()));
};
/**
@ -736,7 +755,7 @@ X86FPU.FILD64 = function()
*/
X86FPU.FIMUL16 = function()
{
this.opUnimplemented();
this.setST(0, this.doMultiply(this.getST(0), this.getWIFromEA()));
};
/**
@ -746,7 +765,7 @@ X86FPU.FIMUL16 = function()
*/
X86FPU.FIMUL32 = function()
{
this.opUnimplemented();
this.setST(0, this.doMultiply(this.getST(0), this.getSIFromEA()));
};
/**
@ -777,7 +796,7 @@ X86FPU.FINIT = function()
*/
X86FPU.FIST16 = function()
{
this.opUnimplemented();
if (this.getWI(0)) this.setEAFromWI();
};
/**
@ -787,7 +806,7 @@ X86FPU.FIST16 = function()
*/
X86FPU.FIST32 = function()
{
this.opUnimplemented();
if (this.getSI(0)) this.setEAFromSI();
};
/**
@ -797,7 +816,10 @@ X86FPU.FIST32 = function()
*/
X86FPU.FISTP16 = function()
{
this.opUnimplemented();
if (this.getWI(0)) {
this.setEAFromWI();
this.popValue();
}
};
/**
@ -820,7 +842,10 @@ X86FPU.FISTP32 = function()
*/
X86FPU.FISTP64 = function()
{
this.opUnimplemented();
if (this.getLI(0)) {
this.setEAFromLI();
this.popValue();
}
};
/**
@ -830,7 +855,7 @@ X86FPU.FISTP64 = function()
*/
X86FPU.FISUB16 = function()
{
this.opUnimplemented();
this.setST(0, this.doSubtract(this.getST(0), this.getWIFromEA()));
};
/**
@ -840,7 +865,7 @@ X86FPU.FISUB16 = function()
*/
X86FPU.FISUB32 = function()
{
this.opUnimplemented();
this.setST(0, this.doSubtract(this.getST(0), this.getSIFromEA()));
};
/**
@ -850,7 +875,7 @@ X86FPU.FISUB32 = function()
*/
X86FPU.FISUBR16 = function()
{
this.opUnimplemented();
this.setST(0, this.doSubtract(this.getWIFromEA(), this.getST(0)));
};
/**
@ -860,7 +885,7 @@ X86FPU.FISUBR16 = function()
*/
X86FPU.FISUBR32 = function()
{
this.opUnimplemented();
this.setST(0, this.doSubtract(this.getSIFromEA(), this.getST(0)));
};
/**
@ -876,7 +901,7 @@ X86FPU.FISUBR32 = function()
* the register is taken before TOP is changed. The source operand may also be a short real, long real,
* or temporary real memory operand. Short real and long real operands are converted automatically.
*
* Note that coding the instruction FLD ST duplicates the value at the stack top.
* Note that coding the instruction FLD ST(0) duplicates the value at the stack top.
*
* On the 8087 and 80287, the FLD real80 instruction will raise the denormal exception if the memory
* operand is a denormal. The 80287XL and later coprocessors will not, since the operation is not arithmetic.
@ -913,8 +938,7 @@ X86FPU.FLDsr = function()
*/
X86FPU.FLDsti = function()
{
var v = this.getST(this.iStack);
if (v != null) this.pushValue(v);
this.pushValue(this.getST(this.iStack));
};
/**
@ -1083,31 +1107,120 @@ X86FPU.FNOP = function()
/**
* FPATAN()
*
* FPATAN calculates the partial arctangent of ST(0) divided by ST(1):
*
* ST(1) = tan^-1( ST(1) / ST(0) )
*
* On the 8087 and 80287, the arguments must satisfy the inequality 0 < ST(1) < ST(0) < +infinity.
* On the 80287XL and later coprocessors, the range of the operands is unrestricted. The result is
* returned to ST(1), and the stack is popped, destroying both operands and leaving the result in ST(0).
*
* @this {X86FPU}
*/
X86FPU.FPATAN = function()
{
this.opUnimplemented();
if (this.setST(1, Math.atan2(this.getST(1), this.getST(0)))) this.popValue();
};
/**
* FPTAN()
*
* FPTAN calculates the partial tangent of ST(0):
*
* y / x = tan( ST(0) )
*
* The result of the operation is a ratio. y replaces the argument on the stack, and x is pushed onto the stack,
* where it becomes the new ST(0).
*
* On the 8087 and 80287, the FPTAN function assumes that its argument is valid and in-range. No argument checking
* is performed. The value of ST(0) must satisfy the inequality -pi/4 <= ST(0) <= pi/4. In the case of an invalid
* argument, the result is undefined and no error is signaled.
*
* On the 80287XL and later coprocessors, if value of ST(0) satisfies the condition -2^63 < ST(0) < 2^63, it will
* automatically be reduced to within range. If the operand is outside this range, however, C2 is set to 1 to indicate
* that the function is incomplete, and ST(0) is left unchanged.
*
* The 80287XL, 80387, and 80486 always push a value of +1.0 for x. The value of x pushed by the 8087 and 80287 may be
* any real number. In either case, the ratio is the same. The cotangent can be calculated by executing FDIVR immediately
* after FPTAN. The following code will leave the 8087 and 80287 in the same state as the later coprocessors:
*
* FDIV
* FLD1
*
* ST(7) must be empty before this instruction is executed to avoid an invalid operation exception. If the invalid
* operation exception is masked, the 8087 and 80287 leave the original operand unchanged, but push it to ST(1). On the
* 80287XL and later coprocessors, both ST(0) and ST(1) will contain quiet NaNs. On the 80287XL and later coprocessors,
* if condition code bit C2 is 0 and the precision exception is raised, then C1=1 if the last bit was rounded up. C1 is
* undefined for the 8087 and 80287.
*
* @this {X86FPU}
*/
X86FPU.FPTAN = function()
{
this.opUnimplemented();
if (this.setST(0, Math.tan(this.getST(0)))) this.pushValue(1.0);
};
/**
* FPREM()
*
* FPREM performs modulo division of ST(0) by ST(1) and returns the result to ST(0).
*
* The FPREM instruction is used to reduce the real operand in ST(0) to a value whose magnitude is less than the
* magnitude of ST(1). FPREM produces an exact result, so the precision exception is never raised and the rounding
* control has no effect. The sign of the remainder is the same as the sign of the original operand.
*
* The remaindering operation is performed by iterative scaled subtractions and can reduce the exponent of ST(0) by
* no more than 63 in one execution. If the remainder is less than ST(1) (the modulus), the function is complete and
* C2 in the status word is cleared.
*
* If the modulo function is incomplete, C2 is set to 1, and the result in ST(0) is termed the partial remainder.
* C2 can be inspected by storing the status word and re-executing the instruction until C2 is clear. Alternately,
* ST(0) can be compared to ST(1). If ST(0) > ST(1), then FPREM must be executed again. If ST(0) = ST(1), then the
* remainder is 0.
*
* FPREM is important for reducing arguments to the periodic transcendental functions such as FPTAN. Because FPREM
* produces an exact result, no round-off error is introduced into the calculation.
*
* When reduction is complete, the three least-significant bits of the quotient are stored in the condition code bits
* C3, C1, and C0, respectively. When arguments to the tangent function are reduced by pi/4, this result can be used
* to identify the octant that contained the original angle.
*
* The FPREM function operates differently than specified by the IEEE 754 standard when rounding the quotient to form
* a partial remainder (see the algorithm). The FPREM1 function (80287XL and up) is provided for compatibility with
* that standard.
*
* The FPREM instruction can also be used to normalize ST(0). If ST(0) is unnormal and ST(1) is greater than ST(0),
* FPREM will normalize ST(0). On the 8087 and 80287, operation on a denormal operand raises the invalid operation
* exception. Underflow is not possible. On the 80287XL and later coprocessors, operation on a denormal is supported
* and an underflow exception can occur.
*
* ALGORITHM:
*
* t = EXPONENT(ST) - EXPONENT(ST(1))
* IF (t < 64) THEN
* q = R0UND(ST(0) / ST(1), CHOP)
* ST(0) = ST(0) - (ST(1) * q)
* C2 = 0
* C0 = BIT 2 of q
* C1 = BIT 1 of q
* C3 = BIT 0 of q
* ELSE
* n = a number between 32 and 63
* q = ROUND((ST(0) / ST(1)) / 2^(t-n), CHOP)
* ST(0) = ST(0) - (ST(1) * q * 2^(t-n))
* C2 = 1
* ENDIF
*
* TODO: Determine the extent to which the JavaScript MOD operator differs from the above algorithm.
*
* ERRATA: On the 8087 and 80287, the condition code bits C3, C1, and C0 are incorrect when performing a reduction of
* 64^n + m, where n >= 1, and m=1 or m=2. A bug fix should be implemented in software.
*
* @this {X86FPU}
*/
X86FPU.FPREM = function()
{
this.opUnimplemented();
this.setST(0, this.getST(0) % this.getST(1));
};
/**
@ -1136,7 +1249,7 @@ X86FPU.FRSTOR = function()
*/
X86FPU.FRNDINT = function()
{
this.opUnimplemented();
this.setST(0, this.roundInteger(this.getST(0), X86FPU.MAX_INT64));
};
/**
@ -1161,11 +1274,26 @@ X86FPU.FSAVE = function()
/**
* FSCALE()
*
* FSCALE interprets the value in ST(1) as an integer and adds this number to the exponent of the number in ST(0).
*
* The FSCALE instruction provides a means of quickly performing multiplication or division by powers of two.
* This operation is often required when scaling array indexes.
*
* On the 8087 and 80287, FSCALE assumes that the scale factor in ST(1) is an integer that satisfies the inequality
* -2^15 <= ST(1) < +2^15. If ST(1) is not an integer value, the value is chopped to the next smallest integer in
* magnitude (chopped toward zero). If the value is out of range or 0 < ST(1) < 1, FSCALE produces an undefined
* result and doesn't signal an exception. Typically, the value in ST(0) is unchanged but should not be depended on.
*
* On the 80287XL and later coprocessors, there is no limit on the range of the scale factor in ST(1). The value in
* ST(1) is still chopped toward zero. If ST(1) is 0, ST(0) is unchanged.
*
* @this {X86FPU}
*/
X86FPU.FSCALE = function()
{
this.opUnimplemented();
var x = this.getST(0);
var y = this.getST(1);
if (x != null && y != null) this.setST(0, x * Math.pow(2, this.truncateValue(y)));
};
/**
@ -1209,9 +1337,7 @@ X86FPU.FSQRT = function()
*/
X86FPU.FSTlr = function()
{
if (this.getLR(0)) {
this.setEAFromLR();
}
if (this.getLR(0)) this.setEAFromLR();
};
/**
@ -1221,9 +1347,7 @@ X86FPU.FSTlr = function()
*/
X86FPU.FSTsr = function()
{
if (this.getSR(0)) {
this.setEAFromSR();
}
if (this.getSR(0)) this.setEAFromSR();
};
/**
@ -1464,7 +1588,7 @@ X86FPU.FSUBRPsti = function()
*/
X86FPU.FTST = function()
{
this.opUnimplemented();
this.doCompare(this.getST(0), 0);
};
/**
@ -1528,31 +1652,91 @@ X86FPU.FXCH8087 = function()
/**
* FXTRACT()
*
* FXTRACT splits the value encoded in ST(0) into two separate numbers representing the actual value of the
* fraction (mantissa) and exponent fields.
*
* The FXTRACT instruction is used to decompose the two fields of the temporary real number in ST(0). The exponent
* replaces the value in ST(0), then the fraction is pushed onto the stack. When execution is complete, ST(0)
* contains the original fraction, expressed as a real number with a true exponent of 0 (0x3FFF in biased form),
* and ST(1) contains the value of the original operand's true (unbiased) exponent expressed as a real number.
*
* If ST(0) is 0, the 8087 and 80287 will leave zeros in both ST(0) and ST(1); both zeros will have the same sign as
* the original operand. If ST(0) is +infinity, the invalid operation exception is raised.
*
* On the 80287XL and later coprocessors, if ST(0) is 0, the zero-divide exception is reported and ST(1) is set to
* -infinity. If ST(0) is +infinity, no exception is reported.
*
* The FXTRACT instruction may be thought of as the complement to the FSCALE instruction, which combines a separate
* fraction and exponent into a single value.
*
* ALGORITHM:
*
* IF (ST(0) = 0) THEN
* DEC TOP
* ST(0) = ST(1)
* ELSE
* temp = ST(0)
* ST(0) = EXPONENT(ST(0)) ; stored as true exponent
* DEC TOP
* ST(0) = FRACTION(ST(0))
* ENDIF
*
* @this {X86FPU}
*/
X86FPU.FXTRACT = function()
{
this.opUnimplemented();
var v = this.getST(0);
if (v != null) {
this.regTmpLR[0] = v;
this.setST(0, ((this.intTmpLR[1] >> 20) & 0x7ff) - 0x3ff);
this.intTmpLR[1] = (this.intTmpLR[1] | 0x3ff00000) & ~0x40000000;
this.pushValue(this.regTmpLR[0]);
}
};
/**
* FYL2X()
*
* FYL2X (y log base 2 of x) calculates:
*
* ST(1) = ST(1) * log2(ST(0))
*
* The operands must satisfy the inequalities 0 < ST(0) < +infinity and -infinity < ST(1) < +infinity. FYL2X pops
* the stack and returns the result to the new ST(0). Both original operands are destroyed.
*
* The FYL2X function is designed to optimize the calculation of a log to a base, n, other than two. In such a case,
* the following multiplication is required; ie:
*
* logn(x) = logn(2) * log2(x)
*
* @this {X86FPU}
*/
X86FPU.FYL2X = function()
{
this.opUnimplemented();
if (this.setST(1, this.getST(1) * Math.log(this.getST(0)) / Math.LN2)) this.popValue();
};
/**
* FYL2XP1()
*
* FYL2XP1 (y log base 2 of x plus 1) calculates:
*
* ST(1) = ST(1) * log2(ST(0) + 1)
*
* The operands must satisfy the inequalities -(1-sqrt(2)/2) < ST(0) < (1-sqrt(2)/2) and -infinity < ST(1) < +infinity.
* FYL2XP1 pops the stack and returns the result to the new ST(0). Both original operands are destroyed.
*
* The FYL2XP1 function provides greater accuracy than FYL2X in computing the log of a number that is very close to 1.
*
* FYL2XP1 is typically used when computing compound interest, for example, which requires the calculation of a logarithm
* of 1.0 + n where 0 < n < 0.29. If 1.0 was added to n, significant digits might be lost. By using FYL2XP1, the result
* will be as accurate as n to within three units of temporary real precision.
*
* @this {X86FPU}
*/
X86FPU.FYL2XP1 = function()
{
this.opUnimplemented();
if (this.setST(1, this.getST(1) * Math.log(this.getST(0) + 1.0) / Math.LN2)) this.popValue();
};
/**
@ -2047,16 +2231,16 @@ X86FPU.prototype.doDivide = function(dividend, divisor)
X86FPU.prototype.doCompare = function(operand1, operand2)
{
if (operand1 != null && operand2 != null) {
var cc = X86.FPU.STATUS.C0 | X86.FPU.STATUS.C2 | X86.FPU.STATUS.C3;
var cc = 0; // default value used when result > 0
if (!isNaN(operand1) && !isNaN(operand2)) {
var result = operand1 - operand2;
if (result > 0) {
cc = 0;
} else if (result < 0) {
if (result < 0) {
cc = X86.FPU.STATUS.C0;
} else {
} else if (result === 0) {
cc = X86.FPU.STATUS.C3;
}
} else {
cc = X86.FPU.STATUS.C0 | X86.FPU.STATUS.C2 | X86.FPU.STATUS.C3;
}
this.regStatus = (this.regStatus & ~X86.FPU.STATUS.CC) | cc;
return true;
@ -2091,13 +2275,14 @@ X86FPU.prototype.doSquareRoot = function(operand)
*
* @this {X86FPU}
* @param {number|null} operand
* @param {number} max (ie, 0x8000, 0x80000000, or 0x8000000000000000)
* @return {boolean} true if intTmpLR was loaded, false if not
* @param {number} [max] (ie, 0x8000, 0x80000000, or 0x8000000000000000)
* @return {number|null} (rounded result if intTmpLR was loaded, null if not)
*/
X86FPU.prototype.roundInteger = function(operand, max)
{
var result;
var rc = (this.regControl & X86.FPU.CONTROL.RC);
if (operand == null) return null;
var rc = (this.regControl & X86.FPU.CONTROL.RC), result;
if (rc == X86.FPU.CONTROL.RC_NEAR) {
result = Math.round(operand);
@ -2110,22 +2295,34 @@ X86FPU.prototype.roundInteger = function(operand, max)
result = Math.ceil(operand);
}
if (result >= max) {
if (this.setException(X86.FPU.STATUS.OE)) return false;
result = max - 1;
}
else if (result < -max) {
if (this.setException(X86.FPU.STATUS.UE)) return false;
result = -max;
if (max) {
if (result >= max) {
if (this.setException(X86.FPU.STATUS.IE)) return null;
result = -max; // apparently, the masked response is to return the most negative integer (not max - 1)
}
else if (result < -max) {
if (this.setException(X86.FPU.STATUS.IE)) return null;
result = -max;
}
this.intTmpLR[0] = result|0;
if (max > X86FPU.MAX_INT32) {
this.intTmpLR[1] = (result / 0x100000000)|0;
if (!this.intTmpLR[1] && result < 0) this.intTmpLR[1] = -1;
}
}
return result;
};
this.intTmpLR[0] = result|0;
if (max > X86FPU.MAX_INT32) {
this.intTmpLR[1] = (result / 0x100000000)|0;
if (!this.intTmpLR[1] && result < 0) this.intTmpLR[1] = -1;
}
return true;
/**
* truncateValue(v)
*
* @this {X86FPU}
* @param {number} v
* @return {number}
*/
X86FPU.prototype.truncateValue = function(v)
{
return v > 0? Math.floor(v) : Math.ceil(v);
};
/**
@ -2202,10 +2399,24 @@ X86FPU.prototype.setTags = function(n)
}
};
/**
* getWI(i)
*
* Gets a "word-integer" (WI aka INT16) from ST(i)
*
* @this {X86FPU}
* @param {number} i (eg, 0 for top-of-stack)
* @return {boolean} true if intTmpLR was loaded, false if not
*/
X86FPU.prototype.getWI = function(i)
{
return this.roundInteger(this.getST(i), X86FPU.MAX_INT16) != null;
};
/**
* getSI(i)
*
* Gets a "short-integer" (SI aka INT32)
* Gets a "short-integer" (SI aka INT32) from ST(i)
*
* @this {X86FPU}
* @param {number} i (eg, 0 for top-of-stack)
@ -2213,8 +2424,21 @@ X86FPU.prototype.setTags = function(n)
*/
X86FPU.prototype.getSI = function(i)
{
var v = this.getST(i);
return v != null && this.roundInteger(v, X86FPU.MAX_INT32);
return this.roundInteger(this.getST(i), X86FPU.MAX_INT32) != null;
};
/**
* getLI(i)
*
* Gets a "long-integer" (LI aka INT64) from ST(i)
*
* @this {X86FPU}
* @param {number} i (eg, 0 for top-of-stack)
* @return {boolean} true if intTmpLR was loaded, false if not
*/
X86FPU.prototype.getLI = function(i)
{
return this.roundInteger(this.getST(i), X86FPU.MAX_INT64) != null;
};
/**
@ -2343,9 +2567,7 @@ X86FPU.prototype.getTR = function(i, fSafe)
*/
X86FPU.prototype.setTR = function(i, a)
{
if (a) {
this.setST(i, this.getLRFromTR(a));
}
if (a) this.setST(i, this.getLRFromTR(a));
};
/**
@ -2487,11 +2709,7 @@ X86FPU.prototype.setEAFromLI = function()
*
* @this {X86FPU}
*/
X86FPU.prototype.setEAFromSR = function()
{
this.assert(this.cpu.regEA !== X86.ADDR_INVALID);
this.cpu.setLong(this.cpu.regEA, this.intTmpSR[0]);
};
X86FPU.prototype.setEAFromSR = X86FPU.prototype.setEAFromSI;
/**
* setEAFromLR()
@ -2500,12 +2718,7 @@ X86FPU.prototype.setEAFromSR = function()
*
* @this {X86FPU}
*/
X86FPU.prototype.setEAFromLR = function()
{
this.assert(this.cpu.regEA !== X86.ADDR_INVALID);
this.cpu.setLong(this.cpu.regEA, this.intTmpLR[0]);
this.cpu.setLong(this.cpu.regEA + 4, this.intTmpLR[1]);
};
X86FPU.prototype.setEAFromLR = X86FPU.prototype.setEAFromLI;
/**
* setEAFromTR()
@ -2527,7 +2740,7 @@ X86FPU.prototype.setEAFromTR = function()
*
* Since we must use the "long-real" (64-bit) format internally, rather than the "temp-real" (80-bit) format,
* this function converts a 64-bit value to an 80-bit value. The major differences: 1) the former uses a 52-bit
* fraction and 11-bit exponent, while the latter uses a 64-bit fraction and 15-bit exponent, 2) the former
* fraction and 11-bit exponent, while the latter uses a 64-bit fraction and 15-bit exponent; 2) the former
* does NOT store a leading 1 with the fraction, whereas the latter does.
*
* @this {X86FPU}
@ -2573,7 +2786,7 @@ X86FPU.prototype.getLRFromTR = function(a)
*
* Since we must use the "long-real" (64-bit) format internally, rather than the "temp-real" (80-bit) format,
* this function converts a 64-bit value to an 80-bit value. The major differences: 1) the former uses a 52-bit
* fraction and 11-bit exponent, while the latter uses a 64-bit fraction and 15-bit exponent, 2) the former
* fraction and 11-bit exponent, while the latter uses a 64-bit fraction and 15-bit exponent; 2) the former
* does NOT store a leading 1 with the fraction, whereas the latter does.
*
* @this {X86FPU}
@ -2619,6 +2832,48 @@ X86FPU.prototype.getTRFromLR = function(loLR, hiLR)
return this.intTmpTR;
};
/**
* decodeBCD()
*
* @this {X86FPU}
* @param {number} i (32-bit integer containing n BCD digits)
* @param {number} n (number of BCD digits to decode)
* @return {number} (binary value representing the specified number of BCD digits)
*/
X86FPU.prototype.decodeBCD = function(i, n)
{
var v = 0, m = 1;
this.assert(n > 0 && n <= 8);
while (n--) {
var d = i & 0xf;
this.assert(d <= 9);
v += d * m;
m *= 10;
i >>= 4;
}
return v;
};
/**
* encodeBCD()
*
* @this {X86FPU}
* @param {number} v (binary value from which to extract n BCD digits)
* @param {number} n (number of BCD digits to extract)
* @return {number} (integer containing the requested number of BCD digits)
*/
X86FPU.prototype.encodeBCD = function(v, n)
{
var i = 0, s = 0;
this.assert(n > 0 && n <= 8);
while (n--) {
i |= (v % 10) << s;
v /= 10;
s += 4;
}
return i;
};
/**
* popValue()
*
@ -2643,10 +2898,11 @@ X86FPU.prototype.popValue = function()
* pushValue(v)
*
* @this {X86FPU}
* @param {number} v
* @param {number|null} v
*/
X86FPU.prototype.pushValue = function(v)
{
if (v == null) return;
var iReg = (this.iST - 1) & 7;
var bitUsed = (1 << iReg);
if (this.regUsed & bitUsed) {
@ -2847,7 +3103,7 @@ if (DEBUGGER) {
*
* The second lookup value corresponds to bits in the ModRegRM byte that follows the ESC byte (0xD8-0xDF).
*
* Here's a little cheat-sheet for how the lookup values relate to ModRegRM values; see opFPU() for details.
* Here's a little cheat-sheet for how the 2nd lookup values relate to ModRegRM values; see opFPU() for details.
*
* Lookup ModRegRM value(s)
* ------ -------------------------------
@ -2869,8 +3125,8 @@ if (DEBUGGER) {
* 0x37: 0xF8-0xFF
*
* ESC bytes 0xD9 and 0xDB use the RM field to further describe the operation when the ModRegRM value >= 0xE0.
* In those cases, we shift the Reg value into the high nibble and the RM value into the low nibble; you can think
* of those lookup values as hex-encoded octal.
* In those cases, we shift the Reg value into the high nibble and the RM value into the low nibble, resulting in
* the following lookup values (which look a lot like hex-encoded octal):
*
* 0x40: 0xE0
* 0x41: 0xE1