blob: 62293509e3efb2f6c1fd38203ef544a8d9f87d04 [file] [log] [blame] [raw]
"use strict";
/** @const */
var FPU_LOG_OP = false;
var
/** @const */
FPU_C0 = 0x100,
/** @const */
FPU_C1 = 0x200,
/** @const */
FPU_C2 = 0x400,
/** @const */
FPU_C3 = 0x4000,
/** @const */
FPU_RESULT_FLAGS = FPU_C0 | FPU_C1 | FPU_C2 | FPU_C3,
/** @const */
FPU_STACK_TOP = 0x3800;
var
// precision, round & infinity control
/** @const */
FPU_PC = 3 << 8,
/** @const */
FPU_RC = 3 << 10,
/** @const */
FPU_IF = 1 << 12;
// exception bits in the status word
var
/** @const */
FPU_EX_SF = 1 << 6,
/** @const */
FPU_EX_P = 1 << 5,
/** @const */
FPU_EX_U = 1 << 4,
/** @const */
FPU_EX_O = 1 << 3,
/** @const */
FPU_EX_Z = 1 << 2,
/** @const */
FPU_EX_D = 1 << 1,
/** @const */
FPU_EX_I = 1 << 0;
var
/** @const */
TWO_POW_63 = 0x8000000000000000;
/**
* @constructor
* @param {CPU} cpu
*/
function FPU(cpu)
{
// TODO:
// - Precision Control
// - QNaN, unordered comparison
// - Exceptions
this.cpu = cpu;
// Why no Float80Array :-(
this.st = new Float64Array(8);
// used for conversion
/** @const */ this.float32 = new Float32Array(1);
/** @const */ this.float32_byte = new Uint8Array(this.float32.buffer);
/** @const */ this.float32_int = new Int32Array(this.float32.buffer);
/** @const */ this.float64 = new Float64Array(1);
/** @const */ this.float64_byte = new Uint8Array(this.float64.buffer);
/** @const */ this.float64_int = new Int32Array(this.float64.buffer);
/** @const */ this.st8 = new Uint8Array(this.st.buffer);
/** @const */ this.st32 = new Int32Array(this.st.buffer);
// bitmap of which stack registers are empty
this.stack_empty = 0xff;
this.stack_ptr = 0;
this.control_word = 0x37F;
this.status_word = 0;
this.fpu_ip = 0;
this.fpu_ip_selector = 0;
this.fpu_opcode = 0;
this.fpu_dp = 0;
this.fpu_dp_selector = 0;
/** @const */
this.indefinite_nan = NaN;
/** @const */
this.constants = new Float64Array([
1, Math.log(10) / Math.LN2, Math.LOG2E, Math.PI,
Math.log(2) / Math.LN10, Math.LN2, 0
]);
}
FPU.prototype.get_state = function()
{
var state = [];
state[0] = this.st;
state[1] = this.stack_empty;
state[2] = this.stack_ptr;
state[3] = this.control_word;
state[4] = this.fpu_dp_selector;
state[5] = this.fpu_ip;
state[6] = this.fpu_ip_selector;
state[7] = this.fpu_dp;
state[8] = this.fpu_dp_selector;
state[9] = this.fpu_opcode;
return state;
};
FPU.prototype.set_state = function(state)
{
this.st.set(state[0]);
this.stack_empty = state[1];
this.stack_ptr = state[2];
this.control_word = state[3];
this.fpu_dp_selector = state[4];
this.fpu_ip = state[5];
this.fpu_ip_selector = state[6];
this.fpu_dp = state[7];
this.fpu_dp_selector = state[8];
this.fpu_opcode = state[9];
};
FPU.prototype.fpu_unimpl = function()
{
dbg_trace();
if(DEBUG) throw "fpu: unimplemented";
else this.cpu.trigger_ud();
}
FPU.prototype.stack_fault = function()
{
// TODO: Interrupt
this.status_word |= FPU_EX_SF | FPU_EX_I;
}
FPU.prototype.invalid_arithmatic = function()
{
this.status_word |= FPU_EX_I;
}
FPU.prototype.fcom = function(y)
{
var x = this.get_st0();
this.status_word &= ~FPU_RESULT_FLAGS;
if(x > y)
{
}
else if(y > x)
{
this.status_word |= FPU_C0;
}
else if(x === y)
{
this.status_word |= FPU_C3;
}
else
{
this.status_word |= FPU_C0 | FPU_C2 | FPU_C3;
}
}
FPU.prototype.fucom = function(y)
{
// TODO
this.fcom(y);
}
FPU.prototype.fcomi = function(y)
{
var x = this.st[this.stack_ptr];
this.cpu.flags_changed &= ~(1 | flag_parity | flag_zero);
this.cpu.flags &= ~(1 | flag_parity | flag_zero);
if(x > y)
{
}
else if(y > x)
{
this.cpu.flags |= 1;
}
else if(x === y)
{
this.cpu.flags |= flag_zero;
}
else
{
this.cpu.flags |= 1 | flag_parity | flag_zero;
}
}
FPU.prototype.fucomi = function(y)
{
// TODO
this.fcomi(y);
}
FPU.prototype.ftst = function(x)
{
this.status_word &= ~FPU_RESULT_FLAGS;
if(isNaN(x))
{
this.status_word |= FPU_C3 | FPU_C2 | FPU_C0;
}
else if(x === 0)
{
this.status_word |= FPU_C3;
}
else if(x < 0)
{
this.status_word |= FPU_C0;
}
// TODO: unordered (x is nan, etc)
}
FPU.prototype.fxam = function(x)
{
this.status_word &= ~FPU_RESULT_FLAGS;
this.status_word |= this.sign(0) << 9;
if(this.stack_empty >> this.stack_ptr & 1)
{
this.status_word |= FPU_C3 | FPU_C0;
}
else if(isNaN(x))
{
this.status_word |= FPU_C0;
}
else if(x === 0)
{
this.status_word |= FPU_C3;
}
else if(x === Infinity || x === -Infinity)
{
this.status_word |= FPU_C2 | FPU_C0;
}
else
{
this.status_word |= FPU_C2;
}
// TODO:
// Unsupported, Denormal
}
FPU.prototype.finit = function()
{
this.control_word = 0x37F;
this.status_word = 0;
this.fpu_ip = 0;
this.fpu_dp = 0;
this.fpu_opcode = 0;
this.stack_empty = 0xFF;
this.stack_ptr = 0;
}
FPU.prototype.load_status_word = function()
{
return this.status_word & ~(7 << 11) | this.stack_ptr << 11;
}
FPU.prototype.set_status_word = function(sw)
{
this.status_word = sw & ~(7 << 11);
this.stack_ptr = sw >> 11 & 7;
}
FPU.prototype.load_tag_word = function()
{
var tag_word = 0,
value;
for(var i = 0; i < 8; i++)
{
value = this.st[i];
if(this.stack_empty >> i & 1)
{
tag_word |= 3 << (i << 1);
}
else if(value === 0)
{
tag_word |= 1 << (i << 1);
}
else if(!isFinite(value))
{
tag_word |= 2 << (i << 1);
}
}
//dbg_log("load tw=" + h(tag_word) + " se=" + h(this.stack_empty) + " sp=" + this.stack_ptr, LOG_FPU);
return tag_word;
}
FPU.prototype.set_tag_word = function(tag_word)
{
this.stack_empty = 0;
for(var i = 0; i < 8; i++)
{
this.stack_empty |= (tag_word >> i) & (tag_word >> i + 1) & 1 << i;
}
//dbg_log("set_tag_word tw=" + h(tag_word) + " se=" + h(this.stack_empty), LOG_FPU);
}
FPU.prototype.fstenv = function(addr)
{
if(this.cpu.is_osize_32())
{
this.cpu.writable_or_pagefault(addr, 26);
this.cpu.safe_write16(addr, this.control_word);
this.cpu.safe_write16(addr + 4, this.load_status_word());
this.cpu.safe_write16(addr + 8, this.load_tag_word());
this.cpu.safe_write32(addr + 12, this.fpu_ip);
this.cpu.safe_write16(addr + 16, this.fpu_ip_selector);
this.cpu.safe_write16(addr + 18, this.fpu_opcode);
this.cpu.safe_write32(addr + 20, this.fpu_dp);
this.cpu.safe_write16(addr + 24, this.fpu_dp_selector);
}
else
{
this.fpu_unimpl();
}
}
FPU.prototype.fldenv = function(addr)
{
if(this.cpu.is_osize_32())
{
this.control_word = this.cpu.safe_read16(addr);
this.set_status_word(this.cpu.safe_read16(addr + 4));
this.set_tag_word(this.cpu.safe_read16(addr + 8));
this.fpu_ip = this.cpu.safe_read32s(addr + 12);
this.fpu_ip_selector = this.cpu.safe_read16(addr + 16);
this.fpu_opcode = this.cpu.safe_read16(addr + 18);
this.fpu_dp = this.cpu.safe_read32s(addr + 20);
this.fpu_dp_selector = this.cpu.safe_read16(addr + 24);
}
else
{
this.fpu_unimpl();
}
}
FPU.prototype.fsave = function(addr)
{
this.cpu.writable_or_pagefault(addr, 108);
this.fstenv(addr);
addr += 28;
for(var i = 0; i < 8; i++)
{
this.store_m80(addr, this.st[this.stack_ptr + i & 7]);
addr += 10;
}
//dbg_log("save st=" + this.stack_ptr + " " + [].slice.call(this.st), LOG_FPU);
this.finit();
}
FPU.prototype.frstor = function(addr)
{
this.fldenv(addr);
addr += 28;
for(var i = 0; i < 8; i++)
{
this.st[(i + this.stack_ptr) & 7] = this.load_m80(addr);
addr += 10;
}
//dbg_log("rstor st=" + this.stack_ptr + " " + [].slice.call(this.st), LOG_FPU);
}
FPU.prototype.fxtract = function()
{
this.float64[0] = this.get_st0();
var exponent = ((this.float64_byte[7] & 0x7F) << 4 | this.float64_byte[6] >> 4) - 0x3FF;
this.float64_byte[7] = 0x3F | (this.float64_byte[7] & 0x80);
this.float64_byte[6] |= 0xF0;
this.st[this.stack_ptr] = exponent;
this.push(this.float64[0]);
};
FPU.prototype.integer_round = function(f)
{
var rc = this.control_word >> 10 & 3;
return this.cpu.integer_round(f, rc);
}
FPU.prototype.truncate = function(x)
{
return x > 0 ? Math.floor(x) : Math.ceil(x);
}
FPU.prototype.push = function(x)
{
this.stack_ptr = this.stack_ptr - 1 & 7;
if(this.stack_empty >> this.stack_ptr & 1)
{
this.status_word &= ~FPU_C1;
this.stack_empty &= ~(1 << this.stack_ptr);
this.st[this.stack_ptr] = x;
}
else
{
this.status_word |= FPU_C1;
this.stack_fault();
this.st[this.stack_ptr] = this.indefinite_nan;
}
}
FPU.prototype.pop = function()
{
this.stack_empty |= 1 << this.stack_ptr;
this.stack_ptr = this.stack_ptr + 1 & 7;
}
FPU.prototype.get_sti = function(i)
{
dbg_assert(typeof i === "number" && i >= 0 && i < 8);
i = i + this.stack_ptr & 7;
if(this.stack_empty >> i & 1)
{
this.status_word &= ~FPU_C1;
this.stack_fault();
return this.indefinite_nan;
}
else
{
return this.st[i];
}
}
FPU.prototype.get_st0 = function()
{
if(this.stack_empty >> this.stack_ptr & 1)
{
this.status_word &= ~FPU_C1;
this.stack_fault();
return this.indefinite_nan;
}
else
{
return this.st[this.stack_ptr];
}
}
FPU.prototype.load_m80 = function(addr)
{
var exponent = this.cpu.safe_read16(addr + 8),
sign,
low = this.cpu.safe_read32s(addr) >>> 0,
high = this.cpu.safe_read32s(addr + 4) >>> 0;
sign = exponent >> 15;
exponent &= ~0x8000;
if(exponent === 0)
{
// TODO: denormal numbers
return 0;
}
if(exponent < 0x7FFF)
{
exponent -= 0x3FFF;
}
else
{
// TODO: NaN, Infinity
//dbg_log("Load m80 TODO", LOG_FPU);
this.float64_byte[7] = 0x7F | sign << 7;
this.float64_byte[6] = 0xF0 | high >> 30 << 3 & 0x08;
this.float64_byte[5] = 0;
this.float64_byte[4] = 0;
this.float64_int[0] = 0;
return this.float64[0];
}
// Note: some bits might be lost at this point
var mantissa = low + 0x100000000 * high;
if(sign)
{
mantissa = -mantissa;
}
//console.log("m: " + mantissa);
//console.log("e: " + exponent);
//console.log("s: " + this.sign);
//console.log("f: " + mantissa * Math.pow(2, exponent - 63));
// Simply compute the 64 bit floating point number.
// An alternative write the mantissa, sign and exponent in the
// float64_byte and return float64[0]
return mantissa * Math.pow(2, exponent - 63);
}
FPU.prototype.store_m80 = function(addr, n)
{
this.float64[0] = n;
var sign = this.float64_byte[7] & 0x80,
exponent = (this.float64_byte[7] & 0x7f) << 4 | this.float64_byte[6] >> 4,
low,
high;
if(exponent === 0x7FF)
{
// all bits set (NaN and infinity)
exponent = 0x7FFF;
low = 0;
high = 0x80000000 | (this.float64_int[1] & 0x80000) << 11;
}
else if(exponent === 0)
{
// zero and denormal numbers
// Just assume zero for now
low = 0;
high = 0;
}
else
{
exponent += 0x3FFF - 0x3FF;
// does the mantissa need to be adjusted?
low = this.float64_int[0] << 11;
high = 0x80000000 | (this.float64_int[1] & 0xFFFFF) << 11 | (this.float64_int[0] >>> 21);
}
dbg_assert(exponent >= 0 && exponent < 0x8000);
this.cpu.safe_write32(addr, low);
this.cpu.safe_write32(addr + 4, high);
this.cpu.safe_write16(addr + 8, sign << 8 | exponent);
}
FPU.prototype.load_m64 = function(addr)
{
var low = this.cpu.safe_read32s(addr),
high = this.cpu.safe_read32s(addr + 4);
this.float64_int[0] = low;
this.float64_int[1] = high;
return this.float64[0];
};
FPU.prototype.store_m64 = function(addr, i)
{
this.cpu.writable_or_pagefault(addr, 8);
this.float64[0] = this.get_sti(i);
this.cpu.safe_write32(addr, this.float64_int[0]);
this.cpu.safe_write32(addr + 4, this.float64_int[1]);
};
FPU.prototype.load_m32 = function(addr)
{
this.float32_int[0] = this.cpu.safe_read32s(addr);
return this.float32[0];
};
FPU.prototype.store_m32 = function(addr, x)
{
this.float32[0] = x;
this.cpu.safe_write32(addr, this.float32_int[0]);
};
// sign of a number on the stack
FPU.prototype.sign = function(i)
{
return this.st8[(this.stack_ptr + i & 7) << 3 | 7] >> 7;
};
FPU.prototype.dbg_log_fpu_op = function(op, imm8)
{
if(!FPU_LOG_OP)
{
return;
}
if(imm8 >= 0xC0)
{
dbg_log(h(op, 2) + " " + h(imm8, 2) + "/" + (imm8 >> 3 & 7) + "/" + (imm8 & 7) +
" @" + h(this.cpu.instruction_pointer >>> 0, 8) + " sp=" + this.stack_ptr + " st=" + h(this.stack_empty, 2), LOG_FPU);
}
else
{
dbg_log(h(op, 2) + " /" + (imm8 >> 3 & 7) +
" @" + h(this.cpu.instruction_pointer >>> 0, 8) + " sp=" + this.stack_ptr + " st=" + h(this.stack_empty, 2), LOG_FPU);
}
}
FPU.prototype.fwait = function()
{
// NOP unless FPU instructions run in parallel with CPU instructions
};
FPU.prototype.op_D8_reg = function(imm8)
{
this.dbg_log_fpu_op(0xD8, imm8);
var mod = imm8 >> 3 & 7,
low = imm8 & 7,
sti = this.get_sti(low),
st0 = this.get_st0();
switch(mod)
{
case 0:
// fadd
this.st[this.stack_ptr] = st0 + sti;
break;
case 1:
// fmul
this.st[this.stack_ptr] = st0 * sti;
break;
case 2:
// fcom
this.fcom(sti);
break;
case 3:
// fcomp
this.fcom(sti);
this.pop();
break;
case 4:
// fsub
this.st[this.stack_ptr] = st0 - sti;
break;
case 5:
// fsubr
this.st[this.stack_ptr] = sti - st0;
break;
case 6:
// fdiv
this.st[this.stack_ptr] = st0 / sti;
break;
case 7:
// fdivr
this.st[this.stack_ptr] = sti / st0;
break;
default:
dbg_assert(false);
}
};
FPU.prototype.op_D8_mem = function(imm8, addr)
{
this.dbg_log_fpu_op(0xD8, imm8);
var mod = imm8 >> 3 & 7,
m32 = this.load_m32(addr);
var st0 = this.get_st0();
switch(mod)
{
case 0:
// fadd
this.st[this.stack_ptr] = st0 + m32;
break;
case 1:
// fmul
this.st[this.stack_ptr] = st0 * m32;
break;
case 2:
// fcom
this.fcom(m32);
break;
case 3:
// fcomp
this.fcom(m32);
this.pop();
break;
case 4:
// fsub
this.st[this.stack_ptr] = st0 - m32;
break;
case 5:
// fsubr
this.st[this.stack_ptr] = m32 - st0;
break;
case 6:
// fdiv
this.st[this.stack_ptr] = st0 / m32;
break;
case 7:
// fdivr
this.st[this.stack_ptr] = m32 / st0;
break;
default:
dbg_assert(false);
}
};
FPU.prototype.op_D9_reg = function(imm8)
{
this.dbg_log_fpu_op(0xD9, imm8);
var mod = imm8 >> 3 & 7,
low = imm8 & 7;
switch(mod)
{
case 0:
// fld
var sti = this.get_sti(low);
this.push(sti);
break;
case 1:
// fxch
var sti = this.get_sti(low);
this.st[this.stack_ptr + low & 7] = this.get_st0();
this.st[this.stack_ptr] = sti;
break;
case 2:
switch(low)
{
case 0:
// fnop
break;
default:
dbg_log(low);
this.fpu_unimpl();
}
break;
case 3:
// fstp1
this.fpu_unimpl();
break;
case 4:
var st0 = this.get_st0();
switch(low)
{
case 0:
// fchs
this.st[this.stack_ptr] = -st0;
break;
case 1:
// fabs
this.st[this.stack_ptr] = Math.abs(st0);
break;
case 4:
this.ftst(st0);
break;
case 5:
this.fxam(st0);
break;
default:
dbg_log(low);
this.fpu_unimpl();
}
break;
case 5:
this.push(this.constants[low]);
break;
case 6:
var st0 = this.get_st0();
switch(low)
{
case 0:
// f2xm1
this.st[this.stack_ptr] = Math.pow(2, st0) - 1;
break;
case 1:
// fyl2x
this.st[this.stack_ptr + 1 & 7] = this.get_sti(1) * Math.log(st0) / Math.LN2;
this.pop();
break;
case 2:
// fptan
this.st[this.stack_ptr] = Math.tan(st0);
this.push(1); // no bug: push constant 1
break;
case 3:
// fpatan
this.st[this.stack_ptr + 1 & 7] = Math.atan2(this.get_sti(1), st0);
this.pop();
break;
case 4:
this.fxtract();
break;
case 5:
// fprem1
this.st[this.stack_ptr] = st0 % this.get_sti(1);
break;
case 6:
// fdecstp
this.stack_ptr = this.stack_ptr - 1 & 7;
this.status_word &= ~FPU_C1;
break;
case 7:
// fincstp
this.stack_ptr = this.stack_ptr + 1 & 7;
this.status_word &= ~FPU_C1;
break;
default:
dbg_assert(false);
}
break;
case 7:
var st0 = this.get_st0();
switch(low)
{
case 0:
// fprem
var st1 = this.get_sti(1);
var fprem_quotient = Math.trunc(st0 / st1);
this.st[this.stack_ptr] = st0 % st1;
this.status_word &= ~(FPU_C0 | FPU_C1 | FPU_C3);
if (fprem_quotient & 1) {
this.status_word |= FPU_C1;
}
if (fprem_quotient & (1 << 1)) {
this.status_word |= FPU_C3;
}
if (fprem_quotient & (1 << 2)) {
this.status_word |= FPU_C0;
}
this.status_word &= ~FPU_C2;
break;
case 1:
// fyl2xp1: y * log2(x+1) and pop
this.st[this.stack_ptr + 1 & 7] = this.get_sti(1) * Math.log(st0 + 1) / Math.LN2;
this.pop();
break;
case 2:
this.st[this.stack_ptr] = Math.sqrt(st0);
break;
case 3:
this.st[this.stack_ptr] = Math.sin(st0);
this.push(Math.cos(st0));
break;
case 4:
// frndint
this.st[this.stack_ptr] = this.integer_round(st0);
break;
case 5:
// fscale
this.st[this.stack_ptr] = st0 * Math.pow(2, this.truncate(this.get_sti(1)));
break;
case 6:
this.st[this.stack_ptr] = Math.sin(st0);
break;
case 7:
this.st[this.stack_ptr] = Math.cos(st0);
break;
default:
dbg_assert(false);
}
break;
default:
dbg_assert(false);
}
};
FPU.prototype.op_D9_mem = function(imm8, addr)
{
this.dbg_log_fpu_op(0xD9, imm8);
var mod = imm8 >> 3 & 7;
switch(mod)
{
case 0:
// fld
var data = this.load_m32(addr);
this.push(data);
break;
case 1:
// not defined
this.fpu_unimpl();
break;
case 2:
// fst
this.store_m32(addr, this.get_st0());
break;
case 3:
// fstp
this.store_m32(addr, this.get_st0());
this.pop();
break;
case 4:
this.fldenv(addr);
break;
case 5:
// fldcw
var word = this.cpu.safe_read16(addr);
this.control_word = word;
break;
case 6:
this.fstenv(addr);
break;
case 7:
// fstcw
this.cpu.safe_write16(addr, this.control_word);
break;
default:
dbg_assert(false);
}
};
FPU.prototype.op_DA_reg = function(imm8)
{
this.dbg_log_fpu_op(0xDA, imm8);
var mod = imm8 >> 3 & 7,
low = imm8 & 7;
switch(mod)
{
case 0:
// fcmovb
if(this.cpu.test_b())
{
this.st[this.stack_ptr] = this.get_sti(low);
this.stack_empty &= ~(1 << this.stack_ptr);
}
break;
case 1:
// fcmove
if(this.cpu.test_z())
{
this.st[this.stack_ptr] = this.get_sti(low);
this.stack_empty &= ~(1 << this.stack_ptr);
}
break;
case 2:
// fcmovbe
if(this.cpu.test_be())
{
this.st[this.stack_ptr] = this.get_sti(low);
this.stack_empty &= ~(1 << this.stack_ptr);
}
break;
case 3:
// fcmovu
if(this.cpu.test_p())
{
this.st[this.stack_ptr] = this.get_sti(low);
this.stack_empty &= ~(1 << this.stack_ptr);
}
break;
case 5:
if(low === 1)
{
// fucompp
this.fucom(this.get_sti(1));
this.pop();
this.pop();
}
else
{
dbg_log(mod); this.fpu_unimpl();
}
break;
default:
dbg_log(mod);
this.fpu_unimpl();
}
};
FPU.prototype.op_DA_mem = function(imm8, addr)
{
this.dbg_log_fpu_op(0xDA, imm8);
var mod = imm8 >> 3 & 7,
m32 = this.cpu.safe_read32s(addr);
var st0 = this.get_st0();
switch(mod)
{
case 0:
// fadd
this.st[this.stack_ptr] = st0 + m32;
break;
case 1:
// fmul
this.st[this.stack_ptr] = st0 * m32;
break;
case 2:
// fcom
this.fcom(m32);
break;
case 3:
// fcomp
this.fcom(m32);
this.pop();
break;
case 4:
// fsub
this.st[this.stack_ptr] = st0 - m32;
break;
case 5:
// fsubr
this.st[this.stack_ptr] = m32 - st0;
break;
case 6:
// fdiv
this.st[this.stack_ptr] = st0 / m32;
break;
case 7:
// fdivr
this.st[this.stack_ptr] = m32 / st0;
break;
default:
dbg_assert(false);
}
};
FPU.prototype.op_DB_reg = function(imm8)
{
this.dbg_log_fpu_op(0xDB, imm8);
var mod = imm8 >> 3 & 7,
low = imm8 & 7;
switch(mod)
{
case 0:
// fcmovnb
if(!this.cpu.test_b())
{
this.st[this.stack_ptr] = this.get_sti(low);
this.stack_empty &= ~(1 << this.stack_ptr);
}
break;
case 1:
// fcmovne
if(!this.cpu.test_z())
{
this.st[this.stack_ptr] = this.get_sti(low);
this.stack_empty &= ~(1 << this.stack_ptr);
}
break;
case 2:
// fcmovnbe
if(!this.cpu.test_be())
{
this.st[this.stack_ptr] = this.get_sti(low);
this.stack_empty &= ~(1 << this.stack_ptr);
}
break;
case 3:
// fcmovnu
if(!this.cpu.test_p())
{
this.st[this.stack_ptr] = this.get_sti(low);
this.stack_empty &= ~(1 << this.stack_ptr);
}
break;
case 4:
if(imm8 === 0xE3)
{
this.finit();
}
else if(imm8 === 0xE4)
{
// fsetpm
// treat as nop
}
else if(imm8 === 0xE1)
{
// fdisi
// also treat as nop
}
else if(imm8 === 0xE2)
{
// fclex
this.status_word = 0;
}
else
{
dbg_log(h(imm8));
this.fpu_unimpl();
}
break;
case 5:
this.fucomi(this.get_sti(low));
break;
case 6:
this.fcomi(this.get_sti(low));
break;
default:
dbg_log(mod);
this.fpu_unimpl();
}
};
FPU.prototype.op_DB_mem = function(imm8, addr)
{
this.dbg_log_fpu_op(0xDB, imm8);
var mod = imm8 >> 3 & 7;
switch(mod)
{
case 0:
// fild
var int32 = this.cpu.safe_read32s(addr);
this.push(int32);
break;
case 2:
// fist
var st0 = this.integer_round(this.get_st0());
if(st0 <= 0x7FFFFFFF && st0 >= -0x80000000)
{
// TODO: Invalid operation
this.cpu.safe_write32(addr, st0);
}
else
{
this.invalid_arithmatic();
this.cpu.safe_write32(addr, 0x80000000|0);
}
break;
case 3:
// fistp
var st0 = this.integer_round(this.get_st0());
if(st0 <= 0x7FFFFFFF && st0 >= -0x80000000)
{
this.cpu.safe_write32(addr, st0);
}
else
{
this.invalid_arithmatic();
this.cpu.safe_write32(addr, 0x80000000|0);
}
this.pop();
break;
case 5:
// fld
this.push(this.load_m80(addr));
break;
case 7:
// fstp
this.cpu.writable_or_pagefault(addr, 10);
this.store_m80(addr, this.get_st0());
this.pop();
break;
default:
dbg_log(mod);
this.fpu_unimpl();
}
};
FPU.prototype.op_DC_reg = function(imm8)
{
this.dbg_log_fpu_op(0xDC, imm8);
var mod = imm8 >> 3 & 7,
low = imm8 & 7,
low_ptr = this.stack_ptr + low & 7,
sti = this.get_sti(low),
st0 = this.get_st0();
switch(mod)
{
case 0:
// fadd
this.st[low_ptr] = sti + st0;
break;
case 1:
// fmul
this.st[low_ptr] = sti * st0;
break;
case 2:
// fcom
this.fcom(sti);
break;
case 3:
// fcomp
this.fcom(sti);
this.pop();
break;
case 4:
// fsubr
this.st[low_ptr] = st0 - sti;
break;
case 5:
// fsub
this.st[low_ptr] = sti - st0;
break;
case 6:
// fdivr
this.st[low_ptr] = st0 / sti;
break;
case 7:
// fdiv
this.st[low_ptr] = sti / st0;
break;
default:
dbg_assert(false);
}
};
FPU.prototype.op_DC_mem = function(imm8, addr)
{
this.dbg_log_fpu_op(0xDC, imm8);
var
mod = imm8 >> 3 & 7,
m64 = this.load_m64(addr);
var st0 = this.get_st0();
switch(mod)
{
case 0:
// fadd
this.st[this.stack_ptr] = st0 + m64;
break;
case 1:
// fmul
this.st[this.stack_ptr] = st0 * m64;
break;
case 2:
// fcom
this.fcom(m64);
break;
case 3:
// fcomp
this.fcom(m64);
this.pop();
break;
case 4:
// fsub
this.st[this.stack_ptr] = st0 - m64;
break;
case 5:
// fsubr
this.st[this.stack_ptr] = m64 - st0;
break;
case 6:
// fdiv
this.st[this.stack_ptr] = st0 / m64;
break;
case 7:
// fdivr
this.st[this.stack_ptr] = m64 / st0;
break;
default:
dbg_assert(false);
}
};
FPU.prototype.op_DD_reg = function(imm8)
{
this.dbg_log_fpu_op(0xDD, imm8);
var mod = imm8 >> 3 & 7,
low = imm8 & 7;
switch(mod)
{
case 0:
// ffree
this.stack_empty |= 1 << (this.stack_ptr + low & 7);
break;
case 2:
// fst
this.st[this.stack_ptr + low & 7] = this.get_st0();
break;
case 3:
// fstp
if(low === 0)
{
this.pop();
}
else
{
this.st[this.stack_ptr + low & 7] = this.get_st0();
this.pop();
}
break;
case 4:
this.fucom(this.get_sti(low));
break;
case 5:
// fucomp
this.fucom(this.get_sti(low));
this.pop();
break;
default:
dbg_log(mod);
this.fpu_unimpl();
}
};
FPU.prototype.op_DD_mem = function(imm8, addr)
{
this.dbg_log_fpu_op(0xDD, imm8);
var mod = imm8 >> 3 & 7;
switch(mod)
{
case 0:
// fld
var data = this.load_m64(addr);
this.push(data);
break;
case 1:
// fisttp
this.fpu_unimpl();
break;
case 2:
// fst
this.store_m64(addr, 0);
break;
case 3:
// fstp
this.store_m64(addr, 0);
this.pop();
break;
case 4:
this.frstor(addr);
break;
case 5:
// nothing
this.fpu_unimpl();
break;
case 6:
// fsave
this.fsave(addr);
break;
case 7:
// fnstsw / store status word
this.cpu.safe_write16(addr, this.load_status_word());
break;
default:
dbg_assert(false);
}
};
FPU.prototype.op_DE_reg = function(imm8)
{
this.dbg_log_fpu_op(0xDE, imm8);
var mod = imm8 >> 3 & 7,
low = imm8 & 7,
low_ptr = this.stack_ptr + low & 7,
sti = this.get_sti(low),
st0 = this.get_st0();
switch(mod)
{
case 0:
// faddp
this.st[low_ptr] = sti + st0;
break;
case 1:
// fmulp
this.st[low_ptr] = sti * st0;
break;
case 2:
// fcomp
this.fcom(sti);
break;
case 3:
// fcompp
if(low === 1)
{
this.fcom(this.st[low_ptr]);
this.pop();
}
else
{
// not a valid encoding
dbg_log(mod);
this.fpu_unimpl();
}
break;
case 4:
// fsubrp
this.st[low_ptr] = st0 - sti;
break;
case 5:
// fsubp
this.st[low_ptr] = sti - st0;
break;
case 6:
// fdivrp
this.st[low_ptr] = st0 / sti;
break;
case 7:
// fdivp
this.st[low_ptr] = sti / st0;
break;
default:
dbg_assert(false);
}
this.pop();
};
FPU.prototype.op_DE_mem = function(imm8, addr)
{
this.dbg_log_fpu_op(0xDE, imm8);
var mod = imm8 >> 3 & 7,
m16 = this.cpu.safe_read16(addr) << 16 >> 16;
var st0 = this.get_st0();
switch(mod)
{
case 0:
// fadd
this.st[this.stack_ptr] = st0 + m16;
break;
case 1:
// fmul
this.st[this.stack_ptr] = st0 * m16;
break;
case 2:
// fcom
this.fcom(m16);
break;
case 3:
// fcomp
this.fcom(m16);
this.pop();
break;
case 4:
// fsub
this.st[this.stack_ptr] = st0 - m16;
break;
case 5:
// fsubr
this.st[this.stack_ptr] = m16 - st0;
break;
case 6:
// fdiv
this.st[this.stack_ptr] = st0 / m16;
break;
case 7:
// fdivr
this.st[this.stack_ptr] = m16 / st0;
break;
default:
dbg_assert(false);
}
};
FPU.prototype.op_DF_reg = function(imm8)
{
this.dbg_log_fpu_op(0xDF, imm8);
var mod = imm8 >> 3 & 7,
low = imm8 & 7;
switch(mod)
{
case 4:
if(imm8 === 0xE0)
{
// fnstsw
this.cpu.reg16[reg_ax] = this.load_status_word();
}
else
{
dbg_log(imm8);
this.fpu_unimpl();
}
break;
case 5:
// fucomip
this.fucomi(this.get_sti(low));
this.pop();
break;
case 6:
// fcomip
this.fcomi(this.get_sti(low));
this.pop();
break;
default:
dbg_log(mod);
this.fpu_unimpl();
}
};
FPU.prototype.op_DF_mem = function(imm8, addr)
{
this.dbg_log_fpu_op(0xDF, imm8);
var mod = imm8 >> 3 & 7;
switch(mod)
{
case 0:
var m16 = this.cpu.safe_read16(addr) << 16 >> 16;
this.push(m16);
break;
case 1:
// fisttp
this.fpu_unimpl();
break;
case 2:
// fist
var st0 = this.integer_round(this.get_st0());
if(st0 <= 0x7FFF && st0 >= -0x8000)
{
this.cpu.safe_write16(addr, st0);
}
else
{
this.invalid_arithmatic();
this.cpu.safe_write16(addr, 0x8000);
}
break;
case 3:
// fistp
var st0 = this.integer_round(this.get_st0());
if(st0 <= 0x7FFF && st0 >= -0x8000)
{
this.cpu.safe_write16(addr, st0);
}
else
{
this.invalid_arithmatic();
this.cpu.safe_write16(addr, 0x8000);
}
this.pop();
break;
case 4:
// fbld
this.fpu_unimpl();
break;
case 5:
// fild
var low = this.cpu.safe_read32s(addr) >>> 0,
high = this.cpu.safe_read32s(addr + 4);
var m64 = low + 0x100000000 * high;
this.push(m64);
break;
case 6:
// fbstp
this.fpu_unimpl();
break;
case 7:
this.cpu.writable_or_pagefault(addr, 8);
// fistp
var st0 = this.integer_round(this.get_st0()),
st0_low,
st0_high;
if(st0 < TWO_POW_63 && st0 >= -TWO_POW_63)
{
st0_low = st0 | 0;
st0_high = st0 / 0x100000000 | 0;
if(st0_high === 0 && st0 < 0)
st0_high = -1;
}
else
{
// write 0x8000000000000000
st0_low = 0;
st0_high = 0x80000000 | 0;
this.invalid_arithmatic();
}
this.cpu.safe_write32(addr, st0_low);
this.cpu.safe_write32(addr + 4, st0_high);
this.pop();
break;
default:
dbg_assert(false);
}
};