generator-commons.js (13615B)
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 function nop(x) { return x; } 7 8 // Generator - Constants 9 // --------------------- 10 11 const kIndent = " "; 12 exports.kIndent = kIndent; 13 14 const kLineWidth = 120; 15 16 // Generator - Logging 17 // ------------------- 18 19 let VERBOSE = false; 20 21 function setDebugVerbosity(value) { 22 VERBOSE = value; 23 } 24 exports.setDebugVerbosity = setDebugVerbosity; 25 26 function DEBUG(msg) { 27 if (VERBOSE) 28 console.log(msg); 29 } 30 exports.DEBUG = DEBUG; 31 32 function WARN(msg) { 33 console.log(msg); 34 } 35 exports.WARN = WARN; 36 37 function FATAL(msg) { 38 console.log(`FATAL: ${msg}`); 39 throw new Error(msg); 40 } 41 exports.FATAL = FATAL; 42 43 // Generator - Object Utilities 44 // ---------------------------- 45 46 class ObjectUtils { 47 static clone(map) { 48 return Object.assign(Object.create(null), map); 49 } 50 51 static merge(a, b) { 52 if (a === b) 53 return a; 54 55 for (let k in b) { 56 let av = a[k]; 57 let bv = b[k]; 58 59 if (typeof av === "object" && typeof bv === "object") 60 ObjectUtils.merge(av, bv); 61 else 62 a[k] = bv; 63 } 64 65 return a; 66 } 67 68 static equals(a, b) { 69 if (a === b) 70 return true; 71 72 if (typeof a !== typeof b) 73 return false; 74 75 if (typeof a !== "object") 76 return a === b; 77 78 if (Array.isArray(a) || Array.isArray(b)) { 79 if (Array.isArray(a) !== Array.isArray(b)) 80 return false; 81 82 const len = a.length; 83 if (b.length !== len) 84 return false; 85 86 for (let i = 0; i < len; i++) 87 if (!ObjectUtils.equals(a[i], b[i])) 88 return false; 89 } 90 else { 91 if (a === null || b === null) 92 return a === b; 93 94 for (let k in a) 95 if (!Object.hasOwn(b, k) || !ObjectUtils.equals(a[k], b[k])) 96 return false; 97 98 for (let k in b) 99 if (!Object.hasOwn(a, k)) 100 return false; 101 } 102 103 return true; 104 } 105 106 static equalsExcept(a, b, except) { 107 if (a === b) 108 return true; 109 110 if (typeof a !== "object" || typeof b !== "object" || Array.isArray(a) || Array.isArray(b)) 111 return ObjectUtils.equals(a, b); 112 113 for (let k in a) 114 if (!Object.hasOwn(except, k) && (!Object.hasOwn(b, k) || !ObjectUtils.equals(a[k], b[k]))) 115 return false; 116 117 for (let k in b) 118 if (!Object.hasOwn(except, k) && !Object.hasOwn(a, k)) 119 return false; 120 121 return true; 122 } 123 124 static findKey(map, keys) { 125 for (let key in keys) 126 if (Object.hasOwn(map, key)) 127 return key; 128 return undefined; 129 } 130 131 static hasAny(map, keys) { 132 for (let key in keys) 133 if (Object.hasOwn(map, key)) 134 return true; 135 return false; 136 } 137 138 static and(a, b) { 139 const out = Object.create(null); 140 for (let k in a) 141 if (Object.hasOwn(b, k)) 142 out[k] = true; 143 return out; 144 } 145 146 static xor(a, b) { 147 const out = Object.create(null); 148 for (let k in a) if (!Object.hasOwn(b, k)) out[k] = true; 149 for (let k in b) if (!Object.hasOwn(a, k)) out[k] = true; 150 return out; 151 } 152 } 153 exports.ObjectUtils = ObjectUtils; 154 155 // Generator - Array Utilities 156 // --------------------------- 157 158 class ArrayUtils { 159 static min(arr, fn) { 160 if (!arr.length) 161 return null; 162 163 if (!fn) 164 fn = nop; 165 166 let v = fn(arr[0]); 167 for (let i = 1; i < arr.length; i++) 168 v = Math.min(v, fn(arr[i])); 169 return v; 170 } 171 172 static max(arr, fn) { 173 if (!arr.length) 174 return null; 175 176 if (!fn) 177 fn = nop; 178 179 let v = fn(arr[0]); 180 for (let i = 1; i < arr.length; i++) 181 v = Math.max(v, fn(arr[i])); 182 return v; 183 } 184 185 static sorted(obj, cmp) { 186 const out = Array.isArray(obj) ? obj.slice() : Object.getOwnPropertyNames(obj); 187 out.sort(cmp); 188 return out; 189 } 190 191 static deepIndexOf(arr, what) { 192 for (let i = 0; i < arr.length; i++) 193 if (ObjectUtils.equals(arr[i], what)) 194 return i; 195 return -1; 196 } 197 198 static toDict(arr, value) { 199 if (value === undefined) 200 value = true; 201 202 const out = Object.create(null); 203 for (let i = 0; i < arr.length; i++) 204 out[arr[i]] = value; 205 return out; 206 } 207 } 208 exports.ArrayUtils = ArrayUtils; 209 210 211 // Generator - String Utilities 212 // ---------------------------- 213 214 class StringUtils { 215 static asString(x) { return String(x); } 216 217 static makeEnumName(name) { 218 return name ? name.charAt(0).toUpperCase() + name.substring(1) : ""; 219 } 220 static countOf(s, pattern) { 221 if (!pattern) 222 FATAL(`Pattern cannot be empty`); 223 224 let n = 0; 225 let pos = 0; 226 227 while ((pos = s.indexOf(pattern, pos)) >= 0) { 228 n++; 229 pos += pattern.length; 230 } 231 232 return n; 233 } 234 235 static trimLeft(s) { return s.replace(/^\s+/, ""); } 236 static trimRight(s) { return s.replace(/\s+$/, ""); } 237 238 static upFirst(s) { 239 if (!s) return ""; 240 return s[0].toUpperCase() + s.substr(1); 241 } 242 243 static decToHex(n, nPad) { 244 let hex = Number(n < 0 ? 0x100000000 + n : n).toString(16); 245 while (nPad > hex.length) 246 hex = "0" + hex; 247 return "0x" + hex.toUpperCase(); 248 } 249 250 static format(array, indent, showIndex, mapFn) { 251 if (!mapFn) 252 mapFn = StringUtils.asString; 253 254 let s = ""; 255 let threshold = 80; 256 257 if (showIndex === -1) 258 s += indent; 259 260 for (let i = 0; i < array.length; i++) { 261 const item = array[i]; 262 const last = i === array.length - 1; 263 264 if (showIndex !== -1) 265 s += indent; 266 267 s += mapFn(item); 268 if (showIndex > 0) { 269 s += `${last ? " " : ","} // #${i}`; 270 if (typeof array.refCountOf === "function") 271 s += ` [ref=${array.refCountOf(item)}x]`; 272 } 273 else if (!last) { 274 s += ","; 275 } 276 277 if (showIndex === -1) { 278 if (s.length >= threshold - 1 && !last) { 279 s += "\n" + indent; 280 threshold += 80; 281 } 282 else { 283 if (!last) s += " "; 284 } 285 } 286 else { 287 if (!last) s += "\n"; 288 } 289 } 290 291 return s; 292 } 293 294 static makeCxxArray(array, code, indent) { 295 if (typeof indent !== "string") 296 indent = kIndent; 297 298 return `${code} = {\n${indent}` + array.join(`,\n${indent}`) + `\n};\n`; 299 } 300 301 static makeCxxArrayWithComment(array, code, indent) { 302 if (typeof indent !== "string") 303 indent = kIndent; 304 305 let s = ""; 306 for (let i = 0; i < array.length; i++) { 307 const last = i === array.length - 1; 308 s += indent + array[i].data + 309 (last ? " // " : ", // ") + (array[i].refs ? "#" + String(i) : "").padEnd(5) + array[i].comment + "\n"; 310 } 311 return `${code} = {\n${s}};\n`; 312 } 313 314 static formatCppStruct(...args) { 315 return "{ " + args.join(", ") + " }"; 316 } 317 318 static formatCppFlags(obj, fn, none) { 319 if (none == null) 320 none = "0"; 321 322 if (!fn) 323 fn = nop; 324 325 let out = ""; 326 for (let k in obj) { 327 if (obj[k]) 328 out += (out ? " | " : "") + fn(k); 329 } 330 return out ? out : none; 331 } 332 333 static formatRecords(array, indent, fn) { 334 if (typeof indent !== "string") 335 indent = kIndent; 336 337 if (!fn) 338 fn = nop; 339 340 let s = ""; 341 let line = ""; 342 for (let i = 0; i < array.length; i++) { 343 const item = fn(array[i]); 344 const combined = line ? line + ", " + item : item; 345 346 if (combined.length >= kLineWidth) { 347 s = s ? s + ",\n" + line : line; 348 line = item; 349 } 350 else { 351 line = combined; 352 } 353 } 354 355 if (line) { 356 s = s ? s + ",\n" + line : line; 357 } 358 359 return StringUtils.indent(s, indent); 360 } 361 362 static disclaimer(s) { 363 return "// ------------------- Automatically generated, do not edit -------------------\n" + 364 s + 365 "// ----------------------------------------------------------------------------\n"; 366 } 367 368 static indent(s, indentation) { 369 if (typeof indentation === "number") 370 indentation = " ".repeat(indentation); 371 372 let lines = s.split(/\r?\n/g); 373 if (indentation) { 374 for (let i = 0; i < lines.length; i++) { 375 let line = lines[i]; 376 if (line) 377 lines[i] = indentation + line; 378 } 379 } 380 381 return lines.join("\n"); 382 } 383 384 static extract(s, start, end) { 385 const iStart = s.indexOf(start); 386 const iEnd = s.indexOf(end); 387 388 if (iStart === -1) 389 FATAL(`StringUtils.extract(): Couldn't locate start mark '${start}'`); 390 391 if (iEnd === -1) 392 FATAL(`StringUtils.extract(): Couldn't locate end mark '${end}'`); 393 394 return s.substring(iStart + start.length, iEnd).trim(); 395 } 396 397 static inject(s, start, end, code) { 398 let iStart = s.indexOf(start); 399 let iEnd = s.indexOf(end); 400 401 if (iStart === -1) 402 FATAL(`StringUtils.inject(): Couldn't locate start mark '${start}'`); 403 404 if (iEnd === -1) 405 FATAL(`StringUtils.inject(): Couldn't locate end mark '${end}'`); 406 407 let nIndent = 0; 408 while (iStart > 0 && s[iStart-1] === " ") { 409 iStart--; 410 nIndent++; 411 } 412 413 if (nIndent) { 414 const indentation = " ".repeat(nIndent); 415 code = StringUtils.indent(code, indentation) + indentation; 416 } 417 418 return s.substr(0, iStart + start.length + nIndent) + code + s.substr(iEnd); 419 } 420 421 static makePriorityCompare(priorityArray) { 422 const map = Object.create(null); 423 priorityArray.forEach((str, index) => { map[str] = index; }); 424 425 return function(a, b) { 426 const ax = Object.hasOwn(map, a) ? map[a] : Infinity; 427 const bx = Object.hasOwn(map, b) ? map[b] : Infinity; 428 return ax != bx ? ax - bx : a < b ? -1 : a > b ? 1 : 0; 429 } 430 } 431 } 432 exports.StringUtils = StringUtils; 433 434 // Generator - Indexed Array 435 // ========================= 436 437 // IndexedArray is an Array replacement that allows to index each item inserted to it. Its main purpose 438 // is to avoid data duplication, if an item passed to `addIndexed()` is already within the Array then 439 // it's not inserted and the existing index is returned instead. 440 function IndexedArray_keyOf(item) { 441 return typeof item === "string" ? item : JSON.stringify(item); 442 } 443 444 class IndexedArray extends Array { 445 constructor() { 446 super(); 447 this._index = Object.create(null); 448 } 449 450 refCountOf(item) { 451 const key = IndexedArray_keyOf(item); 452 const idx = this._index[key]; 453 454 return idx !== undefined ? idx.refCount : 0; 455 } 456 457 addIndexed(item) { 458 const key = IndexedArray_keyOf(item); 459 let idx = this._index[key]; 460 461 if (idx !== undefined) { 462 idx.refCount++; 463 return idx.data; 464 } 465 466 idx = this.length; 467 this._index[key] = { 468 data: idx, 469 refCount: 1 470 }; 471 this.push(item); 472 return idx; 473 } 474 } 475 exports.IndexedArray = IndexedArray; 476 477 // Generator - Indexed String 478 // ========================== 479 480 // IndexedString is mostly used to merge all instruction names into a single string with external 481 // index. It's designed mostly for generating C++ tables. Consider the following cases in C++: 482 // 483 // a) static const char* const* instNames = { "add", "mov", "vpunpcklbw" }; 484 // 485 // b) static const char instNames[] = { "add\0" "mov\0" "vpunpcklbw\0" }; 486 // static const uint16_t _inst_name_index[] = { 0, 4, 8 }; 487 // 488 // The latter (b) has an advantage that it doesn't have to be relocated by the linker, which saves 489 // a lot of space in the resulting binary and a lot of CPU cycles (and memory) when the linker loads 490 // it. AsmJit supports thousands of instructions so each optimization like this makes it smaller and 491 // faster to load. 492 class IndexedString { 493 constructor() { 494 this.map = Object.create(null); 495 this.array = []; 496 this.size = -1; 497 } 498 499 add(s) { 500 this.map[s] = -1; 501 } 502 503 index() { 504 const map = this.map; 505 const array = this.array; 506 const partialMap = Object.create(null); 507 508 let k, kp; 509 let i, len; 510 511 // Create a map that will contain all keys and partial keys. 512 for (k in map) { 513 if (!k) { 514 partialMap[k] = k; 515 } 516 else { 517 for (i = 0, len = k.length; i < len; i++) { 518 kp = k.substring(i); 519 if (!Object.hasOwn(partialMap, kp) || partialMap[kp].length < len) 520 partialMap[kp] = k; 521 } 522 } 523 } 524 525 // Create an array that will only contain keys that are needed. 526 for (k in map) 527 if (partialMap[k] === k) 528 array.push(k); 529 array.sort(); 530 531 // Create valid offsets to the `array`. 532 let offMap = Object.create(null); 533 let offset = 0; 534 535 for (i = 0, len = array.length; i < len; i++) { 536 k = array[i]; 537 538 offMap[k] = offset; 539 offset += k.length + 1; 540 } 541 this.size = offset; 542 543 // Assign valid offsets to `map`. 544 for (kp in map) { 545 k = partialMap[kp]; 546 map[kp] = offMap[k] + k.length - kp.length; 547 } 548 } 549 550 format(indent, justify) { 551 if (this.size === -1) 552 FATAL(`IndexedString.format(): not indexed yet, call index()`); 553 554 const array = this.array; 555 if (!justify) justify = 0; 556 557 let i; 558 let s = ""; 559 let line = ""; 560 561 for (i = 0; i < array.length; i++) { 562 const item = "\"" + array[i] + ((i !== array.length - 1) ? "\\0\"" : "\";"); 563 const newl = line + (line ? " " : indent) + item; 564 565 if (newl.length <= justify) { 566 line = newl; 567 continue; 568 } 569 else { 570 s += line + "\n"; 571 line = indent + item; 572 } 573 } 574 575 return s + line; 576 } 577 578 getSize() { 579 if (this.size === -1) 580 FATAL(`IndexedString.getSize(): Not indexed yet, call index()`); 581 return this.size; 582 } 583 584 getIndex(k) { 585 if (this.size === -1) 586 FATAL(`IndexedString.getIndex(): Not indexed yet, call index()`); 587 588 if (!Object.hasOwn(this.map, k)) 589 FATAL(`IndexedString.getIndex(): Key '${k}' not found.`); 590 591 return this.map[k]; 592 } 593 } 594 exports.IndexedString = IndexedString;