| "use strict"; |
| |
| /** @const */ |
| var FPU_LOG_OP = false; |
| |
| |
| /** |
| * this behaves as if no x87 fpu existed |
| * @constructor |
| */ |
| function NoFPU(io) |
| { |
| this.is_fpu = 0; |
| //cr0 |= 4; |
| |
| this.fwait = function() |
| { |
| |
| }; |
| |
| this.op_D8_reg = function(imm8) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_D8_mem = function(imm8, addr) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_D9_reg = function(imm8) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_D9_mem = function(imm8, addr) |
| { |
| var mod = imm8 >> 3 & 7; |
| |
| if(mod === 7) |
| { |
| // FNSTCW |
| dbg_log("Unimplemented D9", LOG_FPU); |
| safe_write16(addr, 0); |
| } |
| else |
| { |
| trigger_ud(); |
| } |
| }; |
| |
| this.op_DA = function(imm8) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_DA_mem = function(imm8, addr) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_DB_reg = function(imm8) |
| { |
| if(imm8 === 0xE3) |
| { |
| // fninit |
| // don't error, even if no fpu is present |
| dbg_log("Unimplemented DB", LOG_FPU); |
| } |
| else |
| { |
| trigger_ud(); |
| } |
| }; |
| |
| this.op_DB_mem = function(imm8, addr) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_DC_reg = function(imm8) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_DC_mem = function(imm8, addr) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_DD_reg = function(imm8) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_DD_mem = function(imm8, addr) |
| { |
| var mod = imm8 >> 3 & 7; |
| |
| switch(mod) |
| { |
| case 7: |
| // fnstsw / store status word |
| // no fpu -> write nonzero |
| dbg_log("Unimplemented DD", LOG_FPU); |
| safe_write16(addr, 1); |
| break; |
| default: |
| trigger_ud(); |
| } |
| }; |
| |
| this.op_DE_reg = function(imm8) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_DE_mem = function(imm8, addr) |
| { |
| trigger_ud(); |
| }; |
| |
| this.op_DF_reg = function(imm8) |
| { |
| if(imm8 === 0xE0) |
| { |
| // fnstsw |
| // no fpu -> write nonzero |
| dbg_log("Unimplemented DF", LOG_FPU); |
| reg16[reg_ax] = 1; |
| } |
| else |
| { |
| trigger_ud(); |
| } |
| }; |
| |
| this.op_DF_mem = function(imm8, addr) |
| { |
| trigger_ud(); |
| }; |
| } |
| |
| /** |
| * @constructor |
| */ |
| function FPU(io) |
| { |
| this.is_fpu = 1; |
| |
| // TODO: |
| // - Precision Control |
| // - QNaN, unordered comparison |
| // - Exceptions |
| |
| var |
| /** @const */ |
| C0 = 0x100, |
| /** @const */ |
| C1 = 0x200, |
| /** @const */ |
| C2 = 0x400, |
| /** @const */ |
| C3 = 0x4000, |
| /** @const */ |
| RESULT_FLAGS = C0 | C1 | C2 | C3, |
| /** @const */ |
| STACK_TOP = 0x3800; |
| |
| var |
| // precision, round & infinity control |
| /** @const */ |
| PC = 3 << 8, |
| /** @const */ |
| RC = 3 << 10, |
| /** @const */ |
| IF = 1 << 12; |
| |
| // exception bits in the status word |
| var EX_SF = 1 << 6, |
| EX_P = 1 << 5, |
| EX_U = 1 << 4, |
| EX_O = 1 << 3, |
| EX_Z = 1 << 2, |
| EX_D = 1 << 1, |
| EX_I = 1 << 0; |
| |
| var |
| // Why no Float80Array :-( |
| st = new Float64Array(8), |
| st8 = new Uint8Array(st.buffer), |
| st32 = new Uint32Array(st.buffer), |
| |
| // bitmap of which stack registers are empty |
| stack_empty = 0xff, |
| stack_ptr = 0, |
| |
| // used for conversion |
| float32 = new Float32Array(1), |
| float32_byte = new Uint8Array(float32.buffer), |
| float32_int = new Uint32Array(float32.buffer), |
| |
| float64 = new Float64Array(1), |
| float64_byte = new Uint8Array(float64.buffer), |
| float64_int = new Uint32Array(float64.buffer), |
| |
| float80_int = new Uint8Array(10), |
| |
| |
| control_word = 0x37F, |
| status_word = 0, |
| fpu_ip = 0, |
| fpu_ip_selector = 0, |
| fpu_opcode = 0, |
| fpu_dp = 0, |
| fpu_dp_selector = 0, |
| |
| |
| /** @const */ |
| indefinite_nan = NaN; |
| |
| |
| var constants = new Float64Array([ |
| 1, Math.log(10) / Math.LN2, Math.LOG2E, Math.PI, |
| Math.log(2) / Math.LN10, Math.LN2, 0 |
| ]); |
| |
| function fpu_unimpl() |
| { |
| dbg_trace(); |
| if(DEBUG) throw "fpu: unimplemented"; |
| else trigger_ud(); |
| } |
| |
| function stack_fault() |
| { |
| // TODO: Interrupt |
| status_word |= EX_SF | EX_I; |
| } |
| |
| function invalid_arithmatic() |
| { |
| status_word |= EX_I; |
| } |
| |
| function fcom(y) |
| { |
| var x = get_st0(); |
| |
| status_word &= ~RESULT_FLAGS; |
| |
| if(x > y) |
| { |
| } |
| else if(y > x) |
| { |
| status_word |= C0; |
| } |
| else if(x === y) |
| { |
| status_word |= C3; |
| } |
| else |
| { |
| status_word |= C0 | C2 | C3; |
| } |
| } |
| |
| function fucom(y) |
| { |
| // TODO |
| fcom(y); |
| } |
| |
| |
| function fcomi(y) |
| { |
| var x = st[stack_ptr]; |
| |
| flags_changed &= ~(1 | flag_parity | flag_zero); |
| flags &= ~(1 | flag_parity | flag_zero); |
| |
| if(x > y) |
| { |
| } |
| else if(y > x) |
| { |
| flags |= 1; |
| } |
| else if(x === y) |
| { |
| flags |= flag_zero; |
| } |
| else |
| { |
| flags |= 1 | flag_parity | flag_zero; |
| } |
| } |
| |
| function fucomi(y) |
| { |
| // TODO |
| fcomi(y); |
| } |
| |
| function ftst() |
| { |
| var st0 = get_st0(); |
| |
| status_word &= ~RESULT_FLAGS; |
| |
| if(isNaN(st0)) |
| { |
| status_word |= C3 | C2 | C0; |
| } |
| else if(st0 === 0) |
| { |
| status_word |= C3; |
| } |
| else if(st0 < 0) |
| { |
| status_word |= C0; |
| } |
| |
| // TODO: unordered (st0 is nan, etc) |
| } |
| |
| function fxam() |
| { |
| var x = get_st0(); |
| |
| status_word &= ~RESULT_FLAGS; |
| status_word |= sign(0) << 9; |
| |
| if(stack_empty >> stack_ptr & 1) |
| { |
| status_word |= C3 | C0; |
| } |
| else if(isNaN(x)) |
| { |
| status_word |= C0; |
| } |
| else if(x === 0) |
| { |
| status_word |= C3; |
| } |
| else if(x === Infinity || x === -Infinity) |
| { |
| status_word |= C2 | C0; |
| } |
| else |
| { |
| status_word |= C2; |
| } |
| // TODO: |
| // Unsupported, Denormal |
| } |
| |
| function finit() |
| { |
| control_word = 0x37F; |
| status_word = 0; |
| fpu_ip = 0; |
| fpu_dp = 0; |
| fpu_opcode = 0; |
| |
| stack_empty = 0xFF; |
| stack_ptr = 0; |
| } |
| |
| function load_status_word() |
| { |
| return status_word & ~(7 << 11) | stack_ptr << 11; |
| } |
| |
| function safe_status_word(sw) |
| { |
| status_word = sw & ~(7 << 11); |
| stack_ptr = sw >> 11 & 7; |
| } |
| |
| function load_tag_word() |
| { |
| var tag_word = 0, |
| value; |
| |
| for(var i = 0; i < 8; i++) |
| { |
| value = st[i]; |
| |
| if(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(stack_empty) + " sp=" + stack_ptr, LOG_FPU); |
| |
| return tag_word; |
| } |
| |
| function safe_tag_word(tag_word) |
| { |
| stack_empty = 0; |
| |
| for(var i = 0; i < 8; i++) |
| { |
| stack_empty |= (tag_word >> i) & (tag_word >> i + 1) & 1 << i; |
| } |
| |
| //dbg_log("safe tw=" + h(tag_word) + " se=" + h(stack_empty), LOG_FPU); |
| } |
| |
| function fstenv(addr) |
| { |
| if(operand_size_32) |
| { |
| safe_write16(addr, control_word); |
| |
| safe_write16(addr + 4, load_status_word()); |
| safe_write16(addr + 8, load_tag_word()); |
| |
| safe_write32(addr + 12, fpu_ip); |
| safe_write16(addr + 16, fpu_ip_selector); |
| safe_write16(addr + 18, fpu_opcode); |
| safe_write32(addr + 20, fpu_dp); |
| safe_write16(addr + 24, fpu_dp_selector); |
| } |
| else |
| { |
| fpu_unimpl(); |
| } |
| } |
| |
| function fldenv(addr) |
| { |
| if(operand_size_32) |
| { |
| control_word = safe_read16(addr); |
| |
| safe_status_word(safe_read16(addr + 4)); |
| safe_tag_word(safe_read16(addr + 8)); |
| |
| fpu_ip = safe_read32(addr + 12); |
| fpu_ip_selector = safe_read16(addr + 16); |
| fpu_opcode = safe_read16(addr + 18); |
| fpu_dp = safe_read32(addr + 20); |
| fpu_dp_selector = safe_read16(addr + 24); |
| } |
| else |
| { |
| fpu_unimpl(); |
| } |
| } |
| |
| function fsave(addr) |
| { |
| fstenv(addr); |
| addr += 28; |
| |
| for(var i = 0; i < 8; i++) |
| { |
| store_m80(addr, i - stack_ptr & 7); |
| addr += 10; |
| } |
| |
| //dbg_log("save " + [].slice.call(st), LOG_FPU); |
| |
| finit(); |
| } |
| |
| function frstor(addr) |
| { |
| fldenv(addr); |
| addr += 28; |
| |
| for(var i = 0; i < 8; i++) |
| { |
| st[i] = load_m80(addr); |
| addr += 10; |
| } |
| |
| //dbg_log("rstor " + [].slice.call(st), LOG_FPU); |
| } |
| |
| function integer_round(f) |
| { |
| var rc = control_word >> 10 & 3; |
| |
| if(rc === 0) |
| { |
| // Round to nearest, or even if equidistant |
| var rounded = Math.round(f); |
| |
| if(rounded - f === 0.5 && (rounded & 1)) |
| { |
| // Special case: Math.round rounds to positive infinity |
| // if equidistant |
| rounded--; |
| } |
| |
| return rounded; |
| } |
| // rc=3 is truncate -> floor for positive numbers |
| else if(rc === 1 || (rc === 3 && f > 0)) |
| { |
| return Math.floor(f); |
| } |
| else |
| { |
| return Math.ceil(f); |
| } |
| } |
| |
| function truncate(x) |
| { |
| return x > 0 ? Math.floor(x) : Math.ceil(x); |
| } |
| |
| function push(x) |
| { |
| stack_ptr = stack_ptr - 1 & 7; |
| |
| if(stack_empty >> stack_ptr & 1) |
| { |
| status_word &= ~C1; |
| stack_empty &= ~(1 << stack_ptr); |
| st[stack_ptr] = x; |
| } |
| else |
| { |
| status_word |= C1; |
| stack_fault(); |
| st[stack_ptr] = indefinite_nan; |
| } |
| } |
| |
| function pop() |
| { |
| stack_empty |= 1 << stack_ptr; |
| stack_ptr = stack_ptr + 1 & 7; |
| } |
| |
| function get_sti(i) |
| { |
| dbg_assert(typeof i === "number" && i >= 0 && i < 8); |
| |
| i = i + stack_ptr & 7; |
| |
| if(stack_empty >> i & 1) |
| { |
| status_word &= ~C1; |
| stack_fault(); |
| return indefinite_nan; |
| } |
| else |
| { |
| return st[i]; |
| } |
| } |
| |
| function get_st0() |
| { |
| if(stack_empty >> stack_ptr & 1) |
| { |
| status_word &= ~C1; |
| stack_fault(); |
| return indefinite_nan; |
| } |
| else |
| { |
| return st[stack_ptr]; |
| } |
| } |
| |
| function assert_not_empty(i) |
| { |
| if(stack_empty >> (i + stack_ptr & 7) & 1) |
| { |
| status_word &= ~C1; |
| } |
| else |
| { |
| } |
| } |
| |
| function load_m80(addr) |
| { |
| var exponent = safe_read16(addr + 8), |
| sign, |
| |
| low = safe_read32(addr), |
| high = safe_read32(addr + 4); |
| |
| 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); |
| float64_byte[7] = 0x7F | sign << 7; |
| float64_byte[6] = 0xF0 | high >> 30 << 3 & 0x08; |
| |
| float64_byte[5] = 0; |
| float64_byte[4] = 0; |
| |
| float64_int[0] = 0; |
| |
| return 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: " + 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); |
| } |
| |
| function store_m80(addr, i) |
| { |
| float64[0] = st[stack_ptr + i & 7]; |
| |
| var sign = float64_byte[7] & 0x80, |
| exponent = (float64_byte[7] & 0x7f) << 4 | float64_byte[6] >> 4, |
| low, |
| high; |
| |
| if(exponent === 0x7FF) |
| { |
| // all bits set (NaN and infinity) |
| exponent = 0x7FFF; |
| low = 0; |
| high = 0x80000000 | (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 = float64_int[0] << 11; |
| high = 0x80000000 | (float64_int[1] & 0xFFFFF) << 11 | (float64_int[0] >>> 21); |
| } |
| |
| dbg_assert(exponent >= 0 && exponent < 0x8000); |
| |
| safe_write32(addr, low); |
| safe_write32(addr + 4, high); |
| |
| safe_write16(addr + 8, sign << 8 | exponent); |
| } |
| |
| function load_m64(addr) |
| { |
| float64_int[0] = safe_read32s(addr); |
| float64_int[1] = safe_read32s(addr + 4); |
| |
| return float64[0]; |
| }; |
| |
| function store_m64(addr, i) |
| { |
| // protect against writing only a single dword |
| // and then page-faulting |
| translate_address_write(addr + 7); |
| |
| float64[0] = get_sti(i); |
| |
| safe_write32(addr, float64_int[0]); |
| safe_write32(addr + 4, float64_int[1]); |
| }; |
| |
| function load_m32(addr) |
| { |
| float32_int[0] = safe_read32s(addr); |
| |
| return float32[0]; |
| }; |
| |
| function store_m32(addr, i) |
| { |
| float32[0] = get_sti(i); |
| |
| safe_write32(addr, float32_int[0]); |
| }; |
| |
| // sign of a number on the stack |
| function sign(i) |
| { |
| return st8[(stack_ptr + i & 7) << 3 | 7] >> 7; |
| }; |
| |
| |
| function dbg_log_fpu_op(op, imm8) |
| { |
| if(!FPU_LOG_OP) |
| { |
| return; |
| } |
| |
| if(imm8 >= 0xC0) |
| { |
| dbg_log(h(op, 2) + " " + h(imm8, 2) + "/" + (imm8 >> 3 & 7) + "/" + (imm8 & 7) + |
| " @" + h(instruction_pointer >>> 0, 8) + " sp=" + stack_ptr + " st=" + h(stack_empty, 2), LOG_FPU); |
| } |
| else |
| { |
| dbg_log(h(op, 2) + " /" + (imm8 >> 3 & 7) + |
| " @" + h(instruction_pointer >>> 0, 8) + " sp=" + stack_ptr + " st=" + h(stack_empty, 2), LOG_FPU); |
| } |
| } |
| |
| |
| this.fwait = function() |
| { |
| // TODO: |
| // Exceptions |
| }; |
| |
| |
| this.op_D8_reg = function(imm8) |
| { |
| dbg_log_fpu_op(0xD8, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| low = imm8 & 7, |
| sti = get_sti(low), |
| st0 = get_st0(); |
| |
| switch(mod) |
| { |
| case 0: |
| // fadd |
| st[stack_ptr] = st0 + sti; |
| break; |
| case 1: |
| // fmul |
| st[stack_ptr] = st0 * sti; |
| break; |
| case 2: |
| // fcom |
| fcom(sti); |
| break; |
| case 3: |
| // fcomp |
| fcom(sti); |
| pop(); |
| break; |
| case 4: |
| // fsub |
| st[stack_ptr] = st0 - sti; |
| break; |
| case 5: |
| // fsubr |
| st[stack_ptr] = sti - st0; |
| break; |
| case 6: |
| // fdiv |
| st[stack_ptr] = st0 / sti; |
| break; |
| case 7: |
| // fdivr |
| st[stack_ptr] = sti / st0; |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_D8_mem = function(imm8, addr) |
| { |
| dbg_log_fpu_op(0xD8, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| m32 = load_m32(addr); |
| |
| var st0 = get_st0(); |
| |
| switch(mod) |
| { |
| case 0: |
| // fadd |
| st[stack_ptr] = st0 + m32; |
| break; |
| case 1: |
| // fmul |
| st[stack_ptr] = st0 * m32; |
| break; |
| case 2: |
| // fcom |
| fcom(m32); |
| break; |
| case 3: |
| // fcomp |
| fcom(m32); |
| pop(); |
| break; |
| case 4: |
| // fsub |
| st[stack_ptr] = st0 - m32; |
| break; |
| case 5: |
| // fsubr |
| st[stack_ptr] = m32 - st0; |
| break; |
| case 6: |
| // fdiv |
| st[stack_ptr] = st0 / m32; |
| break; |
| case 7: |
| // fdivr |
| st[stack_ptr] = m32 / st0; |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_D9_reg = function(imm8) |
| { |
| dbg_log_fpu_op(0xD9, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| low = imm8 & 7; |
| |
| switch(mod) |
| { |
| case 0: |
| // fld |
| var sti = get_sti(low); |
| push(sti); |
| break; |
| case 1: |
| // fxch |
| var sti = get_sti(low); |
| |
| st[stack_ptr + low & 7] = get_st0(); |
| st[stack_ptr] = sti; |
| break; |
| case 4: |
| switch(low) |
| { |
| case 0: |
| // fchs |
| st[stack_ptr] = -get_st0(); |
| break; |
| case 1: |
| // fabs |
| st[stack_ptr] = Math.abs(get_st0()); |
| break; |
| case 4: |
| ftst(); |
| break; |
| case 5: |
| fxam(); |
| break; |
| default: |
| dbg_log(low); fpu_unimpl(); |
| } |
| break; |
| case 5: |
| push(constants[low]); |
| break; |
| case 6: |
| switch(low) |
| { |
| case 0: |
| // f2xm1 |
| st[stack_ptr] = Math.pow(2, get_st0()) - 1; |
| break; |
| case 1: |
| // fyl2x |
| st[stack_ptr + 1 & 7] = get_sti(1) * Math.log(get_st0()) / Math.LN2; |
| pop(); |
| break; |
| case 2: |
| // fptan |
| st[stack_ptr] = Math.tan(get_st0()); |
| push(1); // no bug: push constant 1 |
| break; |
| case 3: |
| // fpatan |
| //st[stack_ptr + 1 & 7] = Math.atan(get_sti(1) / get_st0()); |
| st[stack_ptr + 1 & 7] = Math.atan2(get_sti(1), get_st0()); |
| pop(); |
| break; |
| case 5: |
| // fprem1 |
| st[stack_ptr] = get_st0() % get_sti(1); |
| break; |
| default: |
| dbg_log(low); fpu_unimpl(); |
| } |
| break; |
| case 7: |
| switch(low) |
| { |
| case 0: |
| // fprem |
| st[stack_ptr] = get_st0() % get_sti(1); |
| break; |
| case 1: |
| // fyl2xp1: y * log2(x+1) and pop |
| st[stack_ptr + 1 & 7] = get_sti(1) * Math.log(get_st0() + 1) / Math.LN2; |
| pop(); |
| break; |
| case 2: |
| st[stack_ptr] = Math.sqrt(get_st0()); |
| break; |
| case 3: |
| var st0 = get_st0(); |
| |
| st[stack_ptr] = Math.sin(st0); |
| push(Math.cos(st0)); |
| break; |
| case 4: |
| // frndint |
| st[stack_ptr] = integer_round(get_st0()); |
| break; |
| case 5: |
| // fscale |
| st[stack_ptr] = get_st0() * Math.pow(2, truncate(get_sti(1))); |
| break; |
| case 6: |
| st[stack_ptr] = Math.sin(get_st0()); |
| break; |
| case 7: |
| st[stack_ptr] = Math.cos(get_st0()); |
| break; |
| default: |
| dbg_log(low); fpu_unimpl(); |
| } |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_D9_mem = function(imm8, addr) |
| { |
| dbg_log_fpu_op(0xD9, imm8); |
| |
| var mod = imm8 >> 3 & 7; |
| |
| switch(mod) |
| { |
| case 0: |
| var data = load_m32(addr); |
| |
| push(data); |
| break; |
| case 2: |
| store_m32(addr, 0); |
| break; |
| case 3: |
| store_m32(addr, 0); |
| pop(); |
| break; |
| case 4: |
| fldenv(addr); |
| break; |
| case 5: |
| var word = safe_read16(addr); |
| control_word = word; |
| break; |
| case 6: |
| fstenv(addr); |
| break; |
| case 7: |
| safe_write16(addr, control_word); |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DA_reg = function(imm8) |
| { |
| dbg_log_fpu_op(0xDA, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| low = imm8 & 7; |
| |
| switch(mod) |
| { |
| case 0: |
| // fcmovb |
| if(test_b()) |
| { |
| st[stack_ptr] = get_sti(low); |
| stack_empty &= ~(1 << stack_ptr); |
| } |
| break; |
| case 1: |
| // fcmove |
| if(test_z()) |
| { |
| st[stack_ptr] = get_sti(low); |
| stack_empty &= ~(1 << stack_ptr); |
| } |
| break; |
| case 2: |
| // fcmovbe |
| if(test_be()) |
| { |
| st[stack_ptr] = get_sti(low); |
| stack_empty &= ~(1 << stack_ptr); |
| } |
| break; |
| case 3: |
| // fcmovu |
| if(test_p()) |
| { |
| st[stack_ptr] = get_sti(low); |
| stack_empty &= ~(1 << stack_ptr); |
| } |
| break; |
| case 5: |
| if(low === 1) |
| { |
| // fucompp |
| fucom(get_sti(1)); |
| pop(); |
| pop(); |
| } |
| else |
| { |
| dbg_log(mod); fpu_unimpl(); |
| } |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DA_mem = function(imm8, addr) |
| { |
| dbg_log_fpu_op(0xDA, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| m32 = safe_read32s(addr); |
| |
| var st0 = get_st0(); |
| |
| switch(mod) |
| { |
| case 0: |
| // fadd |
| st[stack_ptr] = st0 + m32; |
| break; |
| case 1: |
| // fmul |
| st[stack_ptr] = st0 * m32; |
| break; |
| case 2: |
| // fcom |
| fcom(m32); |
| break; |
| case 3: |
| // fcomp |
| fcom(m32); |
| pop(); |
| break; |
| case 4: |
| // fsub |
| st[stack_ptr] = st0 - m32; |
| break; |
| case 5: |
| // fsubr |
| st[stack_ptr] = m32 - st0; |
| break; |
| case 6: |
| // fdiv |
| st[stack_ptr] = st0 / m32; |
| break; |
| case 7: |
| // fdivr |
| st[stack_ptr] = m32 / st0; |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DB_reg = function(imm8) |
| { |
| dbg_log_fpu_op(0xDB, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| low = imm8 & 7; |
| |
| switch(mod) |
| { |
| case 0: |
| // fcmovnb |
| if(!test_b()) |
| { |
| st[stack_ptr] = get_sti(low); |
| stack_empty &= ~(1 << stack_ptr); |
| } |
| break; |
| case 1: |
| // fcmovne |
| if(!test_z()) |
| { |
| st[stack_ptr] = get_sti(low); |
| stack_empty &= ~(1 << stack_ptr); |
| } |
| break; |
| case 2: |
| // fcmovnbe |
| if(!test_be()) |
| { |
| st[stack_ptr] = get_sti(low); |
| stack_empty &= ~(1 << stack_ptr); |
| } |
| break; |
| case 3: |
| // fcmovnu |
| if(!test_p()) |
| { |
| st[stack_ptr] = get_sti(low); |
| stack_empty &= ~(1 << stack_ptr); |
| } |
| break; |
| case 4: |
| if(imm8 === 0xE3) |
| { |
| finit(); |
| } |
| else if(imm8 === 0xE4) |
| { |
| // fsetpm |
| // treat as nop |
| } |
| else if(imm8 === 0xE1) |
| { |
| // fdisi |
| // also treat as nop |
| } |
| else if(imm8 === 0xE2) |
| { |
| // fclex |
| status_word = 0; |
| } |
| else |
| { |
| dbg_log(h(imm8)); |
| fpu_unimpl(); |
| } |
| break; |
| case 5: |
| fucomi(get_sti(low)); |
| break; |
| case 6: |
| fcomi(get_sti(low)); |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DB_mem = function(imm8, addr) |
| { |
| dbg_log_fpu_op(0xDB, imm8); |
| |
| var mod = imm8 >> 3 & 7; |
| |
| switch(mod) |
| { |
| case 0: |
| // fild |
| var int32 = safe_read32s(addr); |
| push(int32); |
| break; |
| case 2: |
| // fist |
| var st0 = get_st0(); |
| if(st0 <= 0x7FFFFFFF && st0 >= -0x80000000) |
| { |
| // TODO: Invalid operation |
| safe_write32(addr, integer_round(st0)); |
| } |
| else |
| { |
| invalid_arithmatic(); |
| safe_write32(addr, 0x80000000); |
| } |
| break; |
| case 3: |
| // fistp |
| var st0 = get_st0(); |
| if(st0 <= 0x7FFFFFFF && st0 >= -0x80000000) |
| { |
| safe_write32(addr, integer_round(st0)); |
| } |
| else |
| { |
| invalid_arithmatic(); |
| safe_write32(addr, 0x80000000); |
| } |
| pop(); |
| break; |
| case 5: |
| // fld |
| push(load_m80(addr)); |
| break; |
| case 7: |
| // fstp |
| store_m80(addr, 0); |
| pop(); |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DC_reg = function(imm8) |
| { |
| dbg_log_fpu_op(0xDC, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| low = imm8 & 7, |
| low_ptr = stack_ptr + low & 7, |
| sti = get_sti(low), |
| st0 = get_st0(); |
| |
| switch(mod) |
| { |
| case 0: |
| // fadd |
| st[low_ptr] = sti + st0; |
| break; |
| case 1: |
| // fmul |
| st[low_ptr] = sti * st0; |
| break; |
| case 2: |
| // fcom |
| fcom(sti); |
| break; |
| case 3: |
| // fcomp |
| fcom(sti); |
| pop(); |
| break; |
| case 4: |
| // fsubr |
| st[low_ptr] = st0 - sti; |
| break; |
| case 5: |
| // fsub |
| st[low_ptr] = sti - st0; |
| break; |
| case 6: |
| // fdivr |
| st[low_ptr] = st0 / sti; |
| break; |
| case 7: |
| // fdiv |
| st[low_ptr] = sti / st0; |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DC_mem = function(imm8, addr) |
| { |
| dbg_log_fpu_op(0xDC, imm8); |
| |
| var |
| mod = imm8 >> 3 & 7, |
| m64 = load_m64(addr); |
| |
| var st0 = get_st0(); |
| |
| switch(mod) |
| { |
| case 0: |
| // fadd |
| st[stack_ptr] = st0 + m64; |
| break; |
| case 1: |
| // fmul |
| st[stack_ptr] = st0 * m64; |
| break; |
| case 2: |
| // fcom |
| fcom(m64); |
| break; |
| case 3: |
| // fcomp |
| fcom(m64); |
| pop(); |
| break; |
| case 4: |
| // fsub |
| st[stack_ptr] = st0 - m64; |
| break; |
| case 5: |
| // fsubr |
| st[stack_ptr] = m64 - st0; |
| break; |
| case 6: |
| // fdiv |
| st[stack_ptr] = st0 / m64; |
| break; |
| case 7: |
| // fdivr |
| st[stack_ptr] = m64 / st0; |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DD_reg = function(imm8) |
| { |
| dbg_log_fpu_op(0xDD, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| low = imm8 & 7; |
| |
| switch(mod) |
| { |
| case 0: |
| // ffree |
| stack_empty |= 1 << (stack_ptr + low & 7); |
| break; |
| case 2: |
| // fst |
| st[stack_ptr + low & 7] = get_st0(); |
| break; |
| case 3: |
| // fstp |
| if(low === 0) |
| { |
| pop(); |
| } |
| else |
| { |
| st[stack_ptr + low & 7] = get_st0(); |
| pop(); |
| } |
| break; |
| case 4: |
| fucom(get_sti(low)); |
| break; |
| case 5: |
| // fucomp |
| fucom(get_sti(low)); |
| pop(); |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DD_mem = function(imm8, addr) |
| { |
| dbg_log_fpu_op(0xDD, imm8); |
| |
| var mod = imm8 >> 3 & 7; |
| |
| switch(mod) |
| { |
| case 0: |
| // fld |
| var data = load_m64(addr); |
| push(data); |
| break; |
| case 2: |
| // fst |
| store_m64(addr, 0); |
| break; |
| case 3: |
| // fstp |
| store_m64(addr, 0); |
| pop(); |
| break; |
| case 4: |
| frstor(addr); |
| break; |
| case 6: |
| // fsave |
| fsave(addr); |
| break; |
| case 7: |
| // fnstsw / store status word |
| safe_write16(addr, load_status_word()); |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| |
| this.op_DE_reg = function(imm8) |
| { |
| dbg_log_fpu_op(0xDE, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| low = imm8 & 7, |
| low_ptr = stack_ptr + low & 7, |
| sti = get_sti(low), |
| st0 = get_st0(); |
| |
| switch(mod) |
| { |
| case 0: |
| // faddp |
| st[low_ptr] = sti + st0; |
| break; |
| case 1: |
| // fmulp |
| st[low_ptr] = sti * st0; |
| break; |
| case 2: |
| // fcomp |
| fcom(sti); |
| break; |
| case 3: |
| // fcompp |
| if(low === 1) |
| { |
| fcom(st[low_ptr]); |
| pop(); |
| } |
| else |
| { |
| // not a valid encoding |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| break; |
| case 4: |
| // fsubrp |
| st[low_ptr] = st0 - sti; |
| break; |
| case 5: |
| // fsubp |
| st[low_ptr] = sti - st0; |
| break; |
| case 6: |
| // fdivrp |
| st[low_ptr] = st0 / sti; |
| break; |
| case 7: |
| // fdivp |
| st[low_ptr] = sti / st0; |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| |
| pop(); |
| }; |
| |
| this.op_DE_mem = function(imm8, addr) |
| { |
| dbg_log_fpu_op(0xDE, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| m16 = safe_read16s(addr); |
| |
| var st0 = get_st0(); |
| |
| switch(mod) |
| { |
| case 0: |
| // fadd |
| st[stack_ptr] = st0 + m16; |
| break; |
| case 1: |
| // fmul |
| st[stack_ptr] = st0 * m16; |
| break; |
| case 2: |
| // fcom |
| fcom(m16); |
| break; |
| case 3: |
| // fcomp |
| fcom(m16); |
| pop(); |
| break; |
| case 4: |
| // fsub |
| st[stack_ptr] = st0 - m16; |
| break; |
| case 5: |
| // fsubr |
| st[stack_ptr] = m16 - st0; |
| break; |
| case 6: |
| // fdiv |
| st[stack_ptr] = st0 / m16; |
| break; |
| case 7: |
| // fdivr |
| st[stack_ptr] = m16 / st0; |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DF_reg = function(imm8) |
| { |
| dbg_log_fpu_op(0xDF, imm8); |
| |
| var mod = imm8 >> 3 & 7, |
| low = imm8 & 7; |
| |
| switch(mod) |
| { |
| case 4: |
| if(imm8 === 0xE0) |
| { |
| // fnstsw |
| reg16[reg_ax] = load_status_word(); |
| } |
| else |
| { |
| dbg_log(imm8); |
| fpu_unimpl(); |
| } |
| break; |
| case 5: |
| // fucomip |
| fucomi(get_sti(low)); |
| pop(); |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| |
| this.op_DF_mem = function(imm8, addr) |
| { |
| dbg_log_fpu_op(0xDF, imm8); |
| |
| var mod = imm8 >> 3 & 7; |
| |
| switch(mod) |
| { |
| case 0: |
| var m16 = safe_read16s(addr); |
| |
| push(m16); |
| break; |
| case 2: |
| // fist |
| var st0 = get_st0(); |
| if(st0 <= 0x7FFF && st0 >= -0x8000) |
| { |
| safe_write16(addr, integer_round(st0)); |
| } |
| else |
| { |
| invalid_arithmatic(); |
| safe_write16(addr, 0x8000); |
| } |
| break; |
| case 3: |
| // fistp |
| var st0 = get_st0(); |
| if(st0 <= 0x7FFF && st0 >= -0x8000) |
| { |
| safe_write16(addr, integer_round(st0)); |
| } |
| else |
| { |
| invalid_arithmatic(); |
| safe_write16(addr, 0x8000); |
| } |
| pop(); |
| break; |
| case 5: |
| // fild |
| var low = safe_read32(addr); |
| |
| var high = safe_read32(addr + 4); |
| |
| var m64 = low + 0x100000000 * high; |
| |
| if(high >> 31) |
| { |
| m64 -= 0x10000000000000000; |
| } |
| |
| push(m64); |
| break; |
| case 7: |
| // fistp |
| var st0 = integer_round(get_st0()); |
| |
| if(!(st0 <= 0x7FFFFFFFFFFFFFFF && st0 >= -0x8000000000000000)) |
| { |
| st0 = 0x8000000000000000; |
| invalid_arithmatic(); |
| } |
| pop(); |
| safe_write32(addr, st0); |
| |
| st0 /= 0x100000000; |
| |
| if(st0 < 0 && st0 > -1) |
| st0 = -1; |
| |
| safe_write32(addr + 4, st0); |
| break; |
| default: |
| dbg_log(mod); |
| fpu_unimpl(); |
| } |
| }; |
| } |