mirror of
https://github.com/asmjit/asmjit.git
synced 2025-12-17 04:24:37 +03:00
* Removed AVX512_ER, AVX512_PF, AVX512_4FMAPS, and AVX512_4VNNIW
extensions and corresponding instructions (these were never
advertised by any x86 CPU and were only used by Xeon Phi acc.,
which AsmJit never supported)
* Removed CPU extensions HLE, MPX, and TSX
* Kept extension RTM, which is only for backward compatibility to
recognize instructions, but it's no longer checked by CpuInfo as
it's been deprecated together with HLE and MPX
* The xtest instruction now reports it requires RTM
* Reorganized x86 extensions a bit - they are now reordered to group
them by category, preparing for the future where extension IDs will
be always added after existing records for ABI compatibility
* Instruction vcvtneps2bf16 no longer accepts form without an explicit
memory operand size
* Removed aliased instructions in CMOVcc, Jcc, And SETcc categories,
now there is only a single instruction id for all aliased instructions.
* Added a new feature to always show instruction aliases in Logger, which
includes formatting instructio nodes (Builder, Compiler)
Instruction DB-only updates (not applied to C++ yet):
* AsmJit DB from now uses the same license as AsmJit (Zlib) and
no longer applies dual licensing (Zlib and Public Domain)
* Added support for aggregated instruction definitions in
x86 instruction database, which should simplify the maintenance
and reduce bugs (also the syntax is comparable to descriptions
used by Intel APX instruction manuals)
* Added support for APX instructions and new features
* Added support for AVX10.1 and AVX10.2 instructions (both new
instructions and new encodings of existing instructions)
* Added support for MOVRS instructions
* Added support for KL instructions (loadiwkey)
* Added support for AESKLE instructions
* Added support for AESKLEWIDE_KL instructions
* Added support for AMX_[AVX512|MOVRS|FP8|TF32|TRANSPOSE]
* NOTE: None of the instruction additions is currently used by
Asmjit, it's a pure database update that needs more work to
make all the instructions available in future AsmJit
2647 lines
85 KiB
JavaScript
2647 lines
85 KiB
JavaScript
// This file is part of AsmJit project <https://asmjit.com>
|
|
//
|
|
// See asmjit.h or LICENSE.md for license and copyright information
|
|
// SPDX-License-Identifier: Zlib
|
|
|
|
"use strict";
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const commons = require("./generator-commons.js");
|
|
const cxx = require("./generator-cxx.js");
|
|
const core = require("./tablegen.js");
|
|
|
|
const asmdb = core.asmdb;
|
|
|
|
const DEBUG = commons.DEBUG;
|
|
const FATAL = commons.FATAL;
|
|
const kIndent = commons.kIndent;
|
|
const ArrayUtils = commons.ArrayUtils;
|
|
const IndexedArray = commons.IndexedArray;
|
|
const ObjectUtils = commons.ObjectUtils;
|
|
const StringUtils = commons.StringUtils;
|
|
|
|
const hasOwn = Object.prototype.hasOwnProperty;
|
|
const disclaimer = StringUtils.disclaimer;
|
|
|
|
const decToHex = StringUtils.decToHex;
|
|
|
|
function readJSON(fileName) {
|
|
const content = fs.readFileSync(fileName);
|
|
return JSON.parse(content);
|
|
}
|
|
|
|
const x86data = readJSON(path.join(__dirname, "..", "db", asmdb.x86.dbName));
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.x86isa]
|
|
// ============================================================================
|
|
|
|
// Create the X86 database and add some special cases recognized by AsmJit.
|
|
const x86isa = new asmdb.x86.ISA(x86data);
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.Filter]
|
|
// ============================================================================
|
|
|
|
class Filter {
|
|
static unique(instArray) {
|
|
const result = [];
|
|
const known = {};
|
|
|
|
for (let i = 0; i < instArray.length; i++) {
|
|
const inst = instArray[i];
|
|
if (inst.altForm)
|
|
continue;
|
|
|
|
const s = inst.operands.map((op) => { return op.isImm() ? "imm" : op.toString(); }).join(", ");
|
|
if (known[s] === true)
|
|
continue;
|
|
|
|
known[s] = true;
|
|
result.push(inst);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static noAltForm(instArray) {
|
|
const result = [];
|
|
for (let i = 0; i < instArray.length; i++) {
|
|
const inst = instArray[i];
|
|
if (inst.alt)
|
|
continue;
|
|
result.push(inst);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static byArch(instArray, arch) {
|
|
return instArray.filter(function(inst) {
|
|
return inst.arch === "ANY" || inst.arch === arch;
|
|
});
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.GenUtils]
|
|
// ============================================================================
|
|
|
|
const VexToEvexMap = {
|
|
"vbroadcastf128": "vbroadcastf32x4",
|
|
"vbroadcasti128": "vbroadcasti32x4",
|
|
"vextractf128": "vextractf32x4",
|
|
"vextracti128": "vextracti32x4",
|
|
"vinsertf128": "vinsertf32x4",
|
|
"vinserti128": "vinserti32x4",
|
|
"vmovdqa": "vmovdqa32",
|
|
"vmovdqu": "vmovdqu32",
|
|
"vpand": "vpandd",
|
|
"vpandn": "vpandnd",
|
|
"vpor": "vpord",
|
|
"vpxor": "vpxord",
|
|
"vroundpd": "vrndscalepd",
|
|
"vroundps": "vrndscaleps",
|
|
"vroundsd": "vrndscalesd",
|
|
"vroundss": "vrndscaless"
|
|
};
|
|
|
|
class GenUtils {
|
|
static cpuArchOf(dbInsts) {
|
|
let anyArch = false;
|
|
let x86Arch = false;
|
|
let x64Arch = false;
|
|
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
if (dbInst.arch === "ANY") anyArch = true;
|
|
if (dbInst.arch === "X86") x86Arch = true;
|
|
if (dbInst.arch === "X64") x64Arch = true;
|
|
}
|
|
|
|
return anyArch || (x86Arch && x64Arch) ? "" : x86Arch ? "(X86)" : "(X64)";
|
|
}
|
|
|
|
static cpuFeaturesOf(dbInsts) {
|
|
function cmp(a, b) {
|
|
if (a.startsWith("AVX512") && !b.startsWith("AVX512"))
|
|
return 1;
|
|
if (b.startsWith("AVX512") && !a.startsWith("AVX512"))
|
|
return -1;
|
|
|
|
if (a.startsWith("AVX") && !b.startsWith("AVX"))
|
|
return 1;
|
|
if (b.startsWith("AVX") && !a.startsWith("AVX"))
|
|
return -1;
|
|
|
|
if (a === "FPU" && b !== "FPU")
|
|
return 1;
|
|
if (b === "FPU" && a !== "FPU")
|
|
return -1;
|
|
|
|
return a < b ? -1 : a === b ? 0 : 1;
|
|
}
|
|
|
|
const features = Object.getOwnPropertyNames(dbInsts.unionCpuFeatures());
|
|
features.sort(cmp);
|
|
return features;
|
|
}
|
|
|
|
static assignVexEvexCompatibilityFlags(f, dbInsts) {
|
|
const vexInsts = dbInsts.filter((inst) => { return inst.prefix === "VEX"; });
|
|
const evexInsts = dbInsts.filter((inst) => { return inst.prefix === "EVEX"; });
|
|
|
|
function isCompatible(vexInst, evexInst) {
|
|
if (vexInst.operands.length !== evexInst.operands.length)
|
|
return false;
|
|
|
|
for (let i = 0; i < vexInst.operands.length; i++) {
|
|
const vexOp = vexInst.operands[i];
|
|
const evexOp = evexInst.operands[i];
|
|
|
|
if (vexOp.data === evexOp.data)
|
|
continue;
|
|
|
|
if (vexOp.reg && vexOp.reg === evexOp.reg)
|
|
continue;
|
|
if (vexOp.mem && vexOp.mem === evexOp.mem)
|
|
continue;
|
|
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
let compatible = 0;
|
|
for (const vexInst of vexInsts) {
|
|
for (const evexInst of evexInsts) {
|
|
if (isCompatible(vexInst, evexInst)) {
|
|
compatible++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (compatible == vexInsts.length) {
|
|
f.EvexCompat = true;
|
|
return true;
|
|
}
|
|
|
|
if (evexInsts[0].operands[0].reg === "k") {
|
|
f.EvexKReg = true;
|
|
return true;
|
|
}
|
|
|
|
if (evexInsts[0].operands.length == 2 && vexInsts[0].operands.length === 3) {
|
|
f.EvexTwoOp = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static flagsOf(dbInsts) {
|
|
const f = Object.create(null);
|
|
|
|
let mib = dbInsts.length > 0 && /^(?:bndldx|bndstx)$/.test(dbInsts[0].name);
|
|
if (mib)
|
|
f.Mib = true;
|
|
|
|
let mmx = false;
|
|
let vec = false;
|
|
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
const operands = dbInst.operands;
|
|
|
|
if (dbInst.name === "emms")
|
|
mmx = true;
|
|
|
|
if (dbInst.name === "vzeroall" || dbInst.name === "vzeroupper")
|
|
vec = true;
|
|
|
|
for (let j = 0; j < operands.length; j++) {
|
|
const op = operands[j];
|
|
if (op.reg === "mm")
|
|
mmx = true;
|
|
else if (/^(xmm|ymm|zmm)$/.test(op.reg)) {
|
|
vec = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mmx) f.Mmx = true;
|
|
if (vec) f.Vec = true;
|
|
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
const operands = dbInst.operands;
|
|
|
|
if (dbInst.prefixes.lock ) f.Lock = true;
|
|
if (dbInst.prefixes.xacquire ) f.XAcquire = true;
|
|
if (dbInst.prefixes.xrelease ) f.XRelease = true;
|
|
if (dbInst.prefixes.bnd ) f.Rep = true;
|
|
if (dbInst.prefixes.rep ) f.Rep = true;
|
|
if (dbInst.prefixes.repne ) f.Rep = true;
|
|
if (dbInst.prefixes.repIgnore ) f.RepIgnored = true;
|
|
if (dbInst.k === "zeroing" ) f.Avx512ImplicitZ = true;
|
|
|
|
if (dbInst.category.FPU) {
|
|
for (let j = 0; j < operands.length; j++) {
|
|
const op = operands[j];
|
|
if (op.memSize === 16) f.FpuM16 = true;
|
|
if (op.memSize === 32) f.FpuM32 = true;
|
|
if (op.memSize === 64) f.FpuM64 = true;
|
|
if (op.memSize === 80) f.FpuM80 = true;
|
|
}
|
|
}
|
|
|
|
if (dbInst.tsib)
|
|
f.Tsib = true;
|
|
|
|
if (dbInst.vsibReg)
|
|
f.Vsib = true;
|
|
|
|
if (dbInst.prefix === "VEX" || dbInst.prefix === "XOP")
|
|
f.Vex = true;
|
|
|
|
if (dbInst.encodingPreference === "EVEX")
|
|
f.PreferEvex = true;
|
|
|
|
if (dbInst.prefix === "EVEX") {
|
|
f.Evex = true;
|
|
if (dbInst.kmask) f.Avx512K = true;
|
|
if (dbInst.zmask) f.Avx512Z = true;
|
|
|
|
if (dbInst.er) f.Avx512ER = true;
|
|
if (dbInst.sae) f.Avx512SAE = true;
|
|
|
|
if (dbInst.broadcast) f["Avx512B" + String(dbInst.elementSize)] = true;
|
|
if (dbInst.tupleType === "T1_4X") f.Avx512T4X = true;
|
|
}
|
|
|
|
if (VexToEvexMap[dbInst.name])
|
|
f.EvexTransformable = true;
|
|
}
|
|
|
|
if (f.Vex && f.Evex) {
|
|
GenUtils.assignVexEvexCompatibilityFlags(f, dbInsts)
|
|
}
|
|
|
|
const result = Object.getOwnPropertyNames(f);
|
|
result.sort();
|
|
return result;
|
|
}
|
|
|
|
static eqOps(aOps, aFrom, bOps, bFrom) {
|
|
let x = 0;
|
|
for (;;) {
|
|
const aIndex = x + aFrom;
|
|
const bIndex = x + bFrom;
|
|
|
|
const aOut = aIndex >= aOps.length;
|
|
const bOut = bIndex >= bOps.length;
|
|
|
|
if (aOut || bOut)
|
|
return !!(aOut && bOut);
|
|
|
|
const aOp = aOps[aIndex];
|
|
const bOp = bOps[bIndex];
|
|
|
|
if (aOp.data !== bOp.data)
|
|
return false;
|
|
|
|
x++;
|
|
}
|
|
}
|
|
|
|
// Prevent some instructions from having implicit memory size if that would
|
|
// make them ambiguous. There are some instructions where the ambiguity is
|
|
// okay, but some like 'push' and 'pop' where it isn't.
|
|
static canUseImplicitMemSize(name) {
|
|
switch (name) {
|
|
case "pop":
|
|
case "push":
|
|
return false;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static singleRegCase(name) {
|
|
switch (name) {
|
|
case "xchg" :
|
|
|
|
case "and" :
|
|
case "pand" : case "vpand" : case "vpandd" : case "vpandq" :
|
|
case "andpd" : case "vandpd" :
|
|
case "andps" : case "vandps" :
|
|
|
|
case "or" :
|
|
case "por" : case "vpor" : case "vpord" : case "vporq" :
|
|
case "orpd" : case "vorpd" :
|
|
case "orps" : case "vorps" :
|
|
|
|
case "pminsb" : case "vpminsb": case "pmaxsb" : case "vpmaxsb" :
|
|
case "pminsw" : case "vpminsw": case "pmaxsw" : case "vpmaxsw" :
|
|
case "pminsd" : case "vpminsd": case "pmaxsd" : case "vpmaxsd" :
|
|
case "pminub" : case "vpminub": case "pmaxub" : case "vpmaxub" :
|
|
case "pminuw" : case "vpminuw": case "pmaxuw" : case "vpmaxuw" :
|
|
case "pminud" : case "vpminud": case "pmaxud" : case "vpmaxud" :
|
|
return "RO";
|
|
|
|
case "pandn" : case "vpandn" : case "vpandnd" : case "vpandnq" :
|
|
|
|
case "xor" :
|
|
case "pxor" : case "vpxor" : case "vpxord" : case "vpxorq" :
|
|
case "xorpd" : case "vxorpd" :
|
|
case "xorps" : case "vxorps" :
|
|
|
|
case "kxnorb":
|
|
case "kxnord":
|
|
case "kxnorw":
|
|
case "kxnorq":
|
|
|
|
case "kxorb":
|
|
case "kxord":
|
|
case "kxorw":
|
|
case "kxorq":
|
|
|
|
case "sub" :
|
|
case "sbb" :
|
|
case "psubb" : case "vpsubb" :
|
|
case "psubw" : case "vpsubw" :
|
|
case "psubd" : case "vpsubd" :
|
|
case "psubq" : case "vpsubq" :
|
|
case "psubsb" : case "vpsubsb": case "psubusb" : case "vpsubusb" :
|
|
case "psubsw" : case "vpsubsw": case "psubusw" : case "vpsubusw" :
|
|
|
|
case "vpcmpeqb": case "pcmpeqb": case "vpcmpgtb": case "pcmpgtb" :
|
|
case "vpcmpeqw": case "pcmpeqw": case "vpcmpgtw": case "pcmpgtw" :
|
|
case "vpcmpeqd": case "pcmpeqd": case "vpcmpgtd": case "pcmpgtd" :
|
|
case "vpcmpeqq": case "pcmpeqq": case "vpcmpgtq": case "pcmpgtq" :
|
|
|
|
case "vpcmpb" : case "vpcmpub":
|
|
case "vpcmpd" : case "vpcmpud":
|
|
case "vpcmpw" : case "vpcmpuw":
|
|
case "vpcmpq" : case "vpcmpuq":
|
|
return "WO";
|
|
|
|
default:
|
|
return "None";
|
|
}
|
|
}
|
|
|
|
static fixedRegOfRegName(reg) {
|
|
switch (reg) {
|
|
case "es" : return 1;
|
|
case "cs" : return 2;
|
|
case "ss" : return 3;
|
|
case "ds" : return 4;
|
|
case "fs" : return 5;
|
|
case "gs" : return 6;
|
|
case "ah" : return 0;
|
|
case "ch" : return 1;
|
|
case "dh" : return 2;
|
|
case "bh" : return 3;
|
|
case "al" : case "ax": case "eax": case "rax": case "zax": return 0;
|
|
case "cl" : case "cx": case "ecx": case "rcx": case "zcx": return 1;
|
|
case "dl" : case "dx": case "edx": case "rdx": case "zdx": return 2;
|
|
case "bl" : case "bx": case "ebx": case "rbx": case "zbx": return 3;
|
|
case "spl" : case "sp": case "esp": case "rsp": case "zsp": return 4;
|
|
case "bpl" : case "bp": case "ebp": case "rbp": case "zbp": return 5;
|
|
case "sil" : case "si": case "esi": case "rsi": case "zsi": return 6;
|
|
case "dil" : case "di": case "edi": case "rdi": case "zdi": return 7;
|
|
case "st0" : return 0;
|
|
case "xmm0": return 0;
|
|
case "ymm0": return 0;
|
|
case "zmm0": return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static fixedRegOf(op) {
|
|
if (op.isReg()) {
|
|
return GenUtils.fixedRegOfRegName(op.reg);
|
|
}
|
|
else if (op.isMem() && op.memRegOnly) {
|
|
return GenUtils.fixedRegOfRegName(op.memRegOnly);
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static controlFlow(dbInsts) {
|
|
if (dbInsts.checkAttribute("control", "jump")) return "Jump";
|
|
if (dbInsts.checkAttribute("control", "call")) return "Call";
|
|
if (dbInsts.checkAttribute("control", "branch")) return "Branch";
|
|
if (dbInsts.checkAttribute("control", "return")) return "Return";
|
|
return "Regular";
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.X86TableGen]
|
|
// ============================================================================
|
|
|
|
class X86TableGen extends core.TableGen {
|
|
constructor() {
|
|
super("X86");
|
|
|
|
this.emitMissingString = "";
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// [Query]
|
|
// --------------------------------------------------------------------------
|
|
|
|
// Get instructions (dbInsts) having the same name as understood by AsmJit.
|
|
query(name) {
|
|
return x86isa.query({ name: name, filter: function(inst) {
|
|
return !inst.ext.APX_F && !inst.ext.AVX10_1 && !inst.ext.AVX10_2;
|
|
}});
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// [Parse / Merge]
|
|
// --------------------------------------------------------------------------
|
|
|
|
parse() {
|
|
const data = this.dataOfFile("src/asmjit/x86/x86instdb.cpp");
|
|
const re = new RegExp(
|
|
"INST\\(" +
|
|
"([A-Za-z0-9_]+)\\s*" + "," + // [01] Instruction.
|
|
"([^,]+)" + "," + // [02] Encoding.
|
|
"(.{26}[^,]*)" + "," + // [03] Opcode[0].
|
|
"(.{26}[^,]*)" + "," + // [04] Opcode[1].
|
|
// --- autogenerated fields ---
|
|
"([^\\)]+)" + "," + // [05] MainOpcodeIndex.
|
|
"([^\\)]+)" + "," + // [06] AltOpcodeIndex.
|
|
"([^\\)]+)" + "," + // [07] CommonDataIndex.
|
|
"([^\\)]+)" + "\\)", // [08] OperationDataIndex.
|
|
"g");
|
|
|
|
let m;
|
|
while ((m = re.exec(data)) !== null) {
|
|
let enum_ = m[1];
|
|
let name = enum_ === "None" ? "" : enum_.toLowerCase();
|
|
let encoding = m[2].trim();
|
|
let opcode0 = m[3].trim();
|
|
let opcode1 = m[4].trim();
|
|
|
|
const dbInsts = this.query(name);
|
|
if (name && !dbInsts.length)
|
|
FATAL(`Instruction '${name}' not found in asmdb`);
|
|
|
|
const flags = GenUtils.flagsOf(dbInsts);
|
|
const controlFlow = GenUtils.controlFlow(dbInsts);
|
|
const singleRegCase = GenUtils.singleRegCase(name);
|
|
|
|
const aliasData = x86isa.aliasData(name);
|
|
|
|
this.addInstruction({
|
|
id : 0, // Instruction id (numeric value).
|
|
name : name, // Instruction name.
|
|
displayName : name, // Instruction name to display.
|
|
enum : enum_, // Instruction enum without `kId` prefix.
|
|
dbInsts : dbInsts, // All dbInsts returned from asmdb query.
|
|
encoding : encoding, // Instruction encoding.
|
|
opcode0 : opcode0, // Primary opcode.
|
|
opcode1 : opcode1, // Secondary opcode.
|
|
flags : flags,
|
|
signatures : null, // Instruction signatures.
|
|
controlFlow : controlFlow,
|
|
singleRegCase : singleRegCase,
|
|
|
|
aliases : aliasData,
|
|
|
|
mainOpcodeValue : -1, // Main opcode value (0.255 hex).
|
|
mainOpcodeIndex : -1, // Index to InstDB::_mainOpcodeTable.
|
|
altOpcodeIndex : -1, // Index to InstDB::_altOpcodeTable.
|
|
nameIndex : -1, // Index to InstDB::_nameData.
|
|
commonInfoIndex : -1,
|
|
additionalInfoIndex: -1,
|
|
|
|
signatureIndex : -1,
|
|
signatureCount : -1
|
|
});
|
|
}
|
|
|
|
if (this.insts.length === 0)
|
|
FATAL("X86TableGen.parse(): Invalid parsing regexp (no data parsed)");
|
|
|
|
console.log("Number of Instructions: " + this.insts.length);
|
|
}
|
|
|
|
merge() {
|
|
let s = StringUtils.format(this.insts, "", true, function(inst) {
|
|
return "INST(" +
|
|
String(inst.enum ).padEnd(17) + ", " +
|
|
String(inst.encoding ).padEnd(19) + ", " +
|
|
String(inst.opcode0 ).padEnd(26) + ", " +
|
|
String(inst.opcode1 ).padEnd(26) + ", " +
|
|
String(inst.mainOpcodeIndex ).padEnd( 3) + ", " +
|
|
String(inst.altOpcodeIndex ).padEnd( 3) + ", " +
|
|
String(inst.commonInfoIndex ).padEnd( 3) + ", " +
|
|
String(inst.additionalInfoIndex).padEnd( 3) + ")";
|
|
}) + "\n";
|
|
this.inject("InstInfo", s, this.insts.length * 8);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// [Other]
|
|
// --------------------------------------------------------------------------
|
|
|
|
printMissing() {
|
|
const ignored = ArrayUtils.toDict([
|
|
"cmpsb", "cmpsw", "cmpsd", "cmpsq",
|
|
"lodsb", "lodsw", "lodsd", "lodsq",
|
|
"movsb", "movsw", "movsd", "movsq",
|
|
"scasb", "scasw", "scasd", "scasq",
|
|
"stosb", "stosw", "stosd", "stosq",
|
|
"insb" , "insw" , "insd" ,
|
|
"outsb", "outsw", "outsd",
|
|
"wait" // Maps to `fwait`, which AsmJit uses instead.
|
|
]);
|
|
|
|
let out = "";
|
|
x86isa.instructionNames.forEach(function(name) {
|
|
let dbInsts = x86isa.query(name);
|
|
if (!this.instMap[name] && ignored[name] !== true) {
|
|
console.log(`MISSING INSTRUCTION '${name}'`);
|
|
let inst = this.newInstFromGroup(dbInsts);
|
|
if (inst) {
|
|
out += " INST(" +
|
|
String(inst.enum ).padEnd(17) + ", " +
|
|
String(inst.encoding ).padEnd(19) + ", " +
|
|
String(inst.opcode0 ).padEnd(26) + ", " +
|
|
String(inst.opcode1 ).padEnd(26) + ", " +
|
|
String("0" ).padEnd( 3) + ", " +
|
|
String("0" ).padEnd( 3) + ", " +
|
|
String("0" ).padEnd( 5) + ", " +
|
|
String("0" ).padEnd( 3) + ", " +
|
|
String("0" ).padEnd( 3) + "),\n";
|
|
}
|
|
}
|
|
}, this);
|
|
console.log(out);
|
|
console.log(this.emitMissingString);
|
|
}
|
|
|
|
newInstFromGroup(dbInsts) {
|
|
function composeOpCode(obj) {
|
|
return `${obj.type}(${obj.prefix},${obj.opcode},${obj.o},${obj.l},${obj.w},${obj.ew},${obj.en},${obj.tt})`;
|
|
}
|
|
|
|
function GetAccess(dbInst) {
|
|
let operands = dbInst.operands;
|
|
if (!operands.length) return "";
|
|
|
|
let op = operands[0];
|
|
if (op.read && op.write)
|
|
return "RW";
|
|
else if (op.read)
|
|
return "RO";
|
|
else
|
|
return "WO";
|
|
}
|
|
|
|
function isVecPrefix(s) {
|
|
return s === "VEX" || s === "EVEX" || s === "XOP";
|
|
}
|
|
|
|
function formatEmit(dbi) {
|
|
const results = [];
|
|
const nameUp = dbi.name[0].toUpperCase() + dbi.name.substr(1);
|
|
|
|
for (let choice = 0; choice < 2; choice++) {
|
|
let s = `ASMJIT_INST_${dbi.operands.length}x(${dbi.name}, ${nameUp}`;
|
|
for (let j = 0; j < dbi.operands.length; j++) {
|
|
s += ", ";
|
|
const op = dbi.operands[j];
|
|
let reg = op.reg;
|
|
let mem = op.mem;
|
|
|
|
if (op.isReg() && op.isMem()) {
|
|
if (choice == 0) mem = null;
|
|
if (choice == 1) reg = null;
|
|
}
|
|
|
|
if (reg) {
|
|
if (reg === "xmm" || reg === "ymm" || reg === "zmm")
|
|
s += "Vec";
|
|
else if (reg === "k")
|
|
s += "KReg";
|
|
else if (reg === "r32" || reg === "r64" || reg === "r16" || reg === "r8")
|
|
s += "Gp";
|
|
else
|
|
s += reg;
|
|
}
|
|
else if (mem) {
|
|
s += "Mem";
|
|
}
|
|
else if (op.isImm()) {
|
|
s += "Imm";
|
|
}
|
|
else {
|
|
s += "Unknown";
|
|
}
|
|
}
|
|
s += `)`;
|
|
results.push(s);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
let dbi = dbInsts[0];
|
|
|
|
let id = this.insts.length;
|
|
let name = dbi.name;
|
|
let enum_ = name[0].toUpperCase() + name.substr(1);
|
|
|
|
let opcode = dbi.opcode.byte;
|
|
let modR = dbi.opcode.modr;
|
|
let mm = dbi.opcode.mm;
|
|
let pp = dbi.opcode.pp;
|
|
let encoding = dbi.encoding;
|
|
let isVec = isVecPrefix(dbi.prefix);
|
|
let evexCount = 0;
|
|
|
|
let access = GetAccess(dbi);
|
|
|
|
let vexL = undefined;
|
|
let vexW = undefined;
|
|
let evexW = undefined;
|
|
let cdshl = "_";
|
|
let tupleType = "_";
|
|
|
|
const tupleTypeToCDSHL = {
|
|
"FVM": "4",
|
|
"FV": "4",
|
|
"HVM": "3",
|
|
"HV": "3",
|
|
"QVM": "2",
|
|
"QV": "2",
|
|
"T1S": "?"
|
|
}
|
|
|
|
const emitMap = {};
|
|
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
dbi = dbInsts[i];
|
|
|
|
if (dbi.prefix === "VEX" || dbi.prefix === "XOP") {
|
|
let newVexL = String(dbi.opcode.l === "128" ? 0 : dbi.opcode.l === "256" ? 1 : dbi.opcode.l === "512" ? 2 : "_");
|
|
let newVexW = String(dbi.opcode.w === "W0" ? 0 : dbi.opcode.w === "W1" ? 1 : "_");
|
|
|
|
if (vexL !== undefined && vexL !== newVexL)
|
|
vexL = "x";
|
|
else
|
|
vexL = newVexL;
|
|
if (vexW !== undefined && vexW !== newVexW)
|
|
vexW = "x";
|
|
else
|
|
vexW = newVexW;
|
|
}
|
|
|
|
if (dbi.prefix === "EVEX") {
|
|
evexCount++;
|
|
let newEvexW = String(dbi.opcode.w === "W0" ? 0 : dbi.opcode.w === "W1" ? 1 : "_");
|
|
if (evexW !== undefined && evexW !== newEvexW)
|
|
evexW = "x";
|
|
else
|
|
evexW = newEvexW;
|
|
|
|
if (dbi.tupleType) {
|
|
if (tupleType !== "_" && tupleType !== dbi.tupleType) {
|
|
console.log(`${dbi.name}: WARNING: TupleType ${tupleType} != ${dbi.tupleType}`);
|
|
}
|
|
|
|
tupleType = dbi.tupleType;
|
|
}
|
|
}
|
|
|
|
if (opcode !== dbi.opcode.byte) { console.log(`${dbi.name}: ISSUE: Opcode ${opcode} != ${dbi.opcode.byte}`); return null; }
|
|
if (modR !== dbi.opcode.modr) { console.log(`${dbi.name}: ISSUE: ModR ${modR} != ${dbi.opcode.modr}`); return null; }
|
|
if (mm !== dbi.opcode.mm ) { console.log(`${dbi.name}: ISSUE: MM ${mm} != ${dbi.opcode.mm}`); return null; }
|
|
if (pp !== dbi.opcode.pp ) { console.log(`${dbi.name}: ISSUE: PP ${pp} != ${dbi.opcode.pp}`); return null; }
|
|
if (encoding !== dbi.encoding ) { console.log(`${dbi.name}: ISSUE: Enc ${encoding} != ${dbi.encoding}`); return null; }
|
|
if (access !== GetAccess(dbi) ) { console.log(`${dbi.name}: ISSUE: Access ${access} != ${GetAccess(dbi)}`); return null; }
|
|
if (isVec != isVecPrefix(dbi.prefix)) { console.log(`${dbi.name}: ISSUE: Vex/Non-Vex mismatch`); return null; }
|
|
|
|
formatEmit(dbi).forEach((emit) => {
|
|
if (!emitMap[emit]) {
|
|
emitMap[emit] = true;
|
|
this.emitMissingString += emit + "\n";
|
|
}
|
|
});
|
|
}
|
|
|
|
if (tupleType !== "_")
|
|
cdshl = tupleTypeToCDSHL[tupleType] || "?";
|
|
|
|
let ppmm = pp.padEnd(2).replace(/ /g, "0") +
|
|
mm.padEnd(4).replace(/ /g, "0") ;
|
|
|
|
let composed = composeOpCode({
|
|
type : evexCount == dbInsts.length ? "E" : isVec ? "V" : "O",
|
|
prefix: ppmm,
|
|
opcode: opcode,
|
|
o : modR === "r" ? "_" : (modR ? modR : "_"),
|
|
l : vexL !== undefined ? vexL : "_",
|
|
w : vexW !== undefined ? vexW : "_",
|
|
ew : evexW !== undefined ? evexW : "_",
|
|
en : cdshl,
|
|
tt : dbi.modRM ? dbi.modRM + " " : tupleType.padEnd(3)
|
|
});
|
|
|
|
return {
|
|
id : id,
|
|
name : name,
|
|
enum : enum_,
|
|
encoding : encoding,
|
|
opcode0 : composed,
|
|
opcode1 : "0",
|
|
nameIndex : -1,
|
|
commonInfoIndex : -1,
|
|
additionalInfoIndex: -1
|
|
};
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// [Hooks]
|
|
// --------------------------------------------------------------------------
|
|
|
|
onBeforeRun() {
|
|
this.load([
|
|
"src/asmjit/x86/x86globals.h",
|
|
"src/asmjit/x86/x86instdb.cpp",
|
|
"src/asmjit/x86/x86instdb.h",
|
|
"src/asmjit/x86/x86instdb_p.h"
|
|
]);
|
|
this.parse();
|
|
}
|
|
|
|
onAfterRun() {
|
|
this.merge();
|
|
this.save();
|
|
this.dumpTableSizes();
|
|
this.printMissing();
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.IdEnum]
|
|
// ============================================================================
|
|
|
|
class IdEnum extends core.IdEnum {
|
|
constructor() {
|
|
super("IdEnum");
|
|
}
|
|
|
|
comment(inst) {
|
|
function filterAVX(features, avx) {
|
|
return features.filter(function(item) { return /^(AVX|FMA)/.test(item) === avx; });
|
|
}
|
|
|
|
let dbInsts = inst.dbInsts;
|
|
if (!dbInsts.length) return "Invalid instruction id.";
|
|
|
|
let text = "";
|
|
let features = GenUtils.cpuFeaturesOf(dbInsts);
|
|
|
|
const priorityFeatures = ["AVX_VNNI", "AVX_VNNI_INT8", "AVX_IFMA", "AVX_NE_CONVERT"];
|
|
|
|
if (features.length) {
|
|
text += "{";
|
|
const avxFeatures = filterAVX(features, true);
|
|
const otherFeatures = filterAVX(features, false);
|
|
|
|
for (const pf of priorityFeatures) {
|
|
const index = avxFeatures.indexOf(pf);
|
|
if (index != -1) {
|
|
avxFeatures.splice(index, 1);
|
|
avxFeatures.unshift(pf);
|
|
}
|
|
}
|
|
|
|
const vl = avxFeatures.indexOf("AVX512_VL");
|
|
if (vl !== -1) avxFeatures.splice(vl, 1);
|
|
|
|
const fma = avxFeatures.indexOf("FMA");
|
|
if (fma !== -1) { avxFeatures.splice(fma, 1); avxFeatures.splice(0, 0, "FMA"); }
|
|
|
|
text += avxFeatures.join("|");
|
|
if (vl !== -1) text += "+VL";
|
|
|
|
if (otherFeatures.length)
|
|
text += (avxFeatures.length ? " & " : "") + otherFeatures.join("|");
|
|
|
|
text += "}";
|
|
}
|
|
|
|
let arch = GenUtils.cpuArchOf(dbInsts);
|
|
if (arch)
|
|
text += (text ? " " : "") + arch;
|
|
|
|
return `Instruction '${inst.name}'${(text ? " " + text : "")}.`;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.NameTable]
|
|
// ============================================================================
|
|
|
|
class NameTable extends core.NameTable {
|
|
constructor() {
|
|
super("NameTable", null, true);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.AltOpcodeTable]
|
|
// ============================================================================
|
|
|
|
class AltOpcodeTable extends core.Task {
|
|
constructor() {
|
|
super("AltOpcodeTable");
|
|
}
|
|
|
|
run() {
|
|
const insts = this.ctx.insts;
|
|
|
|
const mainOpcodeTable = new IndexedArray();
|
|
const altOpcodeTable = new IndexedArray();
|
|
|
|
const cdttSimplification = {
|
|
"0" : "None",
|
|
"_" : "None",
|
|
"FV" : "ByLL",
|
|
"HV" : "ByLL",
|
|
"QV" : "ByLL",
|
|
"FVM" : "ByLL",
|
|
"T1S" : "None",
|
|
"T1F" : "None",
|
|
"T1_4X": "None",
|
|
"T2" : "None",
|
|
"T4" : "None",
|
|
"T8" : "None",
|
|
"HVM" : "ByLL",
|
|
"QVM" : "ByLL",
|
|
"OVM" : "ByLL",
|
|
"128" : "None",
|
|
"T4X" : "None"
|
|
}
|
|
|
|
const noOp = "O(000000,00,0,0,0,0,0,0 )";
|
|
|
|
mainOpcodeTable.addIndexed(noOp);
|
|
|
|
function splitOpcodeToComponents(opcode) {
|
|
const i = opcode.indexOf("(");
|
|
const prefix = opcode.substr(0, i);
|
|
return [prefix].concat(opcode.substring(i + 1, opcode.length - 1).split(","));
|
|
}
|
|
|
|
function normalizeOpcodeComponents(components) {
|
|
for (let i = 1; i < components.length; i++) {
|
|
components[i] = components[i].trim();
|
|
// These all are zeros that only have some contextual meaning in the table, but the assembler doesn't care.
|
|
if (components[i] === "_" || components[i] === "I" || components[i] === "x")
|
|
components[i] = "0";
|
|
}
|
|
|
|
// Simplify CDTT (compressed displacement TupleType).
|
|
if (components.length >= 9) {
|
|
if (components[0] === "V" || components[0] === "E") {
|
|
const cdtt = components[8];
|
|
if (cdttSimplification[cdtt] !== undefined)
|
|
components[8] = cdttSimplification[cdtt];
|
|
}
|
|
}
|
|
return components;
|
|
}
|
|
|
|
function joinOpcodeComponents(components) {
|
|
const prefix = components[0];
|
|
const values = components.slice(1);
|
|
if (values.length >= 8)
|
|
values[7] = values[7].padEnd(4);
|
|
return prefix + "(" + values.join(",") + ")";
|
|
}
|
|
|
|
function indexMainOpcode(opcode) {
|
|
if (opcode === "0")
|
|
return ["00", 0];
|
|
|
|
let opcodeByte = "";
|
|
const components = normalizeOpcodeComponents(splitOpcodeToComponents(opcode));
|
|
|
|
if (components[0] === "O_FPU") {
|
|
// Reset opcode byte, this is stored in the instruction data itself.
|
|
opcodeByte = components[2].substr(2, 2);
|
|
components[2] = components[2].substr(0, 2) + "00";
|
|
}
|
|
else if (components[0] === "O" || components[0] === "V" || components[0] === "E") {
|
|
// Reset opcode byte, this is stored in the instruction data itself.
|
|
opcodeByte = components[2];
|
|
components[2] = "00";
|
|
}
|
|
else {
|
|
FATAL(`Failed to process opcode '${opcode}'`);
|
|
}
|
|
|
|
const newOpcode = joinOpcodeComponents(components);
|
|
return [opcodeByte, mainOpcodeTable.addIndexed(newOpcode.padEnd(27))];
|
|
}
|
|
|
|
function indexAltOpcode(opcode) {
|
|
if (opcode === "0")
|
|
opcode = noOp;
|
|
else
|
|
opcode = joinOpcodeComponents(normalizeOpcodeComponents(splitOpcodeToComponents(opcode)));
|
|
return altOpcodeTable.addIndexed(opcode.padEnd(27));
|
|
}
|
|
|
|
insts.map((inst) => {
|
|
const [value, index] = indexMainOpcode(inst.opcode0);
|
|
inst.mainOpcodeValue = value;
|
|
inst.mainOpcodeIndex = index;
|
|
inst.altOpcodeIndex = indexAltOpcode(inst.opcode1);
|
|
});
|
|
|
|
// console.log(mainOpcodeTable.length);
|
|
// console.log(StringUtils.format(mainOpcodeTable, kIndent, true));
|
|
|
|
this.inject("MainOpcodeTable",
|
|
disclaimer(`const uint32_t InstDB::_mainOpcodeTable[] = {\n${StringUtils.format(mainOpcodeTable, kIndent, true)}\n};\n`),
|
|
mainOpcodeTable.length * 4);
|
|
|
|
this.inject("AltOpcodeTable",
|
|
disclaimer(`const uint32_t InstDB::_altOpcodeTable[] = {\n${StringUtils.format(altOpcodeTable, kIndent, true)}\n};\n`),
|
|
altOpcodeTable.length * 4);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.InstSignatureTable]
|
|
// ============================================================================
|
|
|
|
const RegOp = ArrayUtils.toDict(["al", "ah", "ax", "eax", "rax", "cl", "r8lo", "r8hi", "r16", "r32", "r64", "xmm", "ymm", "zmm", "mm", "k", "sreg", "creg", "dreg", "st", "bnd"]);
|
|
const MemOp = ArrayUtils.toDict(["m8", "m16", "m32", "m48", "m64", "m80", "m128", "m256", "m512", "m1024"]);
|
|
|
|
const cmpOp = StringUtils.makePriorityCompare([
|
|
"RegGpbLo", "RegGpbHi", "RegGpw", "RegGpd", "RegGpq", "RegXmm", "RegYmm", "RegZmm", "RegMm", "RegKReg", "RegSReg", "RegCReg", "RegDReg", "RegSt", "RegBnd", "RegTmm",
|
|
"MemUnspecified", "Mem8", "Mem16", "Mem32", "Mem48", "Mem64", "Mem80", "Mem128", "Mem256", "Mem512", "Mem1024",
|
|
"Vm32x", "Vm32y", "Vm32z", "Vm64x", "Vm64y", "Vm64z",
|
|
"ImmI4", "ImmU4", "ImmI8", "ImmU8", "ImmI16", "ImmU16", "ImmI32", "ImmU32", "ImmI64", "ImmU64",
|
|
"Rel8", "Rel32",
|
|
"FlagMemBase",
|
|
"FlagMemDs",
|
|
"FlagMemEs",
|
|
"FlagMib",
|
|
"FlagTMem",
|
|
"FlagConsecutive",
|
|
"FlagImplicit"
|
|
]);
|
|
|
|
function StringifyOpArray(a, map) {
|
|
let s = "";
|
|
for (let i = 0; i < a.length; i++) {
|
|
const op = a[i];
|
|
let mapped = null;
|
|
if (typeof map === "function")
|
|
mapped = map(op);
|
|
else if (hasOwn.call(map, op))
|
|
mapped = map[op];
|
|
else
|
|
FATAL(`UNHANDLED OPERAND '${op}'`);
|
|
s += (s ? " | " : "") + mapped;
|
|
}
|
|
return s ? s : "0";
|
|
}
|
|
|
|
class OSignature {
|
|
constructor() {
|
|
this.flags = Object.create(null);
|
|
}
|
|
|
|
equals(other) {
|
|
return ObjectUtils.equals(this.flags, other.flags);
|
|
}
|
|
|
|
xor(other) {
|
|
const result = ObjectUtils.xor(this.flags, other.flags);
|
|
return Object.getOwnPropertyNames(result).length === 0 ? null : result;
|
|
}
|
|
|
|
mergeWith(other) {
|
|
const af = this.flags;
|
|
const bf = other.flags;
|
|
|
|
let hasReg = false;
|
|
let indexKind = "";
|
|
|
|
for (let k in af) {
|
|
const index = asmdb.x86.Utils.regIndexOf(k);
|
|
const kind = asmdb.x86.Utils.regKindOf(k);
|
|
|
|
if (kind)
|
|
hasReg = true;
|
|
|
|
if (index !== null && index !== -1)
|
|
indexKind = kind;
|
|
}
|
|
|
|
if (hasReg) {
|
|
for (let k in bf) {
|
|
const index = asmdb.x86.Utils.regIndexOf(k);
|
|
if (index !== null && index !== -1) {
|
|
const kind = asmdb.x86.Utils.regKindOf(k);
|
|
if (indexKind !== kind)
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Can merge...
|
|
for (let k in bf)
|
|
af[k] = true;
|
|
return true;
|
|
}
|
|
|
|
toString() {
|
|
let s = "";
|
|
let flags = this.flags;
|
|
|
|
for (let k in flags) {
|
|
if (k === "read" || k === "write" || k === "implicit" || k === "memDS" || k === "memES")
|
|
continue;
|
|
|
|
let x = k;
|
|
if (x === "memZAX") x = "zax";
|
|
if (x === "memZDI") x = "zdi";
|
|
if (x === "memZSI") x = "zsi";
|
|
s += (s ? "|" : "") + x;
|
|
}
|
|
|
|
if (flags.memDS) s = "ds:[" + s + "]";
|
|
if (flags.memES) s = "es:[" + s + "]";
|
|
|
|
if (flags.implicit)
|
|
s = "<" + s + ">";
|
|
|
|
return s;
|
|
}
|
|
|
|
toAsmJitOpData() {
|
|
let opFlags = Object.create(null);
|
|
let regMask = 0;
|
|
|
|
for (let k in this.flags) {
|
|
switch (k) {
|
|
case "r8lo" : opFlags.RegGpbLo = true; break;
|
|
case "r8hi" : opFlags.RegGpbHi = true; break;
|
|
case "r16" : opFlags.RegGpw = true; break;
|
|
case "r32" : opFlags.RegGpd = true; break;
|
|
case "r64" : opFlags.RegGpq = true; break;
|
|
case "creg" : opFlags.RegCReg = true; break;
|
|
case "dreg" : opFlags.RegDReg = true; break;
|
|
case "sreg" : opFlags.RegSReg = true; break;
|
|
case "bnd" : opFlags.RegBnd = true; break;
|
|
case "st" : opFlags.RegSt = true; break;
|
|
case "k" : opFlags.RegKReg = true; break;
|
|
case "mm" : opFlags.RegMm = true; break;
|
|
case "xmm" : opFlags.RegXmm = true; break;
|
|
case "ymm" : opFlags.RegYmm = true; break;
|
|
case "zmm" : opFlags.RegZmm = true; break;
|
|
case "tmm" : opFlags.RegTmm = true; break;
|
|
|
|
case "m8" : opFlags.Mem8 = true; break;
|
|
case "m16" : opFlags.Mem16 = true; break;
|
|
case "m32" : opFlags.Mem32 = true; break;
|
|
case "m48" : opFlags.Mem48 = true; break;
|
|
case "m64" : opFlags.Mem64 = true; break;
|
|
case "m80" : opFlags.Mem80 = true; break;
|
|
case "m128" : opFlags.Mem128 = true; break;
|
|
case "m256" : opFlags.Mem256 = true; break;
|
|
case "m512" : opFlags.Mem512 = true; break;
|
|
case "m1024" : opFlags.Mem1024 = true; break;
|
|
|
|
case "mem" : opFlags.MemUnspecified = true; break;
|
|
case "mib" : opFlags.MemUnspecified = true; opFlags.FlagMib = true; break;
|
|
case "tmem" : opFlags.MemUnspecified = true; opFlags.FlagTMem = true; break;
|
|
|
|
case "memBase" : opFlags.FlagMemBase = true; break;
|
|
case "memDS" : opFlags.FlagMemDs = true; break;
|
|
case "memES" : opFlags.FlagMemEs = true; break;
|
|
case "memZAX" : regMask |= 1 << 0; break;
|
|
case "memZSI" : regMask |= 1 << 6; break;
|
|
case "memZDI" : regMask |= 1 << 7; break;
|
|
|
|
case "vm32x" : opFlags.Vm32x = true; break;
|
|
case "vm32y" : opFlags.Vm32y = true; break;
|
|
case "vm32z" : opFlags.Vm32z = true; break;
|
|
case "vm64x" : opFlags.Vm64x = true; break;
|
|
case "vm64y" : opFlags.Vm64y = true; break;
|
|
case "vm64z" : opFlags.Vm64z = true; break;
|
|
|
|
case "i4" : opFlags.ImmI4 = true; break;
|
|
case "u4" : opFlags.ImmU4 = true; break;
|
|
case "i8" : opFlags.ImmI8 = true; break;
|
|
case "u8" : opFlags.ImmU8 = true; break;
|
|
case "i16" : opFlags.ImmI16 = true; break;
|
|
case "u16" : opFlags.ImmU16 = true; break;
|
|
case "i32" : opFlags.ImmI32 = true; break;
|
|
case "u32" : opFlags.ImmU32 = true; break;
|
|
case "i64" : opFlags.ImmI64 = true; break;
|
|
case "u64" : opFlags.ImmU64 = true; break;
|
|
|
|
case "rel8" : opFlags.ImmI32 = true; opFlags.ImmI64 = true; opFlags.Rel8 = true; break;
|
|
case "rel16" : opFlags.ImmI32 = true; opFlags.ImmI64 = true; opFlags.Rel32 = true; break;
|
|
case "rel32" : opFlags.ImmI32 = true; opFlags.ImmI64 = true; opFlags.Rel32 = true; break;
|
|
|
|
case "es" : opFlags.RegSReg = true; regMask |= 1 << 1; break;
|
|
case "cs" : opFlags.RegSReg = true; regMask |= 1 << 2; break;
|
|
case "ss" : opFlags.RegSReg = true; regMask |= 1 << 3; break;
|
|
case "ds" : opFlags.RegSReg = true; regMask |= 1 << 4; break;
|
|
case "fs" : opFlags.RegSReg = true; regMask |= 1 << 5; break;
|
|
case "gs" : opFlags.RegSReg = true; regMask |= 1 << 6; break;
|
|
case "al" : opFlags.RegGpbLo = true; regMask |= 1 << 0; break;
|
|
case "ah" : opFlags.RegGpbHi = true; regMask |= 1 << 0; break;
|
|
case "ax" : opFlags.RegGpw = true; regMask |= 1 << 0; break;
|
|
case "eax" : opFlags.RegGpd = true; regMask |= 1 << 0; break;
|
|
case "rax" : opFlags.RegGpq = true; regMask |= 1 << 0; break;
|
|
case "cl" : opFlags.RegGpbLo = true; regMask |= 1 << 1; break;
|
|
case "ch" : opFlags.RegGpbHi = true; regMask |= 1 << 1; break;
|
|
case "cx" : opFlags.RegGpw = true; regMask |= 1 << 1; break;
|
|
case "ecx" : opFlags.RegGpd = true; regMask |= 1 << 1; break;
|
|
case "rcx" : opFlags.RegGpq = true; regMask |= 1 << 1; break;
|
|
case "dl" : opFlags.RegGpbLo = true; regMask |= 1 << 2; break;
|
|
case "dh" : opFlags.RegGpbHi = true; regMask |= 1 << 2; break;
|
|
case "dx" : opFlags.RegGpw = true; regMask |= 1 << 2; break;
|
|
case "edx" : opFlags.RegGpd = true; regMask |= 1 << 2; break;
|
|
case "rdx" : opFlags.RegGpq = true; regMask |= 1 << 2; break;
|
|
case "bl" : opFlags.RegGpbLo = true; regMask |= 1 << 3; break;
|
|
case "bh" : opFlags.RegGpbHi = true; regMask |= 1 << 3; break;
|
|
case "bx" : opFlags.RegGpw = true; regMask |= 1 << 3; break;
|
|
case "ebx" : opFlags.RegGpd = true; regMask |= 1 << 3; break;
|
|
case "rbx" : opFlags.RegGpq = true; regMask |= 1 << 3; break;
|
|
case "si" : opFlags.RegGpw = true; regMask |= 1 << 6; break;
|
|
case "esi" : opFlags.RegGpd = true; regMask |= 1 << 6; break;
|
|
case "rsi" : opFlags.RegGpq = true; regMask |= 1 << 6; break;
|
|
case "di" : opFlags.RegGpw = true; regMask |= 1 << 7; break;
|
|
case "edi" : opFlags.RegGpd = true; regMask |= 1 << 7; break;
|
|
case "rdi" : opFlags.RegGpq = true; regMask |= 1 << 7; break;
|
|
case "st0" : opFlags.RegSt = true; regMask |= 1 << 0; break;
|
|
case "xmm0" : opFlags.RegXmm = true; regMask |= 1 << 0; break;
|
|
case "ymm0" : opFlags.RegYmm = true; regMask |= 1 << 0; break;
|
|
|
|
case "implicit": opFlags.FlagImplicit = true; break;
|
|
|
|
default:
|
|
console.log(`UNKNOWN OPERAND '${k}'`);
|
|
}
|
|
}
|
|
|
|
const outputFlags = StringifyOpArray(ArrayUtils.sorted(opFlags, cmpOp), function(k) { return `F(${k})`; });
|
|
return `ROW(${outputFlags || 0}, ${decToHex(regMask, 2)})`;
|
|
}
|
|
}
|
|
|
|
class ISignature extends Array {
|
|
constructor(name) {
|
|
super();
|
|
this.name = name;
|
|
this.x86 = false;
|
|
this.x64 = false;
|
|
this.implicit = 0; // Number of implicit operands.
|
|
}
|
|
|
|
opEquals(other) {
|
|
const len = this.length;
|
|
if (len !== other.length) return false;
|
|
|
|
for (let i = 0; i < len; i++)
|
|
if (!this[i].equals(other[i]))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
mergeWith(other) {
|
|
// If both architectures are the same, it's fine to merge.
|
|
const sameArch = this.x86 === other.x86 && this.x64 === other.x64;
|
|
|
|
// If the first arch is [X86|X64] and the second [X64] it's also fine.
|
|
// if (!ok && this.x86 && this.x64 && !other.x86 && other.x64)
|
|
// ok = true;
|
|
|
|
// It's not ok if both signatures have different number of implicit operands.
|
|
if (!sameArch || this.implicit !== other.implicit) {
|
|
return false;
|
|
}
|
|
|
|
// It's not ok if both signatures have different number of operands.
|
|
const len = this.length;
|
|
if (len !== other.length)
|
|
return false;
|
|
|
|
let xorIndex = -1;
|
|
for (let i = 0; i < len; i++) {
|
|
const xor = this[i].xor(other[i]);
|
|
if (xor === null)
|
|
continue;
|
|
|
|
if (xorIndex === -1)
|
|
xorIndex = i;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// Bail if mergeWith at operand-level failed.
|
|
if (xorIndex === -1 || !this[xorIndex].mergeWith(other[xorIndex]))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
toString() {
|
|
return "{" + this.join(", ") + "}";
|
|
}
|
|
}
|
|
|
|
class SignatureArray extends Array {
|
|
constructor(instructionName) {
|
|
super();
|
|
this.instructionName = instructionName;
|
|
}
|
|
// Iterate over all signatures and check which operands don't need explicit memory size.
|
|
calcImplicitMemSize(instName) {
|
|
// Calculates a hash-value (aka key) of all register operands specified by `regOps` in `inst`.
|
|
function keyOf(inst, regOps) {
|
|
let s = "";
|
|
for (let i = 0; i < inst.length; i++) {
|
|
const op = inst[i];
|
|
if (regOps & (1 << i))
|
|
s += "{" + ArrayUtils.sorted(ObjectUtils.and(op.flags, RegOp)).join("|") + "}";
|
|
}
|
|
return s || "?";
|
|
}
|
|
|
|
for (let aIndex = 0; aIndex < this.length; aIndex++) {
|
|
const aInst = this[aIndex];
|
|
const len = aInst.length;
|
|
|
|
let memOp = "";
|
|
let memPos = -1;
|
|
let regOps = 0;
|
|
|
|
// Check if this instruction signature has a memory operand of explicit size.
|
|
for (let i = 0; i < len; i++) {
|
|
const aOp = aInst[i];
|
|
const mem = ObjectUtils.findKey(aOp.flags, MemOp);
|
|
|
|
if (mem) {
|
|
// Stop if the memory operand has implicit-size or if there is more than one.
|
|
if (aOp.flags.mem || memPos >= 0) {
|
|
memPos = -1;
|
|
break;
|
|
}
|
|
else {
|
|
memOp = mem;
|
|
memPos = i;
|
|
}
|
|
}
|
|
else if (ObjectUtils.hasAny(aOp.flags, RegOp)) {
|
|
// Doesn't consider 'r/m' as we already checked 'm'.
|
|
regOps |= (1 << i);
|
|
}
|
|
}
|
|
|
|
if (memPos < 0)
|
|
continue;
|
|
|
|
// Create a `sameSizeSet` set of all instructions having the exact
|
|
// explicit memory operand at the same position and registers at
|
|
// positions matching `regOps` bits and `diffSizeSet` having memory
|
|
// operand of different size, but registers at the same positions.
|
|
const sameSizeSet = [aInst];
|
|
const diffSizeSet = [];
|
|
const diffSizeHash = Object.create(null);
|
|
|
|
for (let bIndex = 0; bIndex < this.length; bIndex++) {
|
|
const bInst = this[bIndex];
|
|
if (aIndex === bIndex || len !== bInst.length) continue;
|
|
|
|
let hasMatch = 1;
|
|
for (let i = 0; i < len; i++) {
|
|
if (i === memPos) continue;
|
|
|
|
const reg = ObjectUtils.hasAny(bInst[i].flags, RegOp);
|
|
if (regOps & (1 << i))
|
|
hasMatch &= reg;
|
|
else if (reg)
|
|
hasMatch = 0;
|
|
}
|
|
|
|
if (hasMatch) {
|
|
const bOp = bInst[memPos];
|
|
if (bOp.flags.mem) continue;
|
|
|
|
const mem = ObjectUtils.findKey(bOp.flags, MemOp);
|
|
if (mem === memOp) {
|
|
sameSizeSet.push(bInst);
|
|
}
|
|
else if (mem) {
|
|
const key = keyOf(bInst, regOps);
|
|
diffSizeSet.push(bInst);
|
|
if (!diffSizeHash[key])
|
|
diffSizeHash[key] = [bInst];
|
|
else
|
|
diffSizeHash[key].push(bInst);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Two cases.
|
|
// A) The memory operand has implicit-size if `diffSizeSet` is empty. That
|
|
// means that the instruction only uses one size for all reg combinations.
|
|
//
|
|
// B) The memory operand has implicit-size if `diffSizeSet` contains different
|
|
// register signatures than `sameSizeSet`.
|
|
let implicit = true;
|
|
|
|
if (!diffSizeSet.length) {
|
|
// Case A:
|
|
}
|
|
else {
|
|
// Case B: Find collisions in `sameSizeSet` and `diffSizeSet`.
|
|
for (let bIndex = 0; bIndex < sameSizeSet.length; bIndex++) {
|
|
const bInst = sameSizeSet[bIndex];
|
|
const key = keyOf(bInst, regOps);
|
|
|
|
const diff = diffSizeHash[key];
|
|
if (diff) {
|
|
diff.forEach((diffInst) => {
|
|
if ((bInst.x86 && !diffInst.x86) || (!bInst.x86 && diffInst.x86)) {
|
|
// If this is X86|ANY instruction and the other is X64, or vice-versa,
|
|
// then keep this implicit as it won't do any harm. These instructions
|
|
// cannot be mixed and it will make implicit the 32-bit one in cases
|
|
// where X64 introduced 64-bit ones like `cvtsi2ss`.
|
|
if (!/^(bndcl|bndcn|bndcu|ptwrite|(v)?cvtsi2ss|(v)?cvtsi2sd|vcvtusi2ss|vcvtusi2sd)$/.test(instName))
|
|
implicit = false;
|
|
}
|
|
else {
|
|
implicit = false;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Patch all instructions to accept implicit-size memory operand.
|
|
for (let bIndex = 0; bIndex < sameSizeSet.length; bIndex++) {
|
|
const bInst = sameSizeSet[bIndex];
|
|
if (implicit) {
|
|
bInst[memPos].flags.mem = true;
|
|
}
|
|
|
|
if (!implicit) {
|
|
DEBUG(`${this.name}: Explicit: ${bInst}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
compact() {
|
|
let didSomething = true;
|
|
while (didSomething) {
|
|
didSomething = false;
|
|
for (let i = 0; i < this.length; i++) {
|
|
let row = this[i];
|
|
let j = i + 1;
|
|
while (j < this.length) {
|
|
if (row.mergeWith(this[j])) {
|
|
this.splice(j, 1);
|
|
didSomething = true;
|
|
continue;
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
toString() {
|
|
return `[${this.join(",\n")}]`;
|
|
}
|
|
}
|
|
|
|
class InstSignatureTable extends core.Task {
|
|
constructor() {
|
|
super("InstSignatureTable");
|
|
this.maxOpRows = 0;
|
|
}
|
|
|
|
run() {
|
|
const insts = this.ctx.insts;
|
|
|
|
insts.forEach((inst) => {
|
|
inst.signatures = this.makeSignatures(Filter.noAltForm(inst.dbInsts));
|
|
this.maxOpRows = Math.max(this.maxOpRows, inst.signatures.length);
|
|
});
|
|
|
|
const iSignatureMap = Object.create(null);
|
|
const iSignatureArr = [];
|
|
|
|
const oSignatureMap = Object.create(null);
|
|
const oSignatureArr = [];
|
|
|
|
// Must be first to be assigned to zero.
|
|
const oSignatureNone = "ROW(0, 0xFF)";
|
|
oSignatureMap[oSignatureNone] = [0];
|
|
oSignatureArr.push(oSignatureNone);
|
|
|
|
function findSignaturesIndex(rows) {
|
|
const len = rows.length;
|
|
if (!len) return 0;
|
|
|
|
const indexes = iSignatureMap[rows[0].data];
|
|
if (indexes === undefined) return -1;
|
|
|
|
for (let i = 0; i < indexes.length; i++) {
|
|
const index = indexes[i];
|
|
if (index + len > iSignatureArr.length) continue;
|
|
|
|
let ok = true;
|
|
for (let j = 0; j < len; j++) {
|
|
if (iSignatureArr[index + j].data !== rows[j].data) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
return index;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
function indexSignatures(signatures) {
|
|
const result = iSignatureArr.length;
|
|
|
|
for (let i = 0; i < signatures.length; i++) {
|
|
const signature = signatures[i];
|
|
const idx = iSignatureArr.length;
|
|
|
|
if (!hasOwn.call(iSignatureMap, signature.data))
|
|
iSignatureMap[signature.data] = [];
|
|
|
|
iSignatureMap[signature.data].push(idx);
|
|
iSignatureArr.push(signature);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
for (let len = this.maxOpRows; len >= 0; len--) {
|
|
insts.forEach((inst) => {
|
|
const signatures = inst.signatures;
|
|
if (signatures.length === len) {
|
|
const signatureEntries = [];
|
|
for (let j = 0; j < len; j++) {
|
|
const signature = signatures[j];
|
|
|
|
let signatureEntry = `ROW(${signature.length}, ${signature.x86 ? 1 : 0}, ${signature.x64 ? 1 : 0}, ${signature.implicit}`;
|
|
let signatureComment = signature.toString();
|
|
|
|
let x = 0;
|
|
while (x < signature.length) {
|
|
const h = signature[x].toAsmJitOpData();
|
|
let index = -1;
|
|
if (!hasOwn.call(oSignatureMap, h)) {
|
|
index = oSignatureArr.length;
|
|
oSignatureMap[h] = index;
|
|
oSignatureArr.push(h);
|
|
}
|
|
else {
|
|
index = oSignatureMap[h];
|
|
}
|
|
|
|
signatureEntry += `, ${String(index).padEnd(3)}`;
|
|
x++;
|
|
}
|
|
|
|
while (x < 6) {
|
|
signatureEntry += `, ${String(0).padEnd(3)}`;
|
|
x++;
|
|
}
|
|
|
|
signatureEntry += `)`;
|
|
signatureEntries.push({ data: signatureEntry, comment: signatureComment, refs: 0 });
|
|
}
|
|
|
|
let count = signatureEntries.length;
|
|
let index = findSignaturesIndex(signatureEntries);
|
|
|
|
if (index === -1)
|
|
index = indexSignatures(signatureEntries);
|
|
|
|
iSignatureArr[index].refs++;
|
|
inst.signatureIndex = index;
|
|
inst.signatureCount = count;
|
|
}
|
|
});
|
|
}
|
|
|
|
let s = `#define ROW(count, x86, x64, implicit, o0, o1, o2, o3, o4, o5) \\\n` +
|
|
` { count, uint8_t(x86 ? uint8_t(InstDB::Mode::kX86) : uint8_t(0)) | \\\n` +
|
|
` (x64 ? uint8_t(InstDB::Mode::kX64) : uint8_t(0)) , \\\n` +
|
|
` implicit, \\\n` +
|
|
` 0, \\\n` +
|
|
` { o0, o1, o2, o3, o4, o5 } \\\n` +
|
|
` }\n` +
|
|
StringUtils.makeCxxArrayWithComment(iSignatureArr, "const InstDB::InstSignature InstDB::_instSignatureTable[]") +
|
|
`#undef ROW\n` +
|
|
`\n` +
|
|
`#define ROW(opFlags, regId) { opFlags, uint8_t(regId) }\n` +
|
|
`#define F(VAL) uint64_t(InstDB::OpFlags::k##VAL)\n` +
|
|
StringUtils.makeCxxArray(oSignatureArr, "const InstDB::OpSignature InstDB::_opSignatureTable[]") +
|
|
`#undef F\n` +
|
|
`#undef ROW\n`;
|
|
this.inject("InstSignatureTable", disclaimer(s), oSignatureArr.length * 8 + iSignatureArr.length * 8);
|
|
}
|
|
|
|
makeSignatures(dbInsts) {
|
|
const instName = dbInsts.length ? dbInsts[0].name : "";
|
|
const signatures = new SignatureArray(instName);
|
|
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const inst = dbInsts[i];
|
|
const ops = inst.operands;
|
|
|
|
// NOTE: This changed from having reg|mem merged into creating two signatures
|
|
// instead. Imagine two instructions in one `dbInsts` array:
|
|
//
|
|
// 1. mov reg, reg/mem
|
|
// 2. mov reg/mem, reg
|
|
//
|
|
// If we merge them and then unmerge, we will have 4 signatures, when iterated:
|
|
//
|
|
// 1a. mov reg, reg
|
|
// 1b. mov reg, mem
|
|
// 2a. mov reg, reg
|
|
// 2b. mov mem, reg
|
|
//
|
|
// So, instead of merging them here, we insert separated signatures and let
|
|
// the tool merge them in a way that can be easily unmerged at runtime into:
|
|
//
|
|
// 1a. mov reg, reg
|
|
// 1b. mov reg, mem
|
|
// 2b. mov mem, reg
|
|
let modrmCount = 1;
|
|
for (let modrm = 0; modrm < modrmCount; modrm++) {
|
|
let row = new ISignature(inst.name);
|
|
|
|
row.x86 = (inst.arch === "ANY" || inst.arch === "X86");
|
|
row.x64 = (inst.arch === "ANY" || inst.arch === "X64");
|
|
|
|
let j;
|
|
for (j = 0; j < ops.length; j++) {
|
|
let iop = ops[j];
|
|
|
|
let reg = iop.reg;
|
|
let mem = iop.mem;
|
|
let imm = iop.imm;
|
|
let rel = iop.rel;
|
|
|
|
// Skip all instructions having implicit `imm` operand of `1`.
|
|
if (iop.immValue !== null)
|
|
break;
|
|
|
|
// Shorten the number of signatures of 'mov' instruction.
|
|
if (inst.name === "mov" && mem.startsWith("moff"))
|
|
break;
|
|
|
|
if (reg === "seg") reg = "sreg";
|
|
if (reg === "st(i)") reg = "st";
|
|
if (reg === "st(0)") reg = "st0";
|
|
|
|
if (mem === "moff8") mem = "m8";
|
|
if (mem === "moff16") mem = "m16";
|
|
if (mem === "moff32") mem = "m32";
|
|
if (mem === "moff64") mem = "m64";
|
|
|
|
if (mem === "m32fp") mem = "m32";
|
|
if (mem === "m64fp") mem = "m64";
|
|
if (mem === "m80fp") mem = "m80";
|
|
if (mem === "m80bcd") mem = "m80";
|
|
if (mem === "m80dec") mem = "m80";
|
|
if (mem === "m16int") mem = "m16";
|
|
if (mem === "m32int") mem = "m32";
|
|
if (mem === "m64int") mem = "m64";
|
|
|
|
if (mem === "m16_16") mem = "m32";
|
|
if (mem === "m16_32") mem = "m48";
|
|
if (mem === "m16_64") mem = "m80";
|
|
|
|
if (reg && mem) {
|
|
if (modrmCount === 1) {
|
|
mem = null;
|
|
modrmCount++;
|
|
}
|
|
else {
|
|
reg = null;
|
|
}
|
|
}
|
|
|
|
const op = new OSignature();
|
|
if (iop.implicit) {
|
|
row.implicit++;
|
|
op.flags.implicit = true;
|
|
}
|
|
|
|
const seg = iop.memSegment;
|
|
if (seg) {
|
|
switch (inst.name) {
|
|
case "insb": op.flags.m8 = true; break;
|
|
case "insw": op.flags.m16 = true; break;
|
|
case "insd": op.flags.m32 = true; break;
|
|
case "outsb": op.flags.m8 = true; break;
|
|
case "outsw": op.flags.m16 = true; break;
|
|
case "outsd": op.flags.m32 = true; break;
|
|
case "clzero": op.flags.mem = true; op.flags.m512 = true; break;
|
|
case "enqcmd": op.flags.mem = true; op.flags.m512 = true; break;
|
|
case "enqcmds": op.flags.mem = true; op.flags.m512 = true; break;
|
|
case "movdir64b": op.flags.mem = true; op.flags.m512 = true; break;
|
|
case "maskmovq": op.flags.mem = true; op.flags.m64 = true; break;
|
|
case "maskmovdqu": op.flags.mem = true; op.flags.m128 = true; break;
|
|
case "vmaskmovdqu": op.flags.mem = true; op.flags.m128 = true; break;
|
|
case "monitor": op.flags.mem = true; break;
|
|
case "monitorx": op.flags.mem = true; break;
|
|
case "umonitor": op.flags.mem = true; break;
|
|
default: console.log(`UNKNOWN MEM IN INSTRUCTION '${inst.name}'`); break;
|
|
}
|
|
|
|
if (iop.memRegOnly)
|
|
reg = iop.memRegOnly;
|
|
|
|
if (seg === "ds") op.flags.memDS = true;
|
|
if (seg === "es") op.flags.memES = true;
|
|
if (reg === "reg") { op.flags.memBase = true; }
|
|
if (reg === "r32") { op.flags.memBase = true; }
|
|
if (reg === "r64") { op.flags.memBase = true; }
|
|
if (reg === "zax") { op.flags.memBase = true; op.flags.memZAX = true; }
|
|
if (reg === "zsi") { op.flags.memBase = true; op.flags.memZSI = true; }
|
|
if (reg === "zdi") { op.flags.memBase = true; op.flags.memZDI = true; }
|
|
}
|
|
else if (reg) {
|
|
if (reg == "r8") {
|
|
op.flags["r8lo"] = true;
|
|
|
|
if (!inst.w || inst.w === "W0")
|
|
op.flags["r8hi"] = true;
|
|
}
|
|
else {
|
|
op.flags[reg] = true;
|
|
}
|
|
}
|
|
|
|
if (mem) {
|
|
op.flags[mem] = true;
|
|
// HACK: Allow LEA to use any memory size.
|
|
if (/^(lea)$/.test(inst.name)) {
|
|
op.flags.mem = true;
|
|
Object.assign(op.flags, MemOp);
|
|
}
|
|
|
|
// HACK: These instructions specify explicit memory size, but it's just informational.
|
|
if (/^(call|enqcmd|enqcmds|lcall|ljmp|movdir64b)$/.test(inst.name)) {
|
|
op.flags.mem = true;
|
|
}
|
|
}
|
|
|
|
if (imm) {
|
|
if (iop.immSign === "any" || iop.immSign === "signed" ) op.flags["i" + imm] = true;
|
|
if (iop.immSign === "any" || iop.immSign === "unsigned") op.flags["u" + imm] = true;
|
|
}
|
|
|
|
if (rel) {
|
|
op.flags["rel" + rel] = true;
|
|
}
|
|
|
|
row.push(op);
|
|
}
|
|
|
|
// Not equal if we terminated the loop.
|
|
if (j === ops.length) {
|
|
signatures.push(row);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (signatures.length && GenUtils.canUseImplicitMemSize(instName))
|
|
signatures.calcImplicitMemSize(instName);
|
|
|
|
signatures.compact();
|
|
return signatures;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.AdditionalInfoTable]
|
|
// ============================================================================
|
|
|
|
class AdditionalInfoTable extends core.Task {
|
|
constructor() {
|
|
super("AdditionalInfoTable");
|
|
}
|
|
|
|
run() {
|
|
const insts = this.ctx.insts;
|
|
const rwInfoTable = new IndexedArray();
|
|
const instFlagsTable = new IndexedArray();
|
|
const additionaInfoTable = new IndexedArray();
|
|
|
|
// If the instruction doesn't read any flags it should point to the first index.
|
|
rwInfoTable.addIndexed(`{ 0, 0 }`);
|
|
|
|
insts.forEach((inst) => {
|
|
const dbInsts = inst.dbInsts;
|
|
|
|
let features = GenUtils.cpuFeaturesOf(dbInsts).map(function(f) { return `EXT(${f})`; }).join(", ");
|
|
if (!features) features = "0";
|
|
|
|
let [r, w] = this.rwFlagsOf(dbInsts);
|
|
const rData = r.map(function(flag) { return `FLAG(${flag})`; }).join(" | ") || "0";
|
|
const wData = w.map(function(flag) { return `FLAG(${flag})`; }).join(" | ") || "0";
|
|
const instFlags = Object.create(null);
|
|
|
|
switch (inst.name) {
|
|
case "kmovb":
|
|
case "kmovd":
|
|
case "kmovq":
|
|
case "kmovw":
|
|
case "mov":
|
|
case "movq":
|
|
case "movsd":
|
|
case "movss":
|
|
case "movapd":
|
|
case "movaps":
|
|
case "movdqa":
|
|
case "movdqu":
|
|
case "movupd":
|
|
case "movups":
|
|
case "vmovapd":
|
|
case "vmovaps":
|
|
case "vmovdqa":
|
|
case "vmovdqa8":
|
|
case "vmovdqa16":
|
|
case "vmovdqa32":
|
|
case "vmovdqa64":
|
|
case "vmovdqu":
|
|
case "vmovdqu8":
|
|
case "vmovdqu16":
|
|
case "vmovdqu32":
|
|
case "vmovdqu64":
|
|
case "vmovq":
|
|
case "vmovsd":
|
|
case "vmovss":
|
|
case "vmovupd":
|
|
case "vmovups":
|
|
instFlags["MovOp"] = true;
|
|
break;
|
|
}
|
|
|
|
const instFlagsIndex = instFlagsTable.addIndexed("InstRWFlags(" + StringUtils.formatCppFlags(instFlags, (f) => { return `FLAG(${f})`; }, "FLAG(None)") + ")");
|
|
const rwInfoIndex = rwInfoTable.addIndexed(`{ ${rData}, ${wData} }`);
|
|
|
|
inst.additionalInfoIndex = additionaInfoTable.addIndexed(`{ ${instFlagsIndex}, ${rwInfoIndex}, { ${features} } }`);
|
|
});
|
|
|
|
let s = `#define EXT(VAL) uint32_t(CpuFeatures::X86::k##VAL)\n` +
|
|
`const InstDB::AdditionalInfo InstDB::_additionalInfoTable[] = {\n${StringUtils.format(additionaInfoTable, kIndent, true)}\n};\n` +
|
|
`#undef EXT\n` +
|
|
`\n` +
|
|
`#define FLAG(VAL) uint32_t(CpuRWFlags::kX86_##VAL)\n` +
|
|
`const InstDB::RWFlagsInfoTable InstDB::_rwFlagsInfoTable[] = {\n${StringUtils.format(rwInfoTable, kIndent, true)}\n};\n` +
|
|
`#undef FLAG\n` +
|
|
`\n` +
|
|
`#define FLAG(VAL) uint32_t(InstRWFlags::k##VAL)\n` +
|
|
`const InstRWFlags InstDB::_instFlagsTable[] = {\n${StringUtils.format(instFlagsTable, kIndent, true)}\n};\n` +
|
|
`#undef FLAG\n`;
|
|
this.inject("AdditionalInfoTable", disclaimer(s), additionaInfoTable.length * 8 + rwInfoTable.length * 8 + instFlagsTable.length * 4);
|
|
}
|
|
|
|
rwFlagsOf(dbInsts) {
|
|
const r = Object.create(null);
|
|
const w = Object.create(null);
|
|
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
|
|
// Omit special cases, this is handled well in C++ code.
|
|
if (dbInst.name === "mov")
|
|
continue;
|
|
|
|
const regs = dbInst.io;
|
|
|
|
// Mov is a special case, moving to/from control regs makes flags undefined,
|
|
// which we don't want to have in `X86InstDB::operationData`. This is, thus,
|
|
// a special case instruction analyzer must deal with.
|
|
if (dbInst.name === "mov")
|
|
continue;
|
|
|
|
for (let reg in regs) {
|
|
let flag = "";
|
|
switch (reg) {
|
|
case "CF": flag = "CF"; break;
|
|
case "OF": flag = "OF"; break;
|
|
case "SF": flag = "SF"; break;
|
|
case "ZF": flag = "ZF"; break;
|
|
case "AF": flag = "AF"; break;
|
|
case "PF": flag = "PF"; break;
|
|
case "DF": flag = "DF"; break;
|
|
case "IF": flag = "IF"; break;
|
|
//case "TF": flag = "TF"; break;
|
|
case "AC": flag = "AC"; break;
|
|
case "C0": flag = "C0"; break;
|
|
case "C1": flag = "C1"; break;
|
|
case "C2": flag = "C2"; break;
|
|
case "C3": flag = "C3"; break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
switch (regs[reg]) {
|
|
case "R":
|
|
r[flag] = true;
|
|
break;
|
|
case "X":
|
|
r[flag] = true;
|
|
// ... fallthrough ...
|
|
case "W":
|
|
case "U":
|
|
case "0":
|
|
case "1":
|
|
w[flag] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [ArrayUtils.sorted(r), ArrayUtils.sorted(w)];
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.InstRWInfoTable]
|
|
// ============================================================================
|
|
|
|
const NOT_MEM_AMBIGUOUS = ArrayUtils.toDict([
|
|
"call", "movq"
|
|
]);
|
|
|
|
class InstRWInfoTable extends core.Task {
|
|
constructor() {
|
|
super("InstRWInfoTable");
|
|
|
|
this.rwInfoIndexA = [];
|
|
this.rwInfoIndexB = [];
|
|
this.rwInfoTableA = new IndexedArray();
|
|
this.rwInfoTableB = new IndexedArray();
|
|
|
|
this.rmInfoTable = new IndexedArray();
|
|
this.opInfoTable = new IndexedArray();
|
|
|
|
this.rwCategoryByName = {
|
|
"imul" : "Imul",
|
|
"mov" : "Mov",
|
|
"movabs" : "Movabs",
|
|
"movhpd" : "Movh64",
|
|
"movhps" : "Movh64",
|
|
"punpcklbw" : "Punpcklxx",
|
|
"punpckldq" : "Punpcklxx",
|
|
"punpcklwd" : "Punpcklxx",
|
|
"vmaskmovpd": "Vmaskmov",
|
|
"vmaskmovps": "Vmaskmov",
|
|
"vmovddup" : "Vmovddup",
|
|
"vmovmskpd" : "Vmovmskpd",
|
|
"vmovmskps" : "Vmovmskps",
|
|
"vpmaskmovd": "Vmaskmov",
|
|
"vpmaskmovq": "Vmaskmov"
|
|
};
|
|
|
|
const _ = null;
|
|
this.rwCategoryByData = {
|
|
Vmov1_8: [
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 8}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width: 64},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 16}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:128},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 32}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:256},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 64}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:512},_,_,_,_]
|
|
],
|
|
Vmov1_4: [
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 32}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:128},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 64}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:256},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width:128}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:512},_,_,_,_]
|
|
],
|
|
Vmov1_2: [
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 64}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:128},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width:128}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:256},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width:256}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:512},_,_,_,_]
|
|
],
|
|
Vmov2_1: [
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 128}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width: 64},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 256}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:128},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 512}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:256},_,_,_,_]
|
|
],
|
|
Vmov4_1: [
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 128}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width: 32},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 256}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width: 64},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 512}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width:128},_,_,_,_]
|
|
],
|
|
Vmov8_1: [
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 128}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width: 16},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 256}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width: 32},_,_,_,_],
|
|
[{access: "W", clc: 0, flags: {}, fixed: -1, index: 0, width: 512}, {access: "R", clc: 0, flags: {}, fixed: -1, index: 0, width: 64},_,_,_,_]
|
|
]
|
|
};
|
|
}
|
|
|
|
run() {
|
|
const insts = this.ctx.insts;
|
|
|
|
const noRmInfo = StringUtils.formatCppStruct(
|
|
"InstDB::RWInfoRm::kCategory" + "None".padEnd(10),
|
|
StringUtils.decToHex(0, 2),
|
|
String(0).padEnd(2),
|
|
StringUtils.formatCppFlags({}),
|
|
"0"
|
|
);
|
|
|
|
const noOpInfo = StringUtils.formatCppStruct(
|
|
"0x0000000000000000u",
|
|
"0x0000000000000000u",
|
|
"0xFF",
|
|
"0",
|
|
StringUtils.formatCppStruct(0),
|
|
"OpRWFlags::kNone"
|
|
);
|
|
|
|
this.rmInfoTable.addIndexed(noRmInfo);
|
|
this.opInfoTable.addIndexed(noOpInfo);
|
|
|
|
insts.forEach((inst) => {
|
|
// Alternate forms would only mess this up, so filter them out.
|
|
const dbInsts = Filter.noAltForm(inst.dbInsts);
|
|
|
|
// The best we can do is to divide instructions that have 2 operands and others.
|
|
// This gives us the highest chance of preventing special cases (which were not
|
|
// entirely avoided).
|
|
const o2Insts = dbInsts.filter((inst) => { return inst.operands.length === 2; });
|
|
const oxInsts = dbInsts.filter((inst) => { return inst.operands.length !== 2; });
|
|
|
|
const rwInfoArray = [this.rwInfo(inst, o2Insts), this.rwInfo(inst, oxInsts)];
|
|
const rmInfoArray = [this.rmInfo(inst, o2Insts), this.rmInfo(inst, oxInsts)];
|
|
|
|
for (let i = 0; i < 2; i++) {
|
|
const rwInfo = rwInfoArray[i];
|
|
const rmInfo = rmInfoArray[i];
|
|
|
|
const rwOps = rwInfo.rwOps;
|
|
const rwOpsIndex = [];
|
|
for (let j = 0; j < rwOps.length; j++) {
|
|
const op = rwOps[j];
|
|
if (!op) {
|
|
rwOpsIndex.push(this.opInfoTable.addIndexed(noOpInfo));
|
|
continue;
|
|
}
|
|
|
|
const flags = {};
|
|
const opAcc = op.access;
|
|
|
|
if (opAcc === "R") flags.Read = true;
|
|
if (opAcc === "W") flags.Write = true;
|
|
if (opAcc === "X") flags.RW = true;
|
|
ObjectUtils.merge(flags, op.flags);
|
|
|
|
const rIndex = opAcc === "X" || opAcc === "R" ? op.index : -1;
|
|
const rWidth = opAcc === "X" || opAcc === "R" ? op.width : -1;
|
|
const wIndex = opAcc === "X" || opAcc === "W" ? op.index : -1;
|
|
const wWidth = opAcc === "X" || opAcc === "W" ? op.width : -1;
|
|
|
|
const consecutiveLeadCount = op.clc;
|
|
|
|
const opData = StringUtils.formatCppStruct(
|
|
this.byteMaskFromBitRanges([{ start: rIndex, end: rIndex + rWidth - 1 }]) + "u",
|
|
this.byteMaskFromBitRanges([{ start: wIndex, end: wIndex + wWidth - 1 }]) + "u",
|
|
StringUtils.decToHex(op.fixed === -1 ? 0xFF : op.fixed, 2),
|
|
String(consecutiveLeadCount),
|
|
StringUtils.formatCppStruct(0),
|
|
StringUtils.formatCppFlags(flags, function(flag) { return "OpRWFlags::k" + flag; }, "OpRWFlags::kNone")
|
|
);
|
|
|
|
rwOpsIndex.push(this.opInfoTable.addIndexed(opData));
|
|
}
|
|
|
|
const rmData = StringUtils.formatCppStruct(
|
|
"InstDB::RWInfoRm::kCategory" + rmInfo.category.padEnd(10),
|
|
StringUtils.decToHex(rmInfo.rmIndexes, 2),
|
|
String(Math.max(rmInfo.memFixed, 0)).padEnd(2),
|
|
StringUtils.formatCppFlags({
|
|
"InstDB::RWInfoRm::kFlagAmbiguous": Boolean(rmInfo.memAmbiguous),
|
|
"InstDB::RWInfoRm::kFlagMovssMovsd": Boolean(inst.name === "movss" || inst.name === "movsd"),
|
|
"InstDB::RWInfoRm::kFlagPextrw": Boolean(inst.name === "pextrw"),
|
|
"InstDB::RWInfoRm::kFlagFeatureIfRMI": Boolean(rmInfo.memExtensionIfRMI)
|
|
}),
|
|
rmInfo.memExtension === "None" ? "0" : "uint32_t(CpuFeatures::X86::k" + rmInfo.memExtension + ")"
|
|
);
|
|
|
|
const rwData = StringUtils.formatCppStruct(
|
|
"InstDB::RWInfo::kCategory" + rwInfo.category.padEnd(10),
|
|
String(this.rmInfoTable.addIndexed(rmData)).padEnd(2),
|
|
StringUtils.formatCppStruct(...(rwOpsIndex.map(function(item) { return String(item).padEnd(2); })))
|
|
);
|
|
|
|
if (i == 0)
|
|
this.rwInfoIndexA.push(this.rwInfoTableA.addIndexed(rwData));
|
|
else
|
|
this.rwInfoIndexB.push(this.rwInfoTableB.addIndexed(rwData));
|
|
}
|
|
});
|
|
|
|
let s = "";
|
|
s += "const uint8_t InstDB::rwInfoIndexA[Inst::_kIdCount] = {\n" + StringUtils.format(this.rwInfoIndexA, kIndent, -1) + "\n};\n";
|
|
s += "\n";
|
|
s += "const uint8_t InstDB::rwInfoIndexB[Inst::_kIdCount] = {\n" + StringUtils.format(this.rwInfoIndexB, kIndent, -1) + "\n};\n";
|
|
s += "\n";
|
|
s += "const InstDB::RWInfo InstDB::rwInfoA[] = {\n" + StringUtils.format(this.rwInfoTableA, kIndent, true) + "\n};\n";
|
|
s += "\n";
|
|
s += "const InstDB::RWInfo InstDB::rwInfoB[] = {\n" + StringUtils.format(this.rwInfoTableB, kIndent, true) + "\n};\n";
|
|
s += "\n";
|
|
s += "const InstDB::RWInfoOp InstDB::rwInfoOp[] = {\n" + StringUtils.format(this.opInfoTable, kIndent, true) + "\n};\n";
|
|
s += "\n";
|
|
s += "const InstDB::RWInfoRm InstDB::rwInfoRm[] = {\n" + StringUtils.format(this.rmInfoTable, kIndent, true) + "\n};\n";
|
|
|
|
const size = this.rwInfoIndexA.length +
|
|
this.rwInfoIndexB.length +
|
|
this.rwInfoTableA.length * 8 +
|
|
this.rwInfoTableB.length * 8 +
|
|
this.rmInfoTable.length * 4 +
|
|
this.opInfoTable.length * 24;
|
|
|
|
this.inject("InstRWInfoTable", disclaimer(s), size);
|
|
}
|
|
|
|
byteMaskFromBitRanges(ranges) {
|
|
const arr = [];
|
|
for (let i = 0; i < 64; i++)
|
|
arr.push(0);
|
|
|
|
for (let i = 0; i < ranges.length; i++) {
|
|
const start = ranges[i].start;
|
|
const end = ranges[i].end;
|
|
|
|
if (start < 0)
|
|
continue;
|
|
|
|
for (let j = start; j <= end; j++) {
|
|
const bytePos = j >> 3;
|
|
if (bytePos < 0 || bytePos >= arr.length)
|
|
FATAL(`Range ${start}:${end} cannot be used to create a byte-mask`);
|
|
arr[bytePos] = 1;
|
|
}
|
|
}
|
|
|
|
let s = "0x";
|
|
for (let i = arr.length - 4; i >= 0; i -= 4) {
|
|
const value = (arr[i + 3] << 3) | (arr[i + 2] << 2) | (arr[i + 1] << 1) | arr[i];
|
|
s += value.toString(16).toUpperCase();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// Read/Write Info
|
|
// ---------------
|
|
|
|
rwInfo(asmInst, dbInsts) {
|
|
const self = this;
|
|
|
|
function nullOps() {
|
|
return [null, null, null, null, null, null];
|
|
}
|
|
|
|
function makeRwFromOp(op) {
|
|
if (!op.isRegOrMem())
|
|
return null;
|
|
|
|
return {
|
|
access: op.read && op.write ? "X" : op.read ? "R" : op.write ? "W" : "?",
|
|
clc: 0,
|
|
flags: {},
|
|
fixed: GenUtils.fixedRegOf(op),
|
|
index: op.rwxIndex,
|
|
width: op.rwxWidth
|
|
};
|
|
}
|
|
|
|
function queryRwGeneric(dbInsts, step) {
|
|
let rwOps = nullOps();
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
const operands = dbInst.operands;
|
|
|
|
for (let j = 0; j < operands.length; j++) {
|
|
const op = operands[j];
|
|
if (!op.isRegOrMem())
|
|
continue;
|
|
|
|
const opSize = op.isReg() ? op.regSize : op.memSize;
|
|
let d = {
|
|
access: op.read && op.write ? "X" : op.read ? "R" : op.write ? "W" : "?",
|
|
clc: 0,
|
|
flags: {},
|
|
fixed: -1,
|
|
index: -1,
|
|
width: -1
|
|
};
|
|
|
|
if (op.consecutiveLeadCount)
|
|
d.clc = op.consecutiveLeadCount;
|
|
|
|
const instName = dbInst.name;
|
|
// NOTE: Avoid push/pop here as PUSH/POP has many variations for segment registers,
|
|
// which would set 'd.fixed' field even for GP variation of the instuction.
|
|
if (instName !== "push" && instName !== "pop") {
|
|
d.fixed = GenUtils.fixedRegOf(op);
|
|
}
|
|
|
|
switch (instName) {
|
|
case "vfcmaddcph":
|
|
case "vfmaddcph":
|
|
case "vfcmaddcsh":
|
|
case "vfmaddcsh":
|
|
case "vfcmulcsh":
|
|
case "vfmulcsh":
|
|
case "vfcmulcph":
|
|
case "vfmulcph":
|
|
if (j === 0)
|
|
d.flags.Unique = true;
|
|
break;
|
|
}
|
|
|
|
if (op.zext)
|
|
d.flags.ZExt = true;
|
|
|
|
if (op.regIndexRel)
|
|
d.flags.Consecutive = true;
|
|
|
|
for (let k in self.rwOpFlagsForInstruction(asmInst.name, j))
|
|
d.flags[k] = true;
|
|
|
|
if ((step === -1 || step === j) || op.rwxIndex !== 0 || op.rwxWidth !== opSize) {
|
|
d.index = op.rwxIndex;
|
|
d.width = op.rwxWidth;
|
|
}
|
|
|
|
if (d.fixed !== -1) {
|
|
if (op.memSegment)
|
|
d.flags.MemPhysId = true;
|
|
else
|
|
d.flags.RegPhysId = true;
|
|
}
|
|
|
|
if (rwOps[j] === null) {
|
|
rwOps[j] = d;
|
|
}
|
|
else {
|
|
if (!ObjectUtils.equalsExcept(rwOps[j], d, { "fixed": true, "flags": true }))
|
|
return null;
|
|
|
|
if (rwOps[j].fixed === -1)
|
|
rwOps[j].fixed = d.fixed;
|
|
ObjectUtils.merge(rwOps[j].flags, d.flags);
|
|
}
|
|
}
|
|
}
|
|
|
|
const name = dbInsts.length ? dbInsts[0].name : "";
|
|
|
|
switch (name) {
|
|
case "vpternlogd":
|
|
case "vpternlogq":
|
|
return { category: "GenericEx", rwOps };
|
|
|
|
default:
|
|
return { category: "Generic", rwOps };
|
|
}
|
|
}
|
|
|
|
function queryRwByData(dbInsts, rwOpsArray) {
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
const operands = dbInst.operands;
|
|
const rwOps = nullOps();
|
|
|
|
for (let j = 0; j < operands.length; j++) {
|
|
rwOps[j] = makeRwFromOp(operands[j])
|
|
}
|
|
|
|
let match = 0;
|
|
for (let j = 0; j < rwOpsArray.length; j++)
|
|
match |= ObjectUtils.equals(rwOps, rwOpsArray[j]);
|
|
|
|
if (!match)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function dumpRwToData(dbInsts) {
|
|
const out = [];
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
const operands = dbInst.operands;
|
|
const rwOps = nullOps();
|
|
|
|
for (let j = 0; j < operands.length; j++)
|
|
rwOps[j] = makeRwFromOp(operands[j])
|
|
|
|
if (ArrayUtils.deepIndexOf(out, rwOps) !== -1)
|
|
continue;
|
|
|
|
out.push(rwOps);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Some instructions are just special...
|
|
const name = dbInsts.length ? dbInsts[0].name : "";
|
|
if (name in this.rwCategoryByName)
|
|
return { category: this.rwCategoryByName[name], rwOps: nullOps() };
|
|
|
|
// Generic rules.
|
|
for (let i = -1; i <= 6; i++) {
|
|
const rwInfo = queryRwGeneric(dbInsts, i);
|
|
if (rwInfo)
|
|
return rwInfo;
|
|
}
|
|
|
|
// Specific rules.
|
|
for (let k in this.rwCategoryByData)
|
|
if (queryRwByData(dbInsts, this.rwCategoryByData[k]))
|
|
return { category: k, rwOps: nullOps() };
|
|
|
|
// FATAL: Missing data to categorize this instruction.
|
|
if (name) {
|
|
const items = dumpRwToData(dbInsts)
|
|
console.log(`RW: ${dbInsts.length ? dbInsts[0].name : ""}:`);
|
|
items.forEach((item) => {
|
|
console.log(" " + JSON.stringify(item));
|
|
});
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
rwOpFlagsForInstruction(instName, opIndex) {
|
|
const toMap = ArrayUtils.toDict;
|
|
|
|
// TODO: We should be able to get this information from asmdb.
|
|
switch (instName + "@" + opIndex) {
|
|
case "cmps@0": return toMap(['MemBaseRW', 'MemBasePostModify']);
|
|
case "cmps@1": return toMap(['MemBaseRW', 'MemBasePostModify']);
|
|
case "movs@0": return toMap(['MemBaseRW', 'MemBasePostModify']);
|
|
case "movs@1": return toMap(['MemBaseRW', 'MemBasePostModify']);
|
|
case "lods@1": return toMap(['MemBaseRW', 'MemBasePostModify']);
|
|
case "stos@0": return toMap(['MemBaseRW', 'MemBasePostModify']);
|
|
case "scas@1": return toMap(['MemBaseRW', 'MemBasePostModify']);
|
|
case "bndstx@0": return toMap(['MemBaseWrite', 'MemIndexWrite']);
|
|
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Reg/Mem Info
|
|
// ------------
|
|
|
|
rmInfo(asmInst, dbInsts) {
|
|
const info = {
|
|
category: "None",
|
|
rmIndexes: this.rmReplaceableIndexes(dbInsts),
|
|
memFixed: this.rmFixedSize(dbInsts),
|
|
memAmbiguous: this.rmIsAmbiguous(dbInsts),
|
|
memConsistent: this.rmIsConsistent(dbInsts),
|
|
memExtension: this.rmExtension(dbInsts),
|
|
memExtensionIfRMI: this.rmExtensionIfRMI(dbInsts)
|
|
};
|
|
|
|
if (info.memFixed !== -1)
|
|
info.category = "Fixed";
|
|
else if (info.memConsistent)
|
|
info.category = "Consistent";
|
|
else if (info.rmIndexes)
|
|
info.category = this.rmReplaceableCategory(dbInsts);
|
|
|
|
return info;
|
|
}
|
|
|
|
rmReplaceableCategory(dbInsts) {
|
|
let category = null;
|
|
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
const operands = dbInst.operands;
|
|
|
|
let rs = -1;
|
|
let ms = -1;
|
|
|
|
for (let j = 0; j < operands.length; j++) {
|
|
const op = operands[j];
|
|
if (op.isMem())
|
|
ms = op.memSize;
|
|
else if (op.isReg())
|
|
rs = Math.max(rs, op.regSize);
|
|
}
|
|
|
|
let c = (rs === -1 ) ? "None" :
|
|
(ms === -1 ) ? "None" :
|
|
(ms === rs ) ? "Fixed" :
|
|
(ms === rs / 2) ? "Half" :
|
|
(ms === rs / 4) ? "Quarter" :
|
|
(ms === rs / 8) ? "Eighth" : "Unknown";
|
|
|
|
if (category === null)
|
|
category = c;
|
|
else if (category !== c) {
|
|
// Special cases.
|
|
if (dbInst.name === "mov" || dbInst.name === "vmovddup")
|
|
return "None";
|
|
|
|
if (/^(punpcklbw|punpckldq|punpcklwd)$/.test(dbInst.name))
|
|
return "None";
|
|
|
|
return cxx.Utils.capitalize(dbInst.name);
|
|
}
|
|
}
|
|
|
|
if (category === "Unknown")
|
|
console.log(`Instruction '${dbInsts[0].name}' has no RMInfo category.`);
|
|
|
|
return category || "Unknown";
|
|
}
|
|
|
|
rmReplaceableIndexes(dbInsts) {
|
|
function maskOf(inst, fn) {
|
|
let m = 0;
|
|
let operands = inst.operands;
|
|
for (let i = 0; i < operands.length; i++)
|
|
if (fn(operands[i]))
|
|
m |= (1 << i);
|
|
return m;
|
|
}
|
|
|
|
function getRegIndexes(inst) { return maskOf(inst, function(op) { return op.isReg(); }); };
|
|
function getMemIndexes(inst) { return maskOf(inst, function(op) { return op.isMem(); }); };
|
|
|
|
let mask = 0;
|
|
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
|
|
let mi = getMemIndexes(dbInst);
|
|
let ri = getRegIndexes(dbInst) & ~mi;
|
|
|
|
if (!mi)
|
|
continue;
|
|
|
|
const match = dbInsts.some((inst) => {
|
|
let ti = getRegIndexes(inst);
|
|
return ((ri & ti) === ri && (mi & ti) === mi);
|
|
});
|
|
|
|
if (!match)
|
|
return 0;
|
|
mask |= mi;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
rmFixedSize(insts) {
|
|
let savedOp = null;
|
|
|
|
for (let i = 0; i < insts.length; i++) {
|
|
const inst = insts[i];
|
|
const operands = inst.operands;
|
|
|
|
for (let j = 0; j < operands.length; j++) {
|
|
const op = operands[j];
|
|
if (op.mem) {
|
|
if (savedOp && savedOp.mem !== op.mem)
|
|
return -1;
|
|
savedOp = op;
|
|
}
|
|
}
|
|
}
|
|
|
|
return savedOp ? Math.max(savedOp.memSize, 0) / 8 : -1;
|
|
}
|
|
|
|
rmIsConsistent(insts) {
|
|
let hasMem = 0;
|
|
for (let i = 0; i < insts.length; i++) {
|
|
const inst = insts[i];
|
|
const operands = inst.operands;
|
|
for (let j = 0; j < operands.length; j++) {
|
|
const op = operands[j];
|
|
if (op.mem) {
|
|
hasMem = 1;
|
|
if (!op.reg)
|
|
return 0;
|
|
if (asmdb.x86.Utils.regSize(op.reg) !== op.memSize)
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return hasMem;
|
|
}
|
|
|
|
rmIsAmbiguous(dbInsts) {
|
|
function isAmbiguous(dbInsts) {
|
|
const memMap = {};
|
|
const immMap = {};
|
|
|
|
for (let i = 0; i < dbInsts.length; i++) {
|
|
const dbInst = dbInsts[i];
|
|
const operands = dbInst.operands;
|
|
|
|
let memStr = "";
|
|
let immStr = "";
|
|
let hasMem = false;
|
|
let hasImm = false;
|
|
|
|
for (let j = 0; j < operands.length; j++) {
|
|
const op = operands[j];
|
|
if (j) {
|
|
memStr += ", ";
|
|
immStr += ", ";
|
|
}
|
|
|
|
if (op.isImm()) {
|
|
immStr += "imm";
|
|
hasImm = true;
|
|
}
|
|
else {
|
|
immStr += op.toString();
|
|
}
|
|
|
|
if (op.mem) {
|
|
memStr += "m";
|
|
hasMem = true;
|
|
}
|
|
else {
|
|
memStr += op.isImm() ? "imm" : op.toString();
|
|
}
|
|
}
|
|
|
|
if (hasImm) {
|
|
if (immMap[immStr] === true)
|
|
continue;
|
|
immMap[immStr] = true;
|
|
}
|
|
|
|
if (hasMem) {
|
|
if (memMap[memStr] === true)
|
|
return 1;
|
|
memMap[memStr] = true;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const uniqueInsts = Filter.unique(dbInsts);
|
|
|
|
// Special cases.
|
|
if (!dbInsts.length)
|
|
return 0;
|
|
|
|
if (NOT_MEM_AMBIGUOUS[dbInsts[0].name])
|
|
return 0;
|
|
|
|
return (isAmbiguous(Filter.byArch(uniqueInsts, "X86")) << 0) |
|
|
(isAmbiguous(Filter.byArch(uniqueInsts, "X64")) << 1) ;
|
|
}
|
|
|
|
rmExtension(dbInsts) {
|
|
if (!dbInsts.length)
|
|
return "None";
|
|
|
|
const name = dbInsts[0].name;
|
|
switch (name) {
|
|
case "pextrw":
|
|
return "SSE4_1";
|
|
|
|
case "vpslld":
|
|
case "vpsllq":
|
|
case "vpsrad":
|
|
case "vpsrld":
|
|
case "vpsrlq":
|
|
return "AVX512_F";
|
|
|
|
case "vpslldq":
|
|
case "vpsllw":
|
|
case "vpsraw":
|
|
case "vpsrldq":
|
|
case "vpsrlw":
|
|
return "AVX512_BW";
|
|
|
|
default:
|
|
return "None";
|
|
}
|
|
}
|
|
|
|
rmExtensionIfRMI(dbInsts) {
|
|
if (!dbInsts.length)
|
|
return 0;
|
|
|
|
const name = dbInsts[0].name;
|
|
return /^(vpslld|vpsllq|vpsrad|vpsrld|vpsrlq|vpslldq|vpsllw|vpsraw|vpsrldq|vpsrlw)$/.test(name);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [tablegen.x86.InstCommonTable]
|
|
// ============================================================================
|
|
|
|
class InstCommonTable extends core.Task {
|
|
constructor() {
|
|
super("InstCommonTable", [
|
|
"IdEnum",
|
|
"NameTable",
|
|
"InstSignatureTable",
|
|
"AdditionalInfoTable",
|
|
"InstRWInfoTable"
|
|
]);
|
|
}
|
|
|
|
run() {
|
|
const insts = this.ctx.insts;
|
|
const table = new IndexedArray();
|
|
|
|
insts.forEach((inst) => {
|
|
const commonFlagsArray = inst.flags.filter((flag) => { return !flag.startsWith("Avx512"); });
|
|
const avx512FlagsArray = inst.flags.filter((flag) => { return flag.startsWith("Avx512"); });
|
|
|
|
const commonFlags = commonFlagsArray.map(function(flag) { return `F(${flag })`; }).join("|") || "0";
|
|
const avx512Flags = avx512FlagsArray.map(function(flag) { return `X(${flag.substr(6)})`; }).join("|") || "0";
|
|
|
|
const controlFlow = `CONTROL_FLOW(${inst.controlFlow})`;
|
|
const singleRegCase = `SAME_REG_HINT(${inst.singleRegCase})`;
|
|
|
|
const row = "{ " +
|
|
String(commonFlags ).padEnd(50) + ", " +
|
|
String(avx512Flags ).padEnd(30) + ", " +
|
|
String(inst.signatureIndex).padEnd( 3) + ", " +
|
|
String(inst.signatureCount).padEnd( 2) + ", " +
|
|
String(controlFlow ).padEnd(16) + ", " +
|
|
String(singleRegCase ).padEnd(16) + "}";
|
|
inst.commonInfoIndex = table.addIndexed(row);
|
|
});
|
|
|
|
let s = `#define F(VAL) uint32_t(InstDB::InstFlags::k##VAL)\n` +
|
|
`#define X(VAL) uint32_t(InstDB::Avx512Flags::k##VAL)\n` +
|
|
`#define CONTROL_FLOW(VAL) uint8_t(InstControlFlow::k##VAL)\n` +
|
|
`#define SAME_REG_HINT(VAL) uint8_t(InstSameRegHint::k##VAL)\n` +
|
|
`const InstDB::CommonInfo InstDB::_commonInfoTable[] = {\n${StringUtils.format(table, kIndent, true)}\n};\n` +
|
|
`#undef SAME_REG_HINT\n` +
|
|
`#undef CONTROL_FLOW\n` +
|
|
`#undef X\n` +
|
|
`#undef F\n`;
|
|
this.inject("InstCommonTable", disclaimer(s), table.length * 8);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// [Main]
|
|
// ============================================================================
|
|
|
|
new X86TableGen()
|
|
.addTask(new IdEnum())
|
|
.addTask(new NameTable())
|
|
.addTask(new AltOpcodeTable())
|
|
.addTask(new InstSignatureTable())
|
|
.addTask(new AdditionalInfoTable())
|
|
.addTask(new InstRWInfoTable())
|
|
.addTask(new InstCommonTable())
|
|
.run();
|