Enable fpu instructions in nasm tests

This commit is contained in:
Fabian 2018-12-03 13:42:24 -06:00
parent bb0e58ace9
commit 05296b0586
5 changed files with 260 additions and 42 deletions

View file

@ -273,6 +273,78 @@ const encodings = [
// XXX: Temporary block boundary
{ opcode: 0xD7, skip: 1, block_boundary: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 2, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 3, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 5, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 2, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 3, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, only_reg: 1 }, // fldenv (only memory). TODO: Make reg variant non-block-boundary
{ opcode: 0xD9, e: 1, fixed_g: 5, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, skip: 1, }, // fstenv (mem), fprem (reg)
{ opcode: 0xD9, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, only_mem: 1, }, // fprem
{ opcode: 0xDA, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 2, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 3, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 5, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, only_reg: 1, }, // unimplemented: fisttp (sse3)
{ opcode: 0xDB, e: 1, fixed_g: 2, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 3, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 5, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 2, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 3, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 5, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, only_reg: 1, }, // XXX: Test should fail
{ opcode: 0xDD, e: 1, fixed_g: 2, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 3, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, only_reg: 1, }, // frstor
{ opcode: 0xDD, e: 1, fixed_g: 5, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, only_reg: 1, }, // fsave
{ opcode: 0xDD, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 2, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 3, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 5, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDF, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1 },
{ opcode: 0xDF, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, only_reg: 1 }, // unimplemented: fisttp (sse3)
{ opcode: 0xDF, e: 1, fixed_g: 2, custom: 0, is_fpu: 1, task_switch_test: 1 },
{ opcode: 0xDF, e: 1, fixed_g: 3, custom: 0, is_fpu: 1, task_switch_test: 1 },
{ opcode: 0xDF, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, only_reg: 1 }, // mem exists, but not implemented
{ opcode: 0xDF, e: 1, fixed_g: 5, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDF, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, only_reg: 1 }, // mem exists, but not implemented
{ opcode: 0xDF, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, },
// loop, jcxz, etc.
// Conditional jumps, but condition code not supported by code generator
// (these are never generated by modern compilers)
@ -722,7 +794,7 @@ const encodings = [
{ sse: 1, opcode: 0x660F75, e: 1, },
{ sse: 1, opcode: 0x0F76, e: 1, },
{ sse: 1, opcode: 0x660F76, e: 1, },
{ sse: 1, opcode: 0x0F77 },
{ sse: 1, opcode: 0x0F77, skip: 1 }, // emms (skip as it breaks gdb printing of float registers)
// vmx instructions
{ opcode: 0x0F78, skip: 1, block_boundary: 1, },
@ -892,15 +964,6 @@ for(let i = 0; i < 8; i++)
{ opcode: 0xD1, os: 1, e: 1, fixed_g: i, mask_flags: af, custom: 1, },
{ opcode: 0xD2, e: 1, fixed_g: i, mask_flags: of | af, },
{ opcode: 0xD3, os: 1, e: 1, fixed_g: i, mask_flags: of | af, custom: 1, },
{ opcode: 0xD8, e: 1, fixed_g: i, skip: 1, task_switch_test: 1, },
{ opcode: 0xD9, e: 1, fixed_g: i, skip: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: i, skip: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: i, skip: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: i, skip: 1, task_switch_test: 1, },
{ opcode: 0xDD, e: 1, fixed_g: i, skip: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: i, skip: 1, task_switch_test: 1, },
{ opcode: 0xDF, e: 1, fixed_g: i, skip: 1, task_switch_test: 1, },
]);
}

View file

@ -290,6 +290,9 @@ CPU.prototype.wasm_patch = function(wm)
this.set_stack_reg = get_import("set_stack_reg");
this.fpu_load_tag_word = get_import("fpu_load_tag_word");
this.fpu_load_status_word = get_import("fpu_load_status_word");
this.translate_address_read = get_import("translate_address_read_js");
this.translate_address_system_read = get_import("translate_address_system_read_js");
this.translate_address_system_write = get_import("translate_address_system_write_js");

View file

@ -188,7 +188,7 @@ function create_nasm(op, config)
codes.push("mov " + reg + ", " + rand);
}
if(true) // generate random mmx registers
if(!op.is_fpu) // generate random mmx registers
{
codes.push("sub esp, 8");
for(let i = 0; i < 8; i++)
@ -199,6 +199,25 @@ function create_nasm(op, config)
}
codes.push("add esp, 8");
}
else // generate random fpu registers
{
codes.push("finit");
codes.push("sub esp, 8");
for(let i = 0; i < 8; i++)
{
codes.push("mov dword [esp], " + op_rand.next());
codes.push("mov dword [esp + 4], " + op_rand.next());
codes.push("fld qword [esp]");
}
for(let i = 0; i < 4; i++) // half full stack
{
codes.push("fstp qword [esp]");
}
codes.push("add esp, 8");
}
if(true) // generate random xmm registers
{
@ -294,24 +313,21 @@ function create_nasm(op, config)
g = op.fixed_g;
}
let e;
let sib;
if(config.mem)
{
e = 0x04; // [esp]
sib = 0x24;
const e = 0x04; // [esp]
const sib = 0x24;
codes.push("db " + (e | g << 3));
codes.push("db " + sib);
}
else // op.only_mem
{
e = 0xc2; // edx
sib = "<invalid>";
}
codes.push("db " + (e | g << 3));
if(e < 0xC0)
{
codes.push("db " + sib);
const es = op.is_fpu ? [0, 1, 2, 3, 4, 5, 6, 7] : [
2 // edx
];
const modrm_bytes = es.map(e => "db " + (0xC0 | g << 3 | e));
codes.push(modrm_bytes);
}
}
}

