diff options
Diffstat (limited to '3rdparty/asmjit/tools/tablegen.js')
-rw-r--r-- | 3rdparty/asmjit/tools/tablegen.js | 568 |
1 files changed, 568 insertions, 0 deletions
diff --git a/3rdparty/asmjit/tools/tablegen.js b/3rdparty/asmjit/tools/tablegen.js new file mode 100644 index 00000000000..a151805675b --- /dev/null +++ b/3rdparty/asmjit/tools/tablegen.js @@ -0,0 +1,568 @@ +// 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; |