Files
asmjit/tools/tablegen.js
Petr Kobalicek 5d40561d14 Refactored register allocator asm Compiler. (#249)
Refactored build system macros (ASMJIT_BUILD_STATIC -> ASMJIT_STATIC)
Refactored AVX512 broadcast {1toN} - moved to operand from instruction.
Refactored naming - renamed getters to not use get prefix.
Refactored code structure - move arch-specific stuff into x86 namespace.
Refactored some compiler/arch-specific macros, respect rel/abs option in mov REG, [ADDR].
Refactored StringBuilder (Renamed to String, added small string optimization).
Refactored LabelId<->LabelEntry mapping, force label offset to 64-bits on all archs.
Renamed Runtime to Target (JitRuntime kept for now).
Renamed VirtMemManager to JitAllocator.
Renamed VirtMem to JitUtils.
Renamed FuncSignatureX to FuncSignatureBuilder.
Fixed xchg [mem], rex-lo, refactored RelocEntry.
Fixed Logger to always show abs|rel when formatting a memory operand
Fixed Logger to prefix HEX numbers with 0x prefix
Fixed Support::ctzGeneric to always return uint32_t, T doesn't matter.
Fixed LightCall to not save MMX and K registers
Fixed CpuInfo constructor to propagate NoInit (#243)
Added VAES, AVX512_VBMI2, AVX512_VNNI, and AVX512_BITALG cpu-features and instructions.
Added emscripten support (asmjit can be now compiled by emscripten).
Added asmjit.natvis for better MSVC experience
Added x86::ptr_abs|ptr_rel
Added support for multibyte nop r/m (#135)
Added support for 32-bit to 64-bit zero-extended addresses, improved validation of memory addresses, and removed wrt address type as this will be reworked
Added support for multiple sections, reworked address table support (previously known as trampolines)
Added the following x86 modifiers to the x86::Emitter - xacquire(), xrelease(), and k(kreg)
Added a possibility to use REP prefix with RET instruction
Added a possibility to relocate [rel addr] during relocate()
Added a variadic function-call support (Compiler), argument duplication (Compiler), better /dev/shm vs /tmp shared memory handling (VirtMem).
Removed imm_u imm_ptr helpers, imm() can now accept any integer and pointer.
Changed the default behavior of optimizing instructions to disabled with a possibility to enable that feature through kOptionOptimizedForSize
Use default copy construction / assignment to prevent new kind of warnings introduced by GCC 9
2019-07-16 01:24:22 +02:00

918 lines
22 KiB
JavaScript

// [AsmJit]
// Machine Code Generation for C++.
//
// [License]
// ZLIB - See LICENSE.md file in the package.
// ============================================================================
// tablegen.js
//
// Provides core foundation for generating tables that AsmJit requires. This
// file should provide everything table generators need in general.
// ============================================================================
"use strict";
const VERBOSE = false;
// ============================================================================
// [Imports]
// ============================================================================
const fs = require("fs");
const hasOwn = Object.prototype.hasOwnProperty;
const asmdb = (function() {
// Try to import local 'asmdb' package, if available.
try {
return require("./asmdb");
}
catch (ex) {
if (ex.code !== "MODULE_NOT_FOUND") {
console.log(`FATAL ERROR: ${ex.message}`);
throw ex;
}
}
// Try to import global 'asmdb' package as local package is not available.
return require("asmdb");
})();
exports.asmdb = asmdb;
// ============================================================================
// [Constants]
// ============================================================================
const kIndent = " ";
const kJustify = 119;
const kAsmJitRoot = "..";
exports.kIndent = kIndent;
exports.kJustify = kJustify;
exports.kAsmJitRoot = kAsmJitRoot;
// ============================================================================
// [Debugging]
// ============================================================================
function DEBUG(msg) {
if (VERBOSE)
console.log(msg);
}
exports.DEBUG = DEBUG;
function WARN(msg) {
console.log(msg);
}
exports.WARN = WARN;
function FAIL(msg) {
console.log(`FATAL ERROR: ${msg}`);
throw new Error(msg);
}
exports.FAIL = FAIL;
// ============================================================================
// [Lang]
// ============================================================================
function nop(x) { return x; }
class Lang {
static merge(a, b) {
if (a === b)
return a;
for (var k in b) {
var av = a[k];
var bv = b[k];
if (typeof av === "object" && typeof bv === "object")
Lang.merge(av, bv);
else
a[k] = bv;
}
return a;
}
static deepEq(a, b) {
if (a === b)
return true;
if (typeof a !== typeof b)
return false;
if (typeof a !== "object")
return a === b;
if (Array.isArray(a) || Array.isArray(b)) {
if (Array.isArray(a) !== Array.isArray(b))
return false;
const len = a.length;
if (b.length !== len)
return false;
for (var i = 0; i < len; i++)
if (!Lang.deepEq(a[i], b[i]))
return false;
}
else {
if (a === null || b === null)
return a === b;
for (var k in a)
if (!hasOwn.call(b, k) || !Lang.deepEq(a[k], b[k]))
return false;
for (var k in b)
if (!hasOwn.call(a, k))
return false;
}
return true;
}
static deepEqExcept(a, b, except) {
if (a === b)
return true;
if (typeof a !== "object" || typeof b !== "object" || Array.isArray(a) || Array.isArray(b))
return Lang.deepEq(a, b);
for (var k in a)
if (!hasOwn.call(except, k) && (!hasOwn.call(b, k) || !Lang.deepEq(a[k], b[k])))
return false;
for (var k in b)
if (!hasOwn.call(except, k) && !hasOwn.call(a, k))
return false;
return true;
}
}
exports.Lang = Lang;
// ============================================================================
// [StringUtils]
// ============================================================================
class StringUtils {
static asString(x) { return String(x); }
static capitalize(s) {
s = String(s);
return !s ? s : s[0].toUpperCase() + s.substr(1);
}
static trimLeft(s) { return s.replace(/^\s+/, ""); }
static trimRight(s) { return s.replace(/\s+$/, ""); }
static upFirst(s) {
if (!s) return "";
return s[0].toUpperCase() + s.substr(1);
}
static decToHex(n, nPad) {
var hex = Number(n < 0 ? 0x100000000 + n : n).toString(16);
while (nPad > hex.length)
hex = "0" + hex;
return "0x" + hex.toUpperCase();
}
static format(array, indent, showIndex, mapFn) {
if (!mapFn)
mapFn = StringUtils.asString;
var s = "";
var threshold = 80;
if (showIndex === -1)
s += indent;
for (var i = 0; i < array.length; i++) {
const item = array[i];
const last = i === array.length - 1;
if (showIndex !== -1)
s += indent;
s += mapFn(item);
if (showIndex > 0) {
s += `${last ? " " : ","} // #${i}`;
if (typeof array.refCountOf === "function")
s += ` [ref=${array.refCountOf(item)}x]`;
}
else if (!last) {
s += ",";
}
if (showIndex === -1) {
if (s.length >= threshold - 1 && !last) {
s += "\n" + indent;
threshold += 80;
}
else {
if (!last) s += " ";
}
}
else {
if (!last) s += "\n";
}
}
return s;
}
static makeCxxArray(array, code, indent) {
if (!indent) indent = kIndent;
return `${code} = {\n${indent}` + array.join(`,\n${indent}`) + `\n};\n`;
}
static makeCxxArrayWithComment(array, code, indent) {
if (!indent) indent = kIndent;
var s = "";
for (var i = 0; i < array.length; i++) {
const last = i === array.length - 1;
s += indent + array[i].data +
(last ? " // " : ", // ") + (array[i].refs ? "#" + String(i) : "").padEnd(5) + array[i].comment + "\n";
}
return `${code} = {\n${s}};\n`;
}
static disclaimer(s) {
return "// ------------------- Automatically generated, do not edit -------------------\n" +
s +
"// ----------------------------------------------------------------------------\n";
}
static indent(s, indentation) {
var lines = s.split(/\r?\n/g);
if (indentation) {
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line) lines[i] = indentation + line;
}
}
return lines.join("\n");
}
static inject(s, start, end, code) {
var iStart = s.indexOf(start);
var iEnd = s.indexOf(end);
if (iStart === -1)
FAIL(`Utils.inject(): Couldn't locate start mark '${start}'`);
if (iEnd === -1)
FAIL(`Utils.inject(): Couldn't locate end mark '${end}'`);
var nIndent = 0;
while (iStart > 0 && s[iStart-1] === " ") {
iStart--;
nIndent++;
}
if (nIndent) {
const indentation = " ".repeat(nIndent);
code = StringUtils.indent(code, indentation) + indentation;
}
return s.substr(0, iStart + start.length + nIndent) + code + s.substr(iEnd);
}
static makePriorityCompare(priorityArray) {
const map = Object.create(null);
priorityArray.forEach((str, index) => { map[str] = index; });
return function(a, b) {
const ax = hasOwn.call(map, a) ? map[a] : Infinity;
const bx = hasOwn.call(map, b) ? map[b] : Infinity;
return ax != bx ? ax - bx : a < b ? -1 : a > b ? 1 : 0;
}
}
}
exports.StringUtils = StringUtils;
// ============================================================================
// [ArrayUtils]
// ============================================================================
class ArrayUtils {
static min(arr, fn) {
if (!arr.length)
return null;
if (!fn)
fn = nop;
var v = fn(arr[0]);
for (var i = 1; i < arr.length; i++)
v = Math.min(v, fn(arr[i]));
return v;
}
static max(arr, fn) {
if (!arr.length)
return null;
if (!fn)
fn = nop;
var v = fn(arr[0]);
for (var i = 1; i < arr.length; i++)
v = Math.max(v, fn(arr[i]));
return v;
}
static sorted(obj, cmp) {
const out = Array.isArray(obj) ? obj.slice() : Object.getOwnPropertyNames(obj);
out.sort(cmp);
return out;
}
static deepIndexOf(arr, what) {
for (var i = 0; i < arr.length; i++)
if (Lang.deepEq(arr[i], what))
return i;
return -1;
}
}
exports.ArrayUtils = ArrayUtils;
// ============================================================================
// [MapUtils]
// ============================================================================
class MapUtils {
static clone(map) {
return Object.assign(Object.create(null), map);
}
static arrayToMap(arr, value) {
if (value === undefined)
value = true;
const out = Object.create(null);
for (var i = 0; i < arr.length; i++)
out[arr[i]] = value;
return out;
}
static equals(a, b) {
for (var k in a) if (!hasOwn.call(b, k)) return false;
for (var k in b) if (!hasOwn.call(a, k)) return false;
return true;
}
static firstOf(map, flags) {
for (var k in flags)
if (hasOwn.call(map, k))
return k;
return undefined;
}
static anyOf(map, flags) {
for (var k in flags)
if (hasOwn.call(map, k))
return true;
return false;
}
static add(a, b) {
for (var k in b)
a[k] = b[k];
return a;
}
static and(a, b) {
const out = Object.create(null);
for (var k in a)
if (hasOwn.call(b, k))
out[k] = true;
return out;
}
static xor(a, b) {
const out = Object.create(null);
for (var k in a) if (!hasOwn.call(b, k)) out[k] = true;
for (var k in b) if (!hasOwn.call(a, k)) out[k] = true;
return out;
}
};
exports.MapUtils = MapUtils;
// ============================================================================
// [CxxUtils]
// ============================================================================
class CxxUtils {
static flags(obj, fn) {
if (!fn)
fn = nop;
var out = "";
for (var k in obj) {
if (obj[k])
out += (out ? " | " : "") + fn(k);
}
return out ? out : "0";
}
static struct(...args) {
return "{ " + args.join(", ") + " }";
}
};
exports.CxxUtils = CxxUtils;
// ============================================================================
// [IndexedString]
// ============================================================================
// IndexedString is mostly used to merge all instruction names into a single
// string with external index. It's designed mostly for generating C++ tables.
//
// Consider the following cases in C++:
//
// a) static const char* const* instNames = { "add", "mov", "vpunpcklbw" };
//
// b) static const char instNames[] = { "add\0" "mov\0" "vpunpcklbw\0" };
// static const uint16_t instNameIndex[] = { 0, 4, 8 };
//
// The latter (b) has an advantage that it doesn't have to be relocated by the
// linker, which saves a lot of space in the resulting binary and a lot of CPU
// cycles (and memory) when the linker loads it. AsmJit supports thousands of
// instructions so each optimization like this makes it smaller and faster to
// load.
class IndexedString {
constructor() {
this.map = Object.create(null);
this.array = [];
this.size = -1;
}
add(s) {
this.map[s] = -1;
}
index() {
const map = this.map;
const array = this.array;
const partialMap = Object.create(null);
var k, kp;
var i, len;
// Create a map that will contain all keys and partial keys.
for (k in map) {
if (!k) {
partialMap[k] = k;
}
else {
for (i = 0, len = k.length; i < len; i++) {
kp = k.substr(i);
if (!hasOwn.call(partialMap, kp) || partialMap[kp].length < len)
partialMap[kp] = k;
}
}
}
// Create an array that will only contain keys that are needed.
for (k in map)
if (partialMap[k] === k)
array.push(k);
array.sort();
// Create valid offsets to the `array`.
var offMap = Object.create(null);
var offset = 0;
for (i = 0, len = array.length; i < len; i++) {
k = array[i];
offMap[k] = offset;
offset += k.length + 1;
}
this.size = offset;
// Assign valid offsets to `map`.
for (kp in map) {
k = partialMap[kp];
map[kp] = offMap[k] + k.length - kp.length;
}
}
format(indent, justify) {
if (this.size === -1)
FAIL(`IndexedString.format(): not indexed yet, call index()`);
const array = this.array;
if (!justify) justify = 0;
var i;
var s = "";
var line = "";
for (i = 0; i < array.length; i++) {
const item = "\"" + array[i] + ((i !== array.length - 1) ? "\\0\"" : "\";");
const newl = line + (line ? " " : indent) + item;
if (newl.length <= justify) {
line = newl;
continue;
}
else {
s += line + "\n";
line = indent + item;
}
}
return s + line;
}
getSize() {
if (this.size === -1)
FAIL(`IndexedString.getSize(): Not indexed yet, call index()`);
return this.size;
}
getIndex(k) {
if (this.size === -1)
FAIL(`IndexedString.getIndex(): Not indexed yet, call index()`);
if (!hasOwn.call(this.map, k))
FAIL(`IndexedString.getIndex(): Key '${k}' not found.`);
return this.map[k];
}
}
exports.IndexedString = IndexedString;
// ============================================================================
// [IndexedArray]
// ============================================================================
// IndexedArray is an Array replacement that allows to index each item inserted
// to it. Its main purpose is to avoid data duplication, if an item passed to
// `addIndexed()` is already within the Array then it's not inserted and the
// existing index is returned instead.
function IndexedArray_keyOf(item) {
return typeof item === "string" ? item : JSON.stringify(item);
}
class IndexedArray extends Array {
constructor() {
super();
this._index = Object.create(null);
}
refCountOf(item) {
const key = IndexedArray_keyOf(item);
const idx = this._index[key];
return idx !== undefined ? idx.refCount : 0;
}
addIndexed(item) {
const key = IndexedArray_keyOf(item);
var idx = this._index[key];
if (idx !== undefined) {
idx.refCount++;
return idx.data;
}
idx = this.length;
this._index[key] = {
data: idx,
refCount: 1
};
this.push(item);
return idx;
}
}
exports.IndexedArray = IndexedArray;
// ============================================================================
// [Task]
// ============================================================================
// A base runnable task that can access the TableGen through `this.ctx`.
class Task {
constructor(name, deps) {
this.ctx = null;
this.name = name || "";
this.deps = deps || [];
}
inject(key, str, size) {
this.ctx.inject(key, str, size);
return this;
}
run() {
FAIL("Task.run(): Must be reimplemented");
}
}
exports.Task = Task;
// ============================================================================
// [TableGen]
// ============================================================================
// Main context used to load, generate, and store instruction tables. The idea
// is to be extensible, so it stores 'Task's to be executed with minimal deps
// management.
class TableGen {
constructor(arch) {
this.arch = arch;
this.files = Object.create(null);
this.tableSizes = Object.create(null);
this.tasks = [];
this.taskMap = Object.create(null);
this.insts = [];
this.instMap = Object.create(null);
this.aliases = [];
this.aliasMem = Object.create(null);
}
// --------------------------------------------------------------------------
// [File Management]
// --------------------------------------------------------------------------
load(fileList) {
for (var i = 0; i < fileList.length; i++) {
const file = fileList[i];
const path = kAsmJitRoot + "/" + file;
const data = fs.readFileSync(path, "utf8").replace(/\r\n/g, "\n");
this.files[file] = {
prev: data,
data: data
};
}
return this;
}
save() {
for (var file in this.files) {
const obj = this.files[file];
if (obj.data !== obj.prev) {
const path = kAsmJitRoot + "/" + file;
console.log(`MODIFIED '${file}'`);
fs.writeFileSync(path + ".backup", obj.prev, "utf8");
fs.writeFileSync(path, obj.data, "utf8");
}
}
}
dataOfFile(file) {
const obj = this.files[file];
if (!obj)
FAIL(`TableGen.dataOfFile(): File '${file}' not loaded`);
return obj.data;
}
inject(key, str, size) {
const begin = "// ${" + key + ":Begin}\n";
const end = "// ${" + key + ":End}\n";
var done = false;
for (var file in this.files) {
const obj = this.files[file];
const data = obj.data;
if (data.indexOf(begin) !== -1) {
obj.data = StringUtils.inject(data, begin, end, str);
done = true;
break;
}
}
if (!done)
FAIL(`TableGen.inject(): Cannot find '${key}'`);
if (size)
this.tableSizes[key] = size;
return this;
}
// --------------------------------------------------------------------------
// [Task Management]
// --------------------------------------------------------------------------
addTask(task) {
if (!task.name)
FAIL(`TableGen.addModule(): Module must have a name`);
if (this.taskMap[task.name])
FAIL(`TableGen.addModule(): Module '${task.name}' already added`);
task.deps.forEach((dependency) => {
if (!this.taskMap[dependency])
FAIL(`TableGen.addModule(): Dependency '${dependency}' of module '${task.name}' doesn't exist`);
});
this.tasks.push(task);
this.taskMap[task.name] = task;
task.ctx = this;
return this;
}
runTasks() {
const tasks = this.tasks;
const tasksDone = Object.create(null);
var pending = tasks.length;
while (pending) {
const oldPending = pending;
const arrPending = [];
for (var i = 0; i < tasks.length; i++) {
const task = tasks[i];
if (tasksDone[task.name])
continue;
if (task.deps.every((dependency) => { return tasksDone[dependency] === true; })) {
task.run();
tasksDone[task.name] = true;
pending--;
}
else {
arrPending.push(task.name);
}
}
if (oldPending === pending)
throw Error(`TableGen.runModules(): Modules '${arrPending.join("|")}' stuck (cyclic dependency?)`);
}
}
// --------------------------------------------------------------------------
// [Instruction Management]
// --------------------------------------------------------------------------
addInst(inst) {
if (this.instMap[inst.name])
FAIL(`TableGen.addInst(): Instruction '${inst.name}' already added`);
inst.id = this.insts.length;
this.insts.push(inst);
this.instMap[inst.name] = inst;
return this;
}
addAlias(alias, name) {
this.aliases.push(alias);
this.aliasMap[alias] = name;
return this;
}
// --------------------------------------------------------------------------
// [Run]
// --------------------------------------------------------------------------
run() {
this.onBeforeRun();
this.runTasks();
this.onAfterRun();
}
// --------------------------------------------------------------------------
// [Other]
// --------------------------------------------------------------------------
dumpTableSizes() {
const sizes = this.tableSizes;
var pad = 26;
var total = 0;
for (var name in sizes) {
const size = sizes[name];
total += size;
console.log(("Size of " + name).padEnd(pad) + ": " + size);
}
console.log("Size of all tables".padEnd(pad) + ": " + total);
}
// --------------------------------------------------------------------------
// [Hooks]
// --------------------------------------------------------------------------
onBeforeRun() {}
onAfterRun() {}
}
exports.TableGen = TableGen;
// ============================================================================
// [IdEnum]
// ============================================================================
class IdEnum extends Task {
constructor(name, deps) {
super(name || "IdEnum", deps);
}
comment(name) {
FAIL("IdEnum.comment(): Must be reimplemented");
}
run() {
const insts = this.ctx.insts;
var s = "";
for (var i = 0; i < insts.length; i++) {
const inst = insts[i];
var line = "kId" + inst.enum + (i ? "" : " = 0") + ",";
var text = this.comment(inst);
if (text)
line = line.padEnd(37) + "//!< " + text;
s += line + "\n";
}
s += "_kIdCount\n";
return this.ctx.inject("InstId", s);
}
}
exports.IdEnum = IdEnum;
// ============================================================================
// [NameTable]
// ============================================================================
class NameTable extends Task {
constructor(name, deps) {
super(name || "NameTable", deps);
}
run() {
const arch = this.ctx.arch;
const none = "Inst::kIdNone";
const insts = this.ctx.insts;
const instNames = new IndexedString();
const instFirst = new Array(26);
const instLast = new Array(26);
var maxLength = 0;
for (var i = 0; i < insts.length; i++) {
const inst = insts[i];
instNames.add(inst.name);
maxLength = Math.max(maxLength, inst.name.length);
}
instNames.index();
for (var i = 0; i < insts.length; i++) {
const inst = insts[i];
const name = inst.name;
const nameIndex = instNames.getIndex(name);
const index = name.charCodeAt(0) - 'a'.charCodeAt(0);
if (index < 0 || index >= 26)
FAIL(`TableGen.generateNameData(): Invalid lookup character '${name[0]}' of '${name}'`);
inst.nameIndex = nameIndex;
if (instFirst[index] === undefined)
instFirst[index] = `Inst::kId${inst.enum}`;
instLast[index] = `Inst::kId${inst.enum}`;
}
var s = "";
s += `const char InstDB::_nameData[] =\n${instNames.format(kIndent, kJustify)}\n`;
s += `\n`;
s += `const InstDB::InstNameIndex InstDB::instNameIndex[26] = {\n`;
for (var i = 0; i < instFirst.length; i++) {
const firstId = instFirst[i] || none;
const lastId = instLast[i] || none;
s += ` { ${String(firstId).padEnd(22)}, ${String(lastId).padEnd(22)} + 1 }`;
if (i !== 26 - 1)
s += `,`;
s += `\n`;
}
s += `};\n`;
this.ctx.inject("NameLimits",
StringUtils.disclaimer(`enum : uint32_t { kMaxNameSize = ${maxLength} };\n`));
return this.ctx.inject("NameData", StringUtils.disclaimer(s), instNames.getSize() + 26 * 4);
}
}
exports.NameTable = NameTable;