Files
asmjit/tools/tablegen.js
kobalicek b25df5554d [ABI] Updated instruction DB, operands, and minor API changes
This changeset contains an updated instruction database that brings
ARM32 instructions for the first time. It also updates instruction
database tooling especially for ARM64, which will also be used by
ARM32 generator.

Additionally, new operan has been added, which represents a register
list as used by ARM32 instruction set.

Other minor changes are related to ARM - some stuff had to be moved
to a64 namespace from arm namespace as it's incompatible between
32-bit and 64-bit ISA.
2023-12-26 23:28:40 +01:00

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;