569 lines
15 KiB
JavaScript
569 lines
15 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
|
||
|
|
||
|
// ============================================================================
|
||
|
// tablegen.js
|
||
|
//
|
||
|
// Provides core foundation for generating tables that AsmJit requires. This
|
||
|
// file should provide everything table generators need in general.
|
||
|
// ============================================================================
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
// ============================================================================
|
||
|
// [Imports]
|
||
|
// ============================================================================
|
||
|
|
||
|
const fs = require("fs");
|
||
|
|
||
|
const commons = require("./generator-commons.js");
|
||
|
const cxx = require("./generator-cxx.js");
|
||
|
const asmdb = require("../db");
|
||
|
|
||
|
exports.asmdb = asmdb;
|
||
|
exports.exp = asmdb.base.exp;
|
||
|
|
||
|
const hasOwn = Object.prototype.hasOwnProperty;
|
||
|
|
||
|
const FATAL = commons.FATAL;
|
||
|
const StringUtils = commons.StringUtils;
|
||
|
|
||
|
const kAsmJitRoot = "..";
|
||
|
exports.kAsmJitRoot = kAsmJitRoot;
|
||
|
|
||
|
// ============================================================================
|
||
|
// [InstructionNameData]
|
||
|
// ============================================================================
|
||
|
|
||
|
function charTo5Bit(c) {
|
||
|
if (c >= 'a' && c <= 'z')
|
||
|
return 1 + (c.charCodeAt(0) - 'a'.charCodeAt(0));
|
||
|
else if (c >= '0' && c <= '4')
|
||
|
return 1 + 26 + (c.charCodeAt(0) - '0'.charCodeAt(0));
|
||
|
else
|
||
|
FATAL(`Character '${c}' cannot be encoded into a 5-bit string`);
|
||
|
}
|
||
|
|
||
|
class InstructionNameData {
|
||
|
constructor() {
|
||
|
this.names = [];
|
||
|
this.primaryTable = [];
|
||
|
this.stringTable = "";
|
||
|
this.size = 0;
|
||
|
this.indexComment = [];
|
||
|
this.maxNameLength = 0;
|
||
|
}
|
||
|
|
||
|
add(s) {
|
||
|
// First try to encode the string with 5-bit characters that fit into a 32-bit int.
|
||
|
if (/^[a-z0-4]{0,6}$/.test(s)) {
|
||
|
let index = 0;
|
||
|
for (let i = 0; i < s.length; i++)
|
||
|
index |= charTo5Bit(s[i]) << (i * 5);
|
||
|
|
||
|
this.names.push(s);
|
||
|
this.primaryTable.push(index | (1 << 31));
|
||
|
this.indexComment.push(`Small '${s}'.`);
|
||
|
}
|
||
|
else {
|
||
|
// Put the string into a string table.
|
||
|
this.names.push(s);
|
||
|
this.primaryTable.push(-1);
|
||
|
this.indexComment.push(``);
|
||
|
}
|
||
|
|
||
|
if (this.maxNameLength < s.length)
|
||
|
this.maxNameLength = s.length;
|
||
|
}
|
||
|
|
||
|
index() {
|
||
|
const kMaxPrefixSize = 15;
|
||
|
const kMaxSuffixSize = 7;
|
||
|
const names = [];
|
||
|
|
||
|
for (let idx = 0; idx < this.primaryTable.length; idx++) {
|
||
|
if (this.primaryTable[idx] === -1) {
|
||
|
names.push({ name: this.names[idx], index: idx });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
names.sort(function(a, b) {
|
||
|
if (a.name.length > b.name.length)
|
||
|
return -1;
|
||
|
if (a.name.length < b.name.length)
|
||
|
return 1;
|
||
|
return (a > b) ? 1 : (a < b) ? -1 : 0;
|
||
|
});
|
||
|
|
||
|
for (let z = 0; z < names.length; z++) {
|
||
|
const idx = names[z].index;
|
||
|
const name = names[z].name;
|
||
|
|
||
|
let done = false;
|
||
|
let longestPrefix = 0;
|
||
|
let longestSuffix = 0;
|
||
|
|
||
|
let prefix = "";
|
||
|
let suffix = "";
|
||
|
|
||
|
for (let i = Math.min(name.length, kMaxPrefixSize); i > 0; i--) {
|
||
|
prefix = name.substring(0, i);
|
||
|
suffix = name.substring(i);
|
||
|
|
||
|
const prefixIndex = this.stringTable.indexOf(prefix);
|
||
|
const suffixIndex = this.stringTable.indexOf(suffix);
|
||
|
|
||
|
// Matched both parts?
|
||
|
if (prefixIndex !== -1 && suffix === "") {
|
||
|
done = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (prefixIndex !== -1 && suffixIndex !== -1) {
|
||
|
done = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (prefixIndex !== -1 && longestPrefix === 0)
|
||
|
longestPrefix = prefix.length;
|
||
|
|
||
|
if (suffixIndex !== -1 && suffix.length > longestSuffix)
|
||
|
longestSuffix = suffix.length;
|
||
|
|
||
|
if (suffix.length === kMaxSuffixSize)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!done) {
|
||
|
let minPrefixSize = name.length >= 8 ? name.length / 2 + 1 : name.length - 2;
|
||
|
|
||
|
prefix = "";
|
||
|
suffix = "";
|
||
|
|
||
|
if (longestPrefix >= minPrefixSize) {
|
||
|
prefix = name.substring(0, longestPrefix);
|
||
|
suffix = name.substring(longestPrefix);
|
||
|
}
|
||
|
else if (longestSuffix) {
|
||
|
const splitAt = Math.min(name.length - longestSuffix, kMaxPrefixSize);;
|
||
|
prefix = name.substring(0, splitAt);
|
||
|
suffix = name.substring(splitAt);
|
||
|
}
|
||
|
else if (name.length > kMaxPrefixSize) {
|
||
|
prefix = name.substring(0, kMaxPrefixSize);
|
||
|
suffix = name.substring(kMaxPrefixSize);
|
||
|
}
|
||
|
else {
|
||
|
prefix = name;
|
||
|
suffix = "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (suffix) {
|
||
|
const prefixIndex = this.addOrReferenceString(prefix);
|
||
|
const suffixIndex = this.addOrReferenceString(suffix);
|
||
|
|
||
|
this.primaryTable[idx] = prefixIndex | (prefix.length << 12) | (suffixIndex << 16) | (suffix.length << 28);
|
||
|
this.indexComment[idx] = `Large '${prefix}|${suffix}'.`;
|
||
|
}
|
||
|
else {
|
||
|
const prefixIndex = this.addOrReferenceString(prefix);
|
||
|
|
||
|
this.primaryTable[idx] = prefixIndex | (prefix.length << 12);
|
||
|
this.indexComment[idx] = `Large '${prefix}'.`;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
addOrReferenceString(s) {
|
||
|
let index = this.stringTable.indexOf(s);
|
||
|
if (index === -1) {
|
||
|
index = this.stringTable.length;
|
||
|
this.stringTable += s;
|
||
|
}
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
formatIndexTable(tableName) {
|
||
|
if (this.size === -1)
|
||
|
FATAL(`IndexedString.formatIndexTable(): Not indexed yet, call index()`);
|
||
|
|
||
|
let s = "";
|
||
|
for (let i = 0; i < this.primaryTable.length; i++) {
|
||
|
s += cxx.Utils.toHex(this.primaryTable[i], 8);
|
||
|
s += i !== this.primaryTable.length - 1 ? "," : " ";
|
||
|
s += " // " + this.indexComment[i] + "\n";
|
||
|
}
|
||
|
|
||
|
return `const uint32_t ${tableName}[] = {\n${StringUtils.indent(s, " ")}};\n`;
|
||
|
}
|
||
|
|
||
|
formatStringTable(tableName) {
|
||
|
if (this.size === -1)
|
||
|
FATAL(`IndexedString.formatStringTable(): Not indexed yet, call index()`);
|
||
|
|
||
|
let s = "";
|
||
|
for (let i = 0; i < this.stringTable.length; i += 80) {
|
||
|
if (s)
|
||
|
s += "\n"
|
||
|
s += '"' + this.stringTable.substring(i, i + 80) + '"';
|
||
|
}
|
||
|
s += ";\n";
|
||
|
|
||
|
return `const char ${tableName}[] =\n${StringUtils.indent(s, " ")}\n`;
|
||
|
}
|
||
|
|
||
|
getSize() {
|
||
|
if (this.size === -1)
|
||
|
FATAL(`IndexedString.getSize(): Not indexed yet, call index()`);
|
||
|
|
||
|
return this.primaryTable.length * 4 + this.stringTable.length;
|
||
|
}
|
||
|
|
||
|
getIndex(k) {
|
||
|
if (this.size === -1)
|
||
|
FATAL(`IndexedString.getIndex(): Not indexed yet, call index()`);
|
||
|
|
||
|
if (!hasOwn.call(this.map, k))
|
||
|
FATAL(`IndexedString.getIndex(): Key '${k}' not found.`);
|
||
|
|
||
|
return this.map[k];
|
||
|
}
|
||
|
}
|
||
|
exports.InstructionNameData = InstructionNameData;
|
||
|
|
||
|
// ============================================================================
|
||
|
// [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() {
|
||
|
FATAL("Task.run(): Must be reimplemented");
|
||
|
}
|
||
|
}
|
||
|
exports.Task = Task;
|
||
|
|
||
|
// ============================================================================
|
||
|
// [TableGen]
|
||
|
// ============================================================================
|
||
|
|
||
|
class Injector {
|
||
|
constructor() {
|
||
|
this.files = Object.create(null);
|
||
|
this.tableSizes = Object.create(null);
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
FATAL(`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)
|
||
|
FATAL(`TableGen.inject(): Cannot find '${key}'`);
|
||
|
|
||
|
if (size)
|
||
|
this.tableSizes[key] = size;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
exports.Injector = Injector;
|
||
|
|
||
|
// 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 extends Injector{
|
||
|
constructor(arch) {
|
||
|
super();
|
||
|
|
||
|
this.arch = arch;
|
||
|
|
||
|
this.tasks = [];
|
||
|
this.taskMap = Object.create(null);
|
||
|
|
||
|
this.insts = [];
|
||
|
this.instMap = Object.create(null);
|
||
|
|
||
|
this.aliases = [];
|
||
|
this.aliasMem = Object.create(null);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// [Task Management]
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
addTask(task) {
|
||
|
if (!task.name)
|
||
|
FATAL(`TableGen.addModule(): Module must have a name`);
|
||
|
|
||
|
if (this.taskMap[task.name])
|
||
|
FATAL(`TableGen.addModule(): Module '${task.name}' already added`);
|
||
|
|
||
|
task.deps.forEach((dependency) => {
|
||
|
if (!this.taskMap[dependency])
|
||
|
FATAL(`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])
|
||
|
FATAL(`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();
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// [Hooks]
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
onBeforeRun() {}
|
||
|
onAfterRun() {}
|
||
|
}
|
||
|
exports.TableGen = TableGen;
|
||
|
|
||
|
// ============================================================================
|
||
|
// [IdEnum]
|
||
|
// ============================================================================
|
||
|
|
||
|
class IdEnum extends Task {
|
||
|
constructor(name, deps) {
|
||
|
super(name || "IdEnum", deps);
|
||
|
}
|
||
|
|
||
|
comment(name) {
|
||
|
FATAL("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 Output {
|
||
|
constructor() {
|
||
|
this.content = Object.create(null);
|
||
|
this.tableSize = Object.create(null);
|
||
|
}
|
||
|
|
||
|
add(id, content, tableSize) {
|
||
|
this.content[id] = content;
|
||
|
this.tableSize[id] = typeof tableSize === "number" ? tableSize : 0;
|
||
|
}
|
||
|
};
|
||
|
exports.Output = Output;
|
||
|
|
||
|
function generateNameData(out, instructions) {
|
||
|
const none = "Inst::kIdNone";
|
||
|
|
||
|
const instFirst = new Array(26);
|
||
|
const instLast = new Array(26);
|
||
|
const instNameData = new InstructionNameData();
|
||
|
|
||
|
for (let i = 0; i < instructions.length; i++)
|
||
|
instNameData.add(instructions[i].displayName);
|
||
|
instNameData.index();
|
||
|
|
||
|
for (let i = 0; i < instructions.length; i++) {
|
||
|
const inst = instructions[i];
|
||
|
const displayName = inst.displayName;
|
||
|
const alphaIndex = displayName.charCodeAt(0) - 'a'.charCodeAt(0);
|
||
|
|
||
|
if (alphaIndex < 0 || alphaIndex >= 26)
|
||
|
FATAL(`generateNameData(): Invalid lookup character '${displayName[0]}' of '${displayName}'`);
|
||
|
|
||
|
if (instFirst[alphaIndex] === undefined)
|
||
|
instFirst[alphaIndex] = `Inst::kId${inst.enum}`;
|
||
|
instLast[alphaIndex] = `Inst::kId${inst.enum}`;
|
||
|
}
|
||
|
|
||
|
var s = "";
|
||
|
s += `const InstNameIndex InstDB::instNameIndex = {{\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 += `}, uint16_t(${instNameData.maxNameLength})};\n`;
|
||
|
s += `\n`;
|
||
|
s += instNameData.formatStringTable("InstDB::_instNameStringTable");
|
||
|
s += `\n`;
|
||
|
s += instNameData.formatIndexTable("InstDB::_instNameIndexTable");
|
||
|
|
||
|
const dataSize = instNameData.getSize() + 26 * 4;
|
||
|
out.add("NameData", StringUtils.disclaimer(s), dataSize);
|
||
|
return out;
|
||
|
}
|
||
|
exports.generateNameData = generateNameData;
|
||
|
|
||
|
class NameTable extends Task {
|
||
|
constructor(name, deps) {
|
||
|
super(name || "NameTable", deps);
|
||
|
}
|
||
|
|
||
|
run() {
|
||
|
const output = new Output();
|
||
|
generateNameData(output, this.ctx.insts);
|
||
|
this.ctx.inject("NameData", output.content["NameData"], output.tableSize["NameData"]);
|
||
|
}
|
||
|
}
|
||
|
exports.NameTable = NameTable;
|