123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917 |
- // [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 a 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;
|