odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

tablegen.js (17882B)


      1 // This file is part of AsmJit project <https://asmjit.com>
      2 //
      3 // See <asmjit/core.h> or LICENSE.md for license and copyright information
      4 // SPDX-License-Identifier: Zlib
      5 
      6 // ============================================================================
      7 // tablegen.js
      8 //
      9 // Provides core foundation for generating tables that AsmJit requires. This
     10 // file should provide everything table generators need in general.
     11 // ============================================================================
     12 
     13 "use strict";
     14 
     15 // ============================================================================
     16 // [Imports]
     17 // ============================================================================
     18 
     19 const fs = require("fs");
     20 
     21 const commons = require("./generator-commons.js");
     22 const cxx = require("./generator-cxx.js");
     23 const asmdb = require("../db");
     24 
     25 exports.asmdb = asmdb;
     26 exports.exp = asmdb.base.exp;
     27 
     28 const FATAL = commons.FATAL;
     29 const StringUtils = commons.StringUtils;
     30 
     31 const kAsmJitRoot = "..";
     32 exports.kAsmJitRoot = kAsmJitRoot;
     33 
     34 // ============================================================================
     35 // [InstructionNameData]
     36 // ============================================================================
     37 
     38 function charTo5Bit(c) {
     39   if (c >= 'a' && c <= 'z')
     40     return 1 + (c.charCodeAt(0) - 'a'.charCodeAt(0));
     41   else if (c >= '0' && c <= '4')
     42     return 1 + 26 + (c.charCodeAt(0) - '0'.charCodeAt(0));
     43   else
     44     FATAL(`Character '${c}' cannot be encoded into a 5-bit string`);
     45 }
     46 
     47 class InstructionNameData {
     48   constructor() {
     49     this.names = [];
     50     this.primaryTable = [];
     51     this.stringTable = "";
     52     this.size = 0;
     53     this.indexComment = [];
     54     this.maxNameLength = 0;
     55   }
     56 
     57   add(name, alt) {
     58     if (name === alt) {
     59       alt = "";
     60     }
     61 
     62     if (this.maxNameLength < name.length) {
     63       this.maxNameLength = name.length;
     64     }
     65 
     66     this.names.push(name);
     67 
     68     // First try to encode the string with 5-bit characters that fit into a 32-bit int.
     69     if (/^[a-z0-4]{0,6}$/.test(name) && !alt) {
     70       let index = 0;
     71       for (let i = 0; i < name.length; i++) {
     72         index |= charTo5Bit(name[i]) << (i * 5);
     73       }
     74 
     75       this.indexComment.push(`Small '${name}'.`);
     76       this.primaryTable.push(index | (1 << 31));
     77     }
     78     else if (alt) {
     79       const prefixIndex = this.addOrReferenceString(name + String.fromCharCode(alt.length) + alt);
     80 
     81       if (name === "jz") {
     82         console.log(`jz prefix: ${prefixIndex}`);
     83       }
     84 
     85       this.indexComment.push(`Large '${name}' + '${alt}'`);
     86       this.primaryTable.push(prefixIndex | (name.length << 12) | (0xFFF << 16) | 0);
     87     }
     88     else {
     89       this.indexComment.push(``);
     90       this.primaryTable.push(0);
     91     }
     92   }
     93 
     94   index() {
     95     const kMaxPrefixSize = 15;
     96     const kMaxSuffixSize = 6;
     97     const names = [];
     98 
     99     for (let idx = 0; idx < this.primaryTable.length; idx++) {
    100       if (this.primaryTable[idx] === 0) {
    101         names.push({ name: this.names[idx], index: idx });
    102       }
    103     }
    104 
    105     names.sort(function(a, b) {
    106       if (a.name.length > b.name.length)
    107         return -1;
    108       if (a.name.length < b.name.length)
    109         return 1;
    110       return (a > b) ? 1 : (a < b) ? -1 : 0;
    111     });
    112 
    113     for (let z = 0; z < names.length; z++) {
    114       const idx = names[z].index;
    115       const name = names[z].name;
    116 
    117       let done = false;
    118       let longestPrefix = 0;
    119       let longestSuffix = 0;
    120 
    121       let prefix = "";
    122       let suffix = "";
    123 
    124       for (let i = Math.min(name.length, kMaxPrefixSize); i > 0; i--) {
    125         prefix = name.substring(0, i);
    126         suffix = name.substring(i);
    127 
    128         const prefixIndex = this.stringTable.indexOf(prefix);
    129         const suffixIndex = this.stringTable.indexOf(suffix);
    130 
    131         // Matched both parts?
    132         if (prefixIndex !== -1 && suffix === "") {
    133           done = true;
    134           break;
    135         }
    136 
    137         if (prefixIndex !== -1 && suffixIndex !== -1) {
    138           done = true;
    139           break;
    140         }
    141 
    142         if (prefixIndex !== -1 && longestPrefix === 0)
    143           longestPrefix = prefix.length;
    144 
    145         if (suffixIndex !== -1 && suffix.length > longestSuffix)
    146           longestSuffix = suffix.length;
    147 
    148         if (suffix.length === kMaxSuffixSize)
    149           break;
    150       }
    151 
    152       if (!done) {
    153         let minPrefixSize = name.length >= 8 ? name.length / 2 + 1 : name.length - 2;
    154 
    155         prefix = "";
    156         suffix = "";
    157 
    158         if (longestPrefix >= minPrefixSize) {
    159           prefix = name.substring(0, longestPrefix);
    160           suffix = name.substring(longestPrefix);
    161         }
    162         else if (longestSuffix) {
    163           const splitAt = Math.min(name.length - longestSuffix, kMaxPrefixSize);
    164           prefix = name.substring(0, splitAt);
    165           suffix = name.substring(splitAt);
    166         }
    167         else if (name.length > kMaxPrefixSize) {
    168           prefix = name.substring(0, kMaxPrefixSize);
    169           suffix = name.substring(kMaxPrefixSize);
    170         }
    171         else {
    172           prefix = name;
    173           suffix = "";
    174         }
    175       }
    176 
    177       if (suffix) {
    178         const prefixIndex = this.addOrReferenceString(prefix);
    179         const suffixIndex = this.addOrReferenceString(suffix);
    180 
    181         this.primaryTable[idx] = prefixIndex | (prefix.length << 12) | (suffixIndex << 16) | (suffix.length << 28);
    182         this.indexComment[idx] = `Large '${prefix}|${suffix}'.`;
    183       }
    184       else {
    185         const prefixIndex = this.addOrReferenceString(prefix);
    186 
    187         this.primaryTable[idx] = prefixIndex | (prefix.length << 12);
    188         this.indexComment[idx] = `Large '${prefix}'.`;
    189       }
    190     }
    191   }
    192 
    193   addOrReferenceString(s) {
    194     let index = this.stringTable.indexOf(s);
    195     if (index === -1) {
    196       index = this.stringTable.length;
    197       this.stringTable += s;
    198     }
    199     return index;
    200   }
    201 
    202   formatIndexTable(tableName) {
    203     if (this.size === -1)
    204       FATAL(`IndexedString.formatIndexTable(): Not indexed yet, call index()`);
    205 
    206     let s = "";
    207     for (let i = 0; i < this.primaryTable.length; i++) {
    208       s += cxx.Utils.toHex(this.primaryTable[i], 8);
    209       s += i !== this.primaryTable.length - 1 ? "," : " ";
    210       s += " // " + this.indexComment[i] + "\n";
    211     }
    212 
    213     return `const uint32_t ${tableName}[] = {\n${StringUtils.indent(s, "  ")}};\n`;
    214   }
    215 
    216   formatStringTable(tableName) {
    217     if (this.size === -1)
    218       FATAL(`IndexedString.formatStringTable(): Not indexed yet, call index()`);
    219 
    220     let s = "";
    221     let line = "";
    222 
    223     for (let i = 0; i < this.stringTable.length; i++) {
    224       const c = this.stringTable.charCodeAt(i);
    225       line += "\\x" + cxx.Utils.toHexRaw(c, 2);
    226 
    227       if (line.length >= 115 || i === this.stringTable.length - 1) {
    228         if (s)
    229           s += "\n"
    230         s += `"${line}"`;
    231         line = "";
    232       }
    233     }
    234 
    235     s += ";\n";
    236 
    237     return `const char ${tableName}[] =\n${StringUtils.indent(s, "  ")}\n`;
    238   }
    239 
    240   getSize() {
    241     if (this.size === -1)
    242       FATAL(`IndexedString.getSize(): Not indexed yet, call index()`);
    243 
    244     return this.primaryTable.length * 4 + this.stringTable.length;
    245   }
    246 
    247   getIndex(k) {
    248     if (this.size === -1)
    249       FATAL(`IndexedString.getIndex(): Not indexed yet, call index()`);
    250 
    251     if (!Object.hasOwn(this.map, k))
    252       FATAL(`IndexedString.getIndex(): Key '${k}' not found.`);
    253 
    254     return this.map[k];
    255   }
    256 }
    257 exports.InstructionNameData = InstructionNameData;
    258 
    259 // ============================================================================
    260 // [Task]
    261 // ============================================================================
    262 
    263 // A base runnable task that can access the TableGen through `this.ctx`.
    264 class Task {
    265   constructor(name, deps) {
    266     this.ctx = null;
    267     this.name = name || "";
    268     this.deps = deps || [];
    269   }
    270 
    271   inject(key, str, size) {
    272     this.ctx.inject(key, str, size);
    273     return this;
    274   }
    275 
    276   run() {
    277     FATAL("Task.run(): Must be reimplemented");
    278   }
    279 }
    280 exports.Task = Task;
    281 
    282 // ============================================================================
    283 // [TableGen]
    284 // ============================================================================
    285 
    286 class Injector {
    287   constructor() {
    288     this.files = Object.create(null);
    289     this.tableSizes = Object.create(null);
    290   }
    291 
    292   load(fileList) {
    293     for (var i = 0; i < fileList.length; i++) {
    294       const file = fileList[i];
    295       const path = kAsmJitRoot + "/" + file;
    296       const data = fs.readFileSync(path, "utf8").replace(/\r\n/g, "\n");
    297 
    298       this.files[file] = {
    299         prev: data,
    300         data: data
    301       };
    302     }
    303     return this;
    304   }
    305 
    306   save() {
    307     for (var file in this.files) {
    308       const obj = this.files[file];
    309       if (obj.data !== obj.prev) {
    310         const path = kAsmJitRoot + "/" + file;
    311         console.log(`MODIFIED '${file}'`);
    312 
    313         if (!fs.existsSync(path + ".backup")) {
    314           fs.writeFileSync(path + ".backup", obj.prev, "utf8");
    315         }
    316         fs.writeFileSync(path, obj.data, "utf8");
    317       }
    318     }
    319   }
    320 
    321   dataOfFile(file) {
    322     const obj = this.files[file];
    323     if (!obj)
    324       FATAL(`TableGen.dataOfFile(): File '${file}' not loaded`);
    325     return obj.data;
    326   }
    327 
    328   inject(key, str, size) {
    329     const begin = "// ${" + key + ":Begin}\n";
    330     const end   = "// ${" + key + ":End}\n";
    331 
    332     var done = false;
    333     for (var file in this.files) {
    334       const obj = this.files[file];
    335       const data = obj.data;
    336 
    337       if (data.indexOf(begin) !== -1) {
    338         obj.data = StringUtils.inject(data, begin, end, str);
    339         done = true;
    340         break;
    341       }
    342     }
    343 
    344     if (!done)
    345       FATAL(`TableGen.inject(): Cannot find '${key}'`);
    346 
    347     if (size)
    348       this.tableSizes[key] = size;
    349 
    350     return this;
    351   }
    352 
    353   dumpTableSizes() {
    354     const sizes = this.tableSizes;
    355 
    356     var pad = 26;
    357     var total = 0;
    358 
    359     for (var name in sizes) {
    360       const size = sizes[name];
    361       total += size;
    362       console.log(("Size of " + name).padEnd(pad) + ": " + size);
    363     }
    364 
    365     console.log("Size of all tables".padEnd(pad) + ": " + total);
    366   }
    367 }
    368 exports.Injector = Injector;
    369 
    370 // Main context used to load, generate, and store instruction tables. The idea
    371 // is to be extensible, so it stores 'Task's to be executed with minimal deps
    372 // management.
    373 class TableGen extends Injector {
    374   constructor(arch) {
    375     super();
    376 
    377     this.arch = arch;
    378 
    379     this.tasks = [];
    380     this.taskMap = Object.create(null);
    381 
    382     this.insts = [];
    383     this.instMap = Object.create(null);
    384 
    385     this.aliases = [];
    386     this.aliasMem = Object.create(null);
    387   }
    388 
    389   // --------------------------------------------------------------------------
    390   // [Task Management]
    391   // --------------------------------------------------------------------------
    392 
    393   addTask(task) {
    394     if (!task.name)
    395       FATAL(`TableGen.addModule(): Module must have a name`);
    396 
    397     if (this.taskMap[task.name])
    398       FATAL(`TableGen.addModule(): Module '${task.name}' already added`);
    399 
    400     task.deps.forEach((dependency) => {
    401       if (!this.taskMap[dependency])
    402         FATAL(`TableGen.addModule(): Dependency '${dependency}' of module '${task.name}' doesn't exist`);
    403     });
    404 
    405     this.tasks.push(task);
    406     this.taskMap[task.name] = task;
    407 
    408     task.ctx = this;
    409     return this;
    410   }
    411 
    412   runTasks() {
    413     const tasks = this.tasks;
    414     const tasksDone = Object.create(null);
    415 
    416     var pending = tasks.length;
    417     while (pending) {
    418       const oldPending = pending;
    419       const arrPending = [];
    420 
    421       for (var i = 0; i < tasks.length; i++) {
    422         const task = tasks[i];
    423         if (tasksDone[task.name])
    424           continue;
    425 
    426         if (task.deps.every((dependency) => { return tasksDone[dependency] === true; })) {
    427           task.run();
    428           tasksDone[task.name] = true;
    429           pending--;
    430         }
    431         else {
    432           arrPending.push(task.name);
    433         }
    434       }
    435 
    436       if (oldPending === pending)
    437         throw Error(`TableGen.runModules(): Modules '${arrPending.join("|")}' stuck (cyclic dependency?)`);
    438     }
    439   }
    440 
    441   // --------------------------------------------------------------------------
    442   // [Instruction Management]
    443   // --------------------------------------------------------------------------
    444 
    445   addInstruction(inst) {
    446     if (this.instMap[inst.name])
    447       FATAL(`TableGen.addInst(): Instruction '${inst.name}' already added`);
    448 
    449     inst.id = this.insts.length;
    450     this.insts.push(inst);
    451     this.instMap[inst.name] = inst;
    452 
    453     return this;
    454   }
    455 
    456   addAlias(alias, name) {
    457     this.aliases.push(alias);
    458     this.aliasMap[alias] = name;
    459 
    460     return this;
    461   }
    462 
    463   // --------------------------------------------------------------------------
    464   // [Run]
    465   // --------------------------------------------------------------------------
    466 
    467   run() {
    468     this.onBeforeRun();
    469     this.runTasks();
    470     this.onAfterRun();
    471   }
    472 
    473   // --------------------------------------------------------------------------
    474   // [Hooks]
    475   // --------------------------------------------------------------------------
    476 
    477   onBeforeRun() {}
    478   onAfterRun() {}
    479 }
    480 exports.TableGen = TableGen;
    481 
    482 // ============================================================================
    483 // [IdEnum]
    484 // ============================================================================
    485 
    486 class IdEnum extends Task {
    487   constructor(name, deps) {
    488     super(name || "IdEnum", deps);
    489   }
    490 
    491   comment(name) {
    492     FATAL("IdEnum.comment(): Must be reimplemented");
    493   }
    494 
    495   run() {
    496     const insts = this.ctx.insts;
    497 
    498     let s = "";
    499     let aliases = "";
    500 
    501     for (let i = 0; i < insts.length; i++) {
    502       const inst = insts[i];
    503 
    504       let line = "kId" + inst.enum + (i ? "" : " = 0") + ",";
    505       let text = this.comment(inst);
    506 
    507       if (text)
    508         line = line.padEnd(37) + "//!< " + text;
    509 
    510       s += line + "\n";
    511 
    512       if (inst.aliases) {
    513         for (let aliasName of inst.aliases.aliasNames) {
    514           if (aliases) aliases += ",\n";
    515           aliases += `kId${StringUtils.makeEnumName(aliasName)} = kId${inst.enum}`;
    516         }
    517       }
    518     }
    519     s += "_kIdCount";
    520 
    521     if (aliases) {
    522       s += ",\n\n" + "// Aliases.\n" + aliases + "\n";
    523     }
    524     else {
    525       s += "\n";
    526     }
    527 
    528     return this.ctx.inject("InstId", s);
    529   }
    530 }
    531 exports.IdEnum = IdEnum;
    532 
    533 // ============================================================================
    534 // [NameTable]
    535 // ============================================================================
    536 
    537 class Output {
    538   constructor() {
    539     this.content = Object.create(null);
    540     this.tableSize = Object.create(null);
    541   }
    542 
    543   add(id, content, tableSize) {
    544     this.content[id] = content;
    545     this.tableSize[id] = typeof tableSize === "number" ? tableSize : 0;
    546   }
    547 };
    548 exports.Output = Output;
    549 
    550 function cmp(a, b) { return (a < b) ? -1 : a > b ? 1 : 0; }
    551 
    552 function generateNameData(out, instructions, generateAliases) {
    553   const none = "Inst::kIdNone";
    554 
    555   const aliases = [];
    556   const aliasNameData = new InstructionNameData();
    557   const aliasLinkData = [];
    558 
    559   const instFirst = new Array(26);
    560   const instLast  = new Array(26);
    561   const instNameData = new InstructionNameData();
    562 
    563   for (let i = 0; i < instructions.length; i++) {
    564     const instruction = instructions[i];
    565 
    566     if (instruction.aliases) {
    567       instNameData.add(instruction.displayName, instruction.aliases.format);
    568       for (let aliasName of instruction.aliases.aliasNames) {
    569         aliases.push({ name: instruction.name, alt: aliasName });
    570       }
    571     }
    572     else {
    573       instNameData.add(instruction.displayName);
    574     }
    575   }
    576 
    577   aliases.sort(function(a, b) { return cmp(a.alt, b.alt); });
    578 
    579   for (let i = 0; i < aliases.length; i++) {
    580     const alias = aliases[i];
    581     aliasNameData.add(alias.alt);
    582     aliasLinkData.push(`Inst::kId${StringUtils.makeEnumName(alias.name)}`);
    583   }
    584 
    585   instNameData.index();
    586   aliasNameData.index();
    587 
    588   for (let i = 0; i < instructions.length; i++) {
    589     const inst = instructions[i];
    590     const displayName = inst.displayName;
    591     const alphaIndex = displayName.charCodeAt(0) - 'a'.charCodeAt(0);
    592 
    593     if (alphaIndex < 0 || alphaIndex >= 26)
    594       FATAL(`generateNameData(): Invalid lookup character '${displayName[0]}' of '${displayName}'`);
    595 
    596     if (instFirst[alphaIndex] === undefined)
    597       instFirst[alphaIndex] = `Inst::kId${inst.enum}`;
    598     instLast[alphaIndex] = `Inst::kId${inst.enum}`;
    599   }
    600 
    601   var s = "";
    602   s += `const InstNameIndex InstDB::_inst_name_index = {{\n`;
    603   for (var i = 0; i < instFirst.length; i++) {
    604     const firstId = instFirst[i] || none;
    605     const lastId = instLast[i] || none;
    606 
    607     s += `  { ${String(firstId).padEnd(22)}, ${String(lastId).padEnd(22)} + 1 }`;
    608     if (i !== 26 - 1)
    609       s += `,`;
    610     s += `\n`;
    611   }
    612   s += `}, uint16_t(${instNameData.maxNameLength})};\n`;
    613   s += `\n`;
    614   s += instNameData.formatStringTable("InstDB::_inst_name_string_table");
    615   s += `\n`;
    616   s += instNameData.formatIndexTable("InstDB::_inst_name_index_table");
    617 
    618   let dataSize = instNameData.getSize() + 26 * 4;
    619 
    620   if (generateAliases) {
    621     s += `\n`;
    622     s += aliasNameData.formatStringTable("InstDB::alias_name_string_table");
    623     s += `\n`;
    624     s += aliasNameData.formatIndexTable("InstDB::alias_name_index_table");
    625     s += "\n";
    626     s += "const uint32_t InstDB::alias_index_to_inst_id_table[] = {\n" + StringUtils.format(aliasLinkData, "  ", true, null) + "\n};\n";
    627 
    628     dataSize += aliasNameData.getSize();
    629     let info = `static constexpr uint32_t kAliasTableSize = ${aliasLinkData.length};\n`;
    630     out.add("NameDataInfo", StringUtils.disclaimer(info), 0);
    631   }
    632 
    633   out.add("NameData", StringUtils.disclaimer(s), dataSize);
    634   return out;
    635 }
    636 exports.generateNameData = generateNameData;
    637 
    638 class NameTable extends Task {
    639   constructor(name, deps, generateAliases) {
    640     super(name || "NameTable", deps);
    641     this.generateAliases = generateAliases;
    642   }
    643 
    644   run() {
    645     const output = new Output();
    646     generateNameData(output, this.ctx.insts, this.generateAliases);
    647 
    648     this.ctx.inject("NameData", output.content["NameData"], output.tableSize["NameData"]);
    649 
    650     if (this.generateAliases) {
    651       this.ctx.inject("NameDataInfo", output.content["NameDataInfo"], output.tableSize["NameDataInfo"]);
    652     }
    653   }
    654 }
    655 exports.NameTable = NameTable;