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;