View file

@ -25,6 +25,52 @@ define extract-state
printf " %d,\n", $ebp
printf " %d,\n", $esi
printf " %d,\n", $edi
printf " \n"
# For fpu registers, check the tag register first. If the tag index is
# invalid and you try to access to corresponding register, gdb exits with an
# error.
if ($ftag & (3 << 0)) != (2 << 0)
printf " %.100e,\n", $st0
else
printf " \"invalid\","
end
if ($ftag & (3 << 2)) != (2 << 2)
printf " %.100e,\n", $st1
else
printf " \"invalid\","
end
if ($ftag & (3 << 4)) != (2 << 4)
printf " %.100e,\n", $st2
else
printf " \"invalid\","
end
if ($ftag & (3 << 6)) != (2 << 6)
printf " %.100e,\n", $st3
else
printf " \"invalid\","
end
if ($ftag & (3 << 8)) != (2 << 8)
printf " %.100e,\n", $st4
else
printf " \"invalid\","
end
if ($ftag & (3 << 10)) != (2 << 10)
printf " %.100e,\n", $st5
else
printf " \"invalid\","
end
if ($ftag & (3 << 12)) != (2 << 12)
printf " %.100e,\n", $st6
else
printf " \"invalid\","
end
if ($ftag & (3 << 14)) != (2 << 14)
printf " %.100e,\n", $st7
else
printf " \"invalid\","
end
printf " \n"
printf " %d,\n", $mm0.v2_int32[0]
@ -97,7 +143,9 @@ define extract-state
printf " %d,\n", *(int*)($STACK_TOP-4)
printf " \n"
printf " %d\n", $eflags
printf " %d,\n", $eflags
printf " %d,\n", $ftag
printf " %d\n", $fstat
printf "]\n"
printf "---END JSON---\n"

View file

