v86/tests/nasm/create_tests.js
2020-12-31 19:14:30 -06:00

419 lines
10 KiB
JavaScript
Executable file

#!/usr/bin/env node
"use strict";
// TODO
// - multiple random tests
const assert = require("assert").strict;
const fs = require("fs");
const encodings = require("../../gen/x86_table.js");
const Prand = require("./prand.js");
generate_tests();
function generate_tests()
{
const build_folder = __dirname + "/build/";
try
{
fs.mkdirSync(build_folder);
}
catch(e)
{
if(e.code !== "EEXIST")
{
throw e;
}
}
for(const op of encodings)
{
const configurations = [
{ mem: 0, size: 16, },
{ mem: 0, size: 32, },
{ mem: 1, size: 16, },
{ mem: 1, size: 32, },
];
let i = 0;
for(const config of configurations)
{
for(const code of create_nasm(op, config))
{
const filename = "gen_" + format_opcode(op.opcode) + "_" + (op.fixed_g || 0) + "_" + i + ".asm";
const dirname = build_folder + filename;
let old_code = undefined;
try
{
old_code = fs.readFileSync(dirname, { encoding: "ascii" });
}
catch(e)
{
}
if(old_code !== code)
{
console.log("Creating %s", filename);
fs.writeFileSync(dirname, code);
}
else
{
console.log("Unchanged: %s", filename);
}
i++;
}
}
}
}
function format_opcode(n)
{
let x = n.toString(16);
return (x.length === 1 || x.length === 3) ? "0" + x : x;
}
function create_nasm_modrm_combinations_16()
{
let result = [];
for(let modrm = 0; modrm < 0xC0; modrm++)
{
let mod = modrm >> 6;
let rm = modrm & 7;
let has_imm8 = mod === 1;
let has_imm16 = mod === 2 || rm === 6 && mod === 0;
assert(!has_imm8 || !has_imm16);
let line = ["db " + modrm];
if(has_imm8) line.push("db 9ah");
if(has_imm16) line.push("dw 9a1fh");
result.push(line);
}
return result;
}
function create_nasm_modrm_combinations_32()
{
let result = [];
let sample_sib_bytes = [0x05, 0x65, 0xAD, 0xCD, 0x20, 0xFF];
let exhaustive_sib_bytes = [];
for(let sib = 0; sib < 0x100; sib++) exhaustive_sib_bytes.push(sib);
for(let modrm = 0; modrm < 0xC0; modrm++)
{
let mod = modrm >> 6;
let reg = modrm >> 3 & 7;
let rm = modrm & 7;
let has_imm8 = mod === 1;
let has_imm32 = mod === 2 || rm === 5 && mod === 0;
let has_sib = rm === 4;
assert(!has_imm8 || !has_imm32);
if(has_sib)
{
// avoid generating an excessive number of tests
let sib_bytes = reg === 0 ? exhaustive_sib_bytes : sample_sib_bytes;
for(let sib of sib_bytes)
{
let line = ["db " + modrm, "db " + sib];
if(has_imm8) line.push("db 9ah");
if(has_imm32 || mod === 0 && (sib & 7) === 5) line.push("dd 9a1fbcdeh");
result.push(line);
}
}
else
{
let line = ["db " + modrm];
if(has_imm8) line.push("db 9ah");
if(has_imm32) line.push("dd 9a1fbcdeh");
result.push(line);
}
}
return result;
}
function create_nasm(op, config)
{
const op_rand = new Prand(op.opcode);
if(op.prefix || op.skip)
{
return [];
}
if(config.mem ? op.only_reg : op.only_mem)
{
// illegal opcode
return [];
}
if(!op.e)
{
if(config.mem)
{
// doesn't use memory, don't test both
return [];
}
}
if(!op.os)
{
if(config.size === 16)
{
// equivalent to 32-bit version, don't test both
return [];
}
}
const size = (op.os || op.opcode % 2 === 1) ? config.size : 8;
const is_modrm = op.e || op.fixed_g !== undefined;
const codes = [];
for(let reg of ["eax", "ecx", "edx", "ebx", "ebp", "esi", "edi"])
{
let rand = op_rand.next();
codes.push("mov " + reg + ", " + rand);
}
if(!op.is_fpu) // generate random mmx registers
{
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("movq mm" + i + ", [esp]");
}
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
{
codes.push("sub esp, 16");
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("mov dword [esp + 8], " + op_rand.next());
codes.push("mov dword [esp + 12], " + op_rand.next());
codes.push("movdqu xmm" + i + ", [esp]");
}
codes.push("add esp, 16");
}
if(true) // generate random stack memory
{
for(let i = 0; i < 8; i++)
{
codes.push("sub esp, 4");
codes.push("mov dword [esp], " + op_rand.next());
}
}
codes.push("push dword " + (op_rand.next() & ~(1 << 8 | 1 << 9)));
codes.push("popf");
if(true)
{
// generate random flags using arithmatic instruction
// not well-distributed, but can trigger bugs in lazy flag calculation
if(true)
{
// rarely sets zero flag, other flags mostly well-distributed
codes.push("add al, ah");
}
else
{
// always sets zero flag
codes.push("sub al, al");
}
}
if(op.is_string)
{
codes.push("mov ecx, 3");
codes.push("mov edi, (120000h-16)");
codes.push("mov esi, (120000h-20)");
}
if(size === 16)
{
codes.push("db 66h ; 16 bit");
}
let opcode = op.opcode;
if(opcode === 0x8D)
{
// special case: lea: generate 16-bit addressing and all modrm combinations
assert(is_modrm);
codes.push([].concat(
create_nasm_modrm_combinations_16().map(lines => ["db 67h", "db 8dh"].concat(lines).join("\n")),
create_nasm_modrm_combinations_32().map(lines => ["db 8dh"].concat(lines).join("\n"))
));
}
else
{
assert(opcode < 0x1000000);
if(opcode >= 0x10000)
{
let c = opcode >> 16;
assert(c === 0x66 || c === 0xF3 || c === 0xF2);
codes.push("db " + c);
opcode &= ~0xFF0000;
}
if(opcode >= 0x100)
{
let c = opcode >> 8;
assert(c === 0x0F || c === 0xF2 || c === 0xF3, "Expected 0F, F2, or F3 prefix, got " + c.toString(16));
codes.push("db " + c);
opcode &= ~0xFF00;
}
codes.push("db " + opcode);
if(is_modrm)
{
let g = 7; // edi / di / bh
if(op.fixed_g !== undefined)
{
g = op.fixed_g;
}
if(config.mem)
{
const e = 0x04; // [esp]
const sib = 0x24;
codes.push("db " + (e | g << 3));
codes.push("db " + sib);
}
else // op.only_mem
{
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);
}
}
}
if(op.opcode === 0xC8) // special case: enter
{
codes.push("dw 8h");
codes.push("db 0h");
}
else if(op.imm8 || op.imm8s || op.imm16 || op.imm1632 || op.imm32 || op.immaddr)
{
if(op.imm8 || op.imm8s)
{
codes.push("db 12h");
}
else
{
if(op.immaddr)
{
// immaddr: depends on address size
// generate valid pointer into bss section
codes.push("dd (120000h-16)");
}
else
{
assert(op.imm1632 || op.imm16 || op.imm32);
if(op.imm1632 && size === 16 || op.imm16)
{
codes.push("dw 34cdh");
}
else
{
assert(op.imm1632 && size === 32 || op.imm32);
codes.push("dd 1234abcdh");
}
}
}
}
if(op.mask_flags)
{
codes.push(
"pushf",
"and dword [esp], ~" + op.mask_flags,
"popf"
);
}
return all_combinations(codes).map(c => {
return (
"global _start\n" +
'%include "header.inc"\n\n' +
c.join("\n") + "\n" +
'%include "footer.inc"\n'
);
});
}
function all_combinations(xs)
{
let result = [xs];
for(let i = 0; i < xs.length; i++)
{
let x = xs[i];
if(x instanceof Array)
{
let new_result = [];
for(let r of result)
{
for(let x_ of x)
{
r = r.slice();
r[i] = x_;
new_result.push(r);
}
}
result = new_result;
}
}
return result;
}