@ -31,7 +31,16 @@ const TERMINATE_MSG = "DONE";
const FORCE_JIT = process.argv.includes("--force-jit");
// alternative representation for infinity for json
const JSON_POS_INFINITY = "+INFINITY";
const JSON_NEG_INFINITY = "-INFINITY";
const JSON_POS_NAN = "+NAN";
const JSON_NEG_NAN = "-NAN";
const MASK_ARITH = 1 | 1 << 2 | 1 << 4 | 1 << 6 | 1 << 7 | 1 << 11;
const FPU_TAG_ALL_INVALID = 0xAAAA;
const FPU_STATUS_MASK = 0xFFFF & ~(1 << 9 | 1 << 5 | 1 << 3); // bits that are not correctly implemented by v86
const FP_COMPARISON_SIGNIFICANT_DIGITS = 7;
try {
var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
@ -43,6 +52,39 @@ catch(e) {
process.exit(1);
}
function float_equal(x, y)
{
console.assert(typeof x === "number");
console.assert(typeof y === "number");
if(x === Infinity && y === Infinity || x === -Infinity && y === -Infinity || isNaN(x) && isNaN(y))
{
return true;
}
const epsilon = Math.pow(10, -FP_COMPARISON_SIGNIFICANT_DIGITS);
return Math.abs(x - y) < epsilon;
}
function format_value(v)
{
if(typeof v === "number")
{
if((v >>> 0) !== v)
{
return String(v);
}
else
{
return "0x" + (v >>> 0).toString(16);
}
}
else
{
return String(v);
}
}
if(cluster.isMaster)
{
function extract_json(name, fixture_text)
@ -67,7 +109,13 @@ if(cluster.isMaster)
throw new Error("Test was killed during execution by gdb: " + name + "\n" + fixture_text);
}
const json_regex = /---BEGIN JSON---([\s\[\]\w":\-,]*)---END JSON---/;
fixture_text = fixture_text.toString()
.replace(/-inf\b/g, JSON.stringify(JSON_NEG_INFINITY))
.replace(/\binf\b/g, JSON.stringify(JSON_POS_INFINITY))
.replace(/-nan\b/g, JSON.stringify(JSON_NEG_NAN))
.replace(/\bnan\b/g, JSON.stringify(JSON_POS_NAN));
const json_regex = /---BEGIN JSON---([\s\[\]\.\+\w":\-,]*)---END JSON---/;
const regex_match = json_regex.exec(fixture_text);
if (!regex_match || regex_match.length < 2) {
throw new Error("Could not find JSON in fixture text: " + fixture_text + "\nTest: " + name);
@ -180,15 +228,9 @@ if(cluster.isMaster)
console.error("\n[-] %s:", test_failure.img_name);
test_failure.failures.forEach(function(failure) {
function format_value(v) {
if(typeof v === "number")
return "0x" + (v >>> 0).toString(16);
else
return String(v);
}
console.error("\n\t" + failure.name);
console.error("\tActual: " + format_value(failure.actual));
console.error("\tExpected: " + format_value(failure.expected));
console.error("\tActual: " + failure.actual);
console.error("\tExpected: " + failure.expected);
});
});
process.exit(1);
@ -300,23 +342,36 @@ else {
emulator.stop();
var cpu = emulator.v86.cpu;
const filename = TEST_DIR + current_test.img_name;
const evaluated_fpu_regs = new Float64Array(8).map((_, i) => cpu.fpu_st[i + cpu.fpu_stack_ptr[0] & 7]);
const evaluated_mmxs = cpu.reg_mmxs;
const evaluated_xmms = cpu.reg_xmm32s;
const esp = cpu.reg32s[4];
const evaluated_memory = new Int32Array(cpu.mem8.slice(0x120000 - 16 * 4, 0x120000).buffer);
const evaluated_fpu_tag = cpu.fpu_load_tag_word();
const evaluated_fpu_status = cpu.fpu_load_status_word() & FPU_STATUS_MASK;
let individual_failures = [];
console.assert(current_test.fixture.array || current_test.fixture.exception);
const FLOAT_TRANSLATION = {
[JSON_POS_INFINITY]: Infinity,
[JSON_NEG_INFINITY]: -Infinity,
[JSON_POS_NAN]: NaN,
[JSON_NEG_NAN]: NaN, // XXX: Ignore sign of NaN
};
if(current_test.fixture.array)
{
let offset = 0;
const expected_reg32s = current_test.fixture.array.slice(offset, offset += 8);
const expected_fpu_regs =
current_test.fixture.array.slice(offset, offset += 8) .map(x => x in FLOAT_TRANSLATION ? FLOAT_TRANSLATION[x] : x);
const expected_mmx_registers = current_test.fixture.array.slice(offset, offset += 16);
const expected_xmm_registers = current_test.fixture.array.slice(offset, offset += 32);
const expected_memory = current_test.fixture.array.slice(offset, offset += 16);
const expected_eflags = current_test.fixture.array[offset] & MASK_ARITH;
const expected_eflags = current_test.fixture.array[offset++] & MASK_ARITH;
const fpu_tag = current_test.fixture.array[offset++];
const fpu_status = current_test.fixture.array[offset++] & FPU_STATUS_MASK;
for (let i = 0; i < cpu.reg32s.length; i++) {
let reg = cpu.reg32s[i];
@ -329,15 +384,40 @@ else {
}
}
for (let i = 0; i < evaluated_mmxs.length; i++) {
if (evaluated_mmxs[i] !== expected_mmx_registers[i]) {
if(fpu_tag !== FPU_TAG_ALL_INVALID)
{
for (let i = 0; i < evaluated_fpu_regs.length; i++) {
if (expected_fpu_regs[i] !== "invalid" &&
!float_equal(evaluated_fpu_regs[i], expected_fpu_regs[i])) {
individual_failures.push({
name: "st" + i,
expected: expected_fpu_regs[i],
actual: evaluated_fpu_regs[i],
});
}
}
if(fpu_status !== evaluated_fpu_status)
{
individual_failures.push({
name: "mm" + (i >> 1) + ".int32[" + (i & 1) + "] (cpu.reg_mmx[" + i + "])",
expected: expected_mmx_registers[i],
actual: evaluated_mmxs[i],
name: "fpu status word",
expected: fpu_status,
actual: evaluated_fpu_status,
});
}
}
else
{
for (let i = 0; i < evaluated_mmxs.length; i++) {
if (evaluated_mmxs[i] !== expected_mmx_registers[i]) {
individual_failures.push({
name: "mm" + (i >> 1) + ".int32[" + (i & 1) + "] (cpu.reg_mmx[" + i + "])",
expected: expected_mmx_registers[i],
actual: evaluated_mmxs[i],
});
}
}
}
for (let i = 0; i < evaluated_xmms.length; i++) {
if (evaluated_xmms[i] !== expected_xmm_registers[i]) {
@ -379,6 +459,14 @@ else {
});
}
individual_failures = individual_failures.map(({ name, actual, expected }) => {
return {
name,
actual: format_value(actual),
expected: format_value(expected),
};
});
recorded_exceptions = [];
if (individual_failures.length > 0) {