base.js (19852B)
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($scope, $as) { 7 "use strict"; 8 9 function FAIL(msg) { throw new Error("[BASE] " + msg); } 10 11 const exp = $scope.exp ? $scope.exp : require("./exp.js"); 12 13 // Export. 14 const base = $scope[$as] = Object.create(null); 15 16 base.exp = exp; 17 18 function dict(src) { 19 const dst = Object.create(null); 20 if (src) 21 Object.assign(dst, src); 22 return dst; 23 } 24 base.dict = dict; 25 const NONE = base.NONE = Object.freeze(dict()); 26 27 // asmdb.base.Symbols 28 // ================== 29 30 const Symbols = Object.freeze({ 31 Commutative: '~' 32 }); 33 base.Symbols = Symbols; 34 35 // asmdb.base.Parsing 36 // ================== 37 38 // Namespace that provides functions related to text parsing. 39 const Parsing = { 40 // Get whether the string `s` representing an operand is <implicit>. 41 isImplicit: function(s) { return s.startsWith("<") && s.endsWith(">"); }, 42 43 // Clear <implicit> attribute from the given operand string `s`. 44 clearImplicit: function(s) { return s.substring(1, s.length - 1); }, 45 46 // Get whether the string `s` representing an operand is {optional}. 47 isOptional: function(s) { return s.startsWith("{") && s.endsWith("}"); }, 48 49 // Clear {optional} attribute from the given operand string `s`. 50 clearOptional: function(s) { return s.substring(1, s.length - 1); }, 51 52 // Get whether the string `s` representing an operand specifies commutativity. 53 isCommutative: function(s) { return s.length > 0 && s.charAt(0) === Symbols.Commutative; }, 54 55 // Clear commutative attribute from the given operand string `s`. 56 clearCommutative: function(s) { return s.substring(1); }, 57 58 // Matches a closing bracket in string `s` starting `from` the given index. 59 // It behaves like `s.indexOf()`, but uses a counter and skips all nested 60 // matches. 61 matchClosingChar: function(s, from) { 62 const len = s.length; 63 const opening = s.charCodeAt(from); 64 const closing = opening === 40 ? 31 : // (). 65 opening === 60 ? 62 : // <>. 66 opening === 91 ? 93 : // []. 67 opening === 123 ? 125 : 0; // {}. 68 69 let i = from; 70 let pending = 1; 71 do { 72 if (++i >= len) 73 break; 74 75 const c = s.charCodeAt(i); 76 pending += Number(c === opening); 77 pending -= Number(c === closing); 78 } while (pending); 79 80 return i; 81 }, 82 83 // Split instruction operands into an array containing each operand as a 84 // trimmed string. This function is similar to `s.split(",")`, however, 85 // it matches brackets inside the operands and won't just blindly split 86 // the string based on "," token. If operand contains metadata or it's 87 // an address it would still be split correctly. 88 splitOperands: function(s) { 89 const result = []; 90 91 s = s.trim(); 92 if (!s) 93 return result; 94 95 let start = 0; 96 let i = 0; 97 let c = ""; 98 99 for (;;) { 100 if (i === s.length || (c = s[i]) === ",") { 101 const op = s.substring(start, i).trim(); 102 if (!op) 103 FAIL(`Found empty operand in '${s}'`); 104 105 result.push(op); 106 if (i === s.length) 107 return result; 108 109 start = ++i; 110 continue; 111 } 112 113 if ((c === "<" || c === ">") && i != start) { 114 i++; 115 continue; 116 } 117 118 if (c === "[" || c === "{" || c === "(" || c === "<") 119 i = base.Parsing.matchClosingChar(s, i); 120 else 121 i++; 122 } 123 } 124 } 125 base.Parsing = Parsing; 126 127 // asmdb.base.MapUtils 128 // =================== 129 130 class MapUtils { 131 static cloneExcept(map, except) { 132 if (typeof except === "string") { 133 const key = except; 134 except = Object.create(null); 135 except[key] = true; 136 } 137 138 const out = Object.create(null); 139 for (let k in map) { 140 if (k in except) 141 continue 142 out[k] = map[k]; 143 } 144 return out; 145 } 146 147 static mapFromArray(array) { 148 const out = Object.create(null); 149 for (let k of array) { 150 out[k] = true; 151 } 152 return out; 153 } 154 }; 155 base.MapUtils = MapUtils; 156 157 // asmdb.base.Operand 158 // ================== 159 160 const OperandFlags = Object.freeze({ 161 Optional : 0x00000001, 162 Implicit : 0x00000002, 163 Commutative: 0x00000004, 164 ZExt : 0x00000008, 165 ReadAccess : 0x00000010, 166 WriteAccess: 0x00000020 167 }); 168 base.OperandFlags = OperandFlags; 169 170 class Operand { 171 constructor() { 172 this.type = ""; // Type of the operand ("reg", "reg-list", "mem", "reg/mem", "imm", "rel"). 173 this.data = ""; // The operand's data (possibly processed). 174 this.flags = 0; 175 176 this.reg = ""; // Register operand's definition. 177 this.mem = ""; // Memory operand's definition. 178 this.imm = 0; // Immediate operand's size. 179 this.rel = 0; // Relative displacement operand's size. 180 181 this.restrict = ""; // Operand is restricted (specific register or immediate value). 182 this.read = false; // True if the operand is a read-op from reg/mem. 183 this.write = false; // True if the operand is a write-op to reg/mem. 184 185 this.regType = ""; // Register operand's type. 186 this.regIndexRel = 0; // Register index is relative to the previous register operand index (0 if not). 187 this.memSize = -1; // Memory operand's size. 188 this.immSign = ""; // Immediate sign (any / signed / unsigned). 189 this.immValue = null; // Immediate value - `null` or `1` (only used by shift/rotate instructions). 190 191 this.rwxIndex = -1; // Read/Write (RWX) index. 192 this.rwxWidth = -1; // Read/Write (RWX) width. 193 } 194 195 _getFlag(flag) { 196 return (this.flags & flag) != 0; 197 } 198 199 _setFlag(flag, value) { 200 this.flags = (this.flags & ~flag) | (value ? flag : 0); 201 return this; 202 } 203 204 get optional() { return this._getFlag(OperandFlags.Optional); } 205 set optional(value) { this._setFlag(OperandFlags.Optional, value); } 206 207 get implicit() { return this._getFlag(OperandFlags.Implicit); } 208 set implicit(value) { this._setFlag(OperandFlags.Implicit, value); } 209 210 get commutative() { return this._getFlag(OperandFlags.Commutative); } 211 set commutative(value) { this._setFlag(OperandFlags.Commutative, value); } 212 213 get zext() { return this._getFlag(OperandFlags.ZExt); } 214 set zext(value) { this._setFlag(OperandFlags.ZExt, value); } 215 216 toString() { return this.data; } 217 218 isReg() { return !!this.reg && this.type !== "reg-list"; } 219 isMem() { return !!this.mem; } 220 isImm() { return !!this.imm; } 221 isRel() { return !!this.rel; } 222 223 isRegMem() { return this.reg && this.mem; } 224 isRegOrMem() { return !!this.reg || !!this.mem; } 225 226 isRegList() { return this.type === "reg-list" } 227 isPartialOp() { return false; } 228 } 229 base.Operand = Operand; 230 231 // asmdb.base.Instruction 232 // ====================== 233 234 // Defines interface and properties that each architecture dependent instruction 235 // must provide even if that particular architecture doesn't use that feature(s). 236 class Instruction { 237 constructor(db) { 238 Object.defineProperty(this, "db", { value: db }); 239 240 this.name = ""; // Instruction name. 241 this.arch = "ANY"; // Architecture. 242 this.encoding = ""; // Encoding type. 243 this.operands = []; // Instruction operands. 244 245 this.implicit = 0; // Indexes of all implicit operands (registers / memory). 246 this.commutative = 0; // Indexes of all commutative operands. 247 248 this.opcodeString = ""; // Instruction opcode as specified in manual. 249 this.opcodeValue = 0; // Instruction opcode as number (arch dependent). 250 this.fields = dict(); // Information about each opcode field (arch dependent). 251 this.operations = dict(); // Operations the instruction performs. 252 253 this.io = dict(); // Instruction input / output (CPU flags, states, and other registers). 254 this.ext = dict(); // ISA extensions required by the instruction. 255 this.category = dict(); // Instruction categories. 256 257 this.specialRegs = dict(); // Information about read/write to special registers. 258 259 this.alt = false; // This is an alternative form, not needed to create a signature. 260 this.volatile = false; // Instruction is volatile and should not be reordered. 261 this.control = "none"; // Control flow type (none by default). 262 this.privilege = ""; // Privilege-level required to execute the instruction. 263 this.aliasOf = ""; // Instruction is an alias of another instruction 264 } 265 266 get extArray() { 267 const out = Object.keys(this.ext); 268 out.sort(); 269 return out; 270 } 271 272 get operandCount() { 273 return this.operands.length; 274 } 275 276 get minimumOperandCount() { 277 const count = this.operands.length; 278 for (let i = 0; i < count; i++) { 279 if (this.operands[i].optional) { 280 return i; 281 } 282 } 283 return count 284 } 285 286 _assignAttribute(key, value) { 287 switch (key) { 288 case "ext": 289 case "io": 290 case "category": 291 return this._combineAttribute(key, value); 292 293 default: 294 if (typeof this[key] === undefined) 295 FAIL(`Cannot assign ${key}=${value}`); 296 this[key] = value; 297 break; 298 } 299 } 300 301 _combineAttribute(key, value) { 302 if (typeof value === "string") 303 value = value.split(" "); 304 305 if (Array.isArray(value)) { 306 for (let v of value) { 307 let pKeys = v; 308 let pValue = true; 309 310 const i = v.indexOf("="); 311 if (i !== -1) { 312 pValue = v.substring(i + 1); 313 pKeys = v.substring(0, i).trim(); 314 } 315 316 for (let pk of pKeys.trim().split("|").map(function(s) { return s.trim(); })) { 317 this[key][pk] = pValue; 318 } 319 } 320 } 321 else { 322 for (let k in value) 323 this[key][k] = value[k]; 324 } 325 } 326 327 _updateOperandsInfo() { 328 this.implicit = 0; 329 this.commutative = 0; 330 331 for (let i = 0; i < this.operands.length; i++) { 332 const op = this.operands[i]; 333 334 if (op.implicit) this.implicit |= (1 << i); 335 if (op.commutative) this.commutative |= (1 << i); 336 } 337 } 338 339 isAlias() { return !!this.aliasOf; } 340 isCommutative() { return this.commutative !== 0; } 341 342 hasImplicit() { return this.implicit !== 0; } 343 344 hasAttribute(name, matchValue) { 345 const value = this[name]; 346 if (value === undefined) 347 return false; 348 349 if (matchValue === undefined) 350 return true; 351 352 return value === matchValue; 353 } 354 355 report(msg) { 356 console.log(`${this}: ${msg}`); 357 } 358 359 toString() { 360 return `${this.name} ${this.operands.join(", ")}`; 361 } 362 } 363 base.Instruction = Instruction; 364 365 // asmdb.base.InstructionGroup 366 // =========================== 367 368 // Instruction group is simply array of function that has some additional 369 // functionality. 370 class InstructionGroup extends Array { 371 constructor() { 372 super(); 373 374 if (arguments.length === 1) { 375 const a = arguments[0]; 376 if (Array.isArray(a)) { 377 for (let i = 0; i < a.length; i++) 378 this.push(a[i]); 379 } 380 } 381 } 382 383 unionCpuFeatures(name) { 384 const result = dict(); 385 for (let i = 0; i < this.length; i++) { 386 const instruction = this[i]; 387 const features = instruction.ext; 388 for (let k in features) 389 result[k] = features[k]; 390 } 391 return result; 392 } 393 394 checkAttribute(key, value) { 395 let n = 0; 396 for (let i = 0; i < this.length; i++) 397 n += Number(this[i][key] === value); 398 return n; 399 } 400 } 401 base.InstructionGroup = InstructionGroup; 402 403 const EmptyInstructionGroup = Object.freeze(new InstructionGroup()); 404 405 // asmdb.base.ISA 406 // ============== 407 408 class ISA { 409 constructor() { 410 this._instructions = null; // Instruction array (contains all instructions). 411 this._instructionNames = null; // Instruction names (sorted), regenerated when needed. 412 this._instructionMap = dict(); // Instruction name to `Instruction[]` mapping. 413 this._aliases = dict(); // Instruction aliases. 414 this._aliasMap = dict(); // Instruction aliases. 415 this._cpuLevels = dict(); // Architecture versions. 416 this._extensions = dict(); // Architecture extensions. 417 this._attributes = dict(); // Instruction attributes. 418 this._specialRegs = dict(); // Special registers. 419 this._shortcuts = dict(); // Shortcuts used by instructions metadata. 420 this.stats = { 421 instructions : 0, // Number of all instructions. 422 groups: 0 // Number of grouped instructions (having unique name). 423 }; 424 } 425 426 get instructions() { 427 let array = this._instructions; 428 if (array === null) { 429 array = []; 430 const map = this.instructionMap; 431 const names = this.instructionNames; 432 for (let i = 0; i < names.length; i++) 433 array.push.apply(array, map[names[i]]); 434 this._instructions = array; 435 } 436 return array; 437 } 438 439 get instructionNames() { 440 let names = this._instructionNames; 441 if (names === null) { 442 names = Object.keys(this._instructionMap); 443 names.sort(); 444 this._instructionNames = names; 445 } 446 return names; 447 } 448 449 get instructionMap() { return this._instructionMap; } 450 get aliases() { return this._aliasMap; } 451 get cpuLevels() { return this._cpuLevels; } 452 get extensions() { return this._extensions; } 453 get attributes() { return this._attributes; } 454 get specialRegs() { return this._specialRegs; } 455 get shortcuts() { return this._shortcuts; } 456 457 query(args, copy) { 458 if (typeof args !== "object" || !args || Array.isArray(args)) 459 return this._queryByName(args, copy); 460 461 const filter = args.filter; 462 if (filter) 463 copy = false; 464 465 let result = this._queryByName(args.name, copy); 466 if (filter) 467 result = result.filter(filter, args.filterThis); 468 469 return result; 470 } 471 472 aliasData(name) { 473 return this._aliases[name] || null; 474 } 475 476 _queryByName(name, copy) { 477 let result = EmptyInstructionGroup; 478 const map = this._instructionMap; 479 480 if (typeof name === "string") { 481 const instructions = map[name]; 482 if (instructions) result = instructions; 483 return copy ? result.slice() : result; 484 } 485 486 if (Array.isArray(name)) { 487 const names = name; 488 for (let i = 0; i < names.length; i++) { 489 const instructions = map[names[i]]; 490 if (!instructions) continue; 491 492 if (result === EmptyInstructionGroup) 493 result = new InstructionGroup(); 494 495 for (let j = 0; j < instructions.length; j++) 496 result.push(instructions[j]); 497 } 498 return result; 499 } 500 501 result = this.instructions; 502 return copy ? result.slice() : result; 503 } 504 505 forEachGroup(cb, thisArg) { 506 const map = this._instructionMap; 507 const names = this.instructionNames; 508 509 for (let i = 0; i < names.length; i++) { 510 const name = names[i]; 511 cb.call(thisArg, name, map[name]); 512 } 513 514 return this; 515 } 516 517 addData(data) { 518 if (typeof data !== "object" || !data) 519 FAIL("ISA.addData(): data argument must be object"); 520 521 if (data.cpuLevels) this._addCpuLevels(data.cpuLevels); 522 if (data.specialRegs) this._addSpecialRegs(data.specialRegs); 523 if (data.shortcuts) this._addShortcuts(data.shortcuts); 524 if (data.instructions) this._addInstructions(data.instructions); 525 if (data.aliases) this._addAliases(data.aliases); 526 if (data.postproc) this._postProc(data.postproc); 527 } 528 529 _postProc(groups) { 530 for (let group of groups) { 531 for (let iRule of group.instructions) { 532 const names = iRule.name.split(" "); 533 for (let name of names) { 534 const instructions = this._instructionMap[name]; 535 if (!instructions) 536 FAIL(`Instruction ${name} referenced by '${group.group}' group doesn't exist`); 537 538 for (let k in iRule) { 539 if (k === "name" || k === "data") 540 continue; 541 for (let instruction of instructions) { 542 instruction._assignAttribute(k, iRule[k]); 543 } 544 } 545 } 546 } 547 } 548 } 549 550 _addCpuLevels(items) { 551 if (!Array.isArray(items)) 552 FAIL("Property 'cpuLevels' must be array"); 553 554 for (let i = 0; i < items.length; i++) { 555 const item = items[i]; 556 const name = item.name; 557 558 const obj = { 559 name: name 560 }; 561 562 this._cpuLevels[name] = obj; 563 } 564 } 565 566 _addExtensions(items) { 567 if (!Array.isArray(items)) 568 FAIL("Property 'extensions' must be array"); 569 570 for (let i = 0; i < items.length; i++) { 571 const item = items[i]; 572 const name = item.name; 573 574 const obj = { 575 name: name, 576 from: item.from || "" 577 }; 578 579 this._extensions[name] = obj; 580 } 581 } 582 583 _addAttributes(items) { 584 if (!Array.isArray(items)) 585 FAIL("Property 'attributes' must be array"); 586 587 for (let i = 0; i < items.length; i++) { 588 const item = items[i]; 589 const name = item.name; 590 const type = item.type; 591 592 if (!/^(?:flag|string|string\[\])$/.test(type)) 593 FAIL(`Unknown attribute type '${type}'`); 594 595 const obj = { 596 name: name, 597 type: type, 598 doc : item.doc || "" 599 }; 600 601 this._attributes[name] = obj; 602 } 603 } 604 605 _addSpecialRegs(items) { 606 if (!Array.isArray(items)) 607 FAIL("Property 'specialRegs' must be array"); 608 609 for (let i = 0; i < items.length; i++) { 610 const item = items[i]; 611 const name = item.name; 612 613 const obj = { 614 name : name, 615 group: item.group || name, 616 doc : item.doc || "" 617 }; 618 619 this._specialRegs[name] = obj; 620 } 621 } 622 623 _addShortcuts(items) { 624 if (!Array.isArray(items)) 625 FAIL("Property 'shortcuts' must be array"); 626 627 for (let i = 0; i < items.length; i++) { 628 const item = items[i]; 629 const name = item.name; 630 const expand = item.expand; 631 632 if (!name || !expand) 633 FAIL("Shortcut must contain 'name' and 'expand' properties"); 634 635 const obj = { 636 name : name, 637 expand: expand, 638 doc : item.doc || "" 639 }; 640 641 this._shortcuts[name] = obj; 642 } 643 } 644 645 _addInstructions(instructions) { 646 FAIL("ISA._addInstructions() must be reimplemented"); 647 } 648 649 _addInstruction(instruction) { 650 let group; 651 652 if (Object.hasOwn(this._instructionMap, instruction.name)) { 653 group = this._instructionMap[instruction.name]; 654 } 655 else { 656 group = new InstructionGroup(); 657 this._instructionNames = null; 658 this._instructionMap[instruction.name] = group; 659 this.stats.groups++; 660 } 661 662 if (instruction.aliasOf) { 663 this._addAlias(instruction.name, instruction.aliasOf); 664 } 665 666 group.push(instruction); 667 this.stats.instructions++; 668 this._instructions = null; 669 670 return this; 671 } 672 673 // Add aliases from instruction database - aliases must be an object where each key is a non-aliased instruction. 674 _addAliases(aliases) { 675 for (let instructionName in aliases) { 676 const data = aliases[instructionName]; 677 for (let aliasName of data.aliases) { 678 this._addAlias(instructionName, aliasName, data.format || ""); 679 } 680 } 681 } 682 683 _addAlias(instructionName, aliasName, aliasFormat) { 684 const group = this._instructionMap[instructionName]; 685 if (!group) { 686 FAIL(`Instruction ${instructionName} doesn't exist when processing alias (${aliasName})`); 687 } 688 689 let alias = this._aliases[instructionName]; 690 if (!alias) { 691 alias = dict({ 692 primaryName: instructionName, 693 aliasNames: [], 694 format: "" 695 }); 696 this._aliases[instructionName] = alias; 697 } 698 699 this._aliasMap[aliasName] = instructionName; 700 alias.aliasNames.push(aliasName); 701 alias.format = aliasFormat || ""; 702 } 703 } 704 base.ISA = ISA; 705 706 }).apply(this, typeof module === "object" && module && module.exports 707 ? [module, "exports"] : [this.asmdb || (this.asmdb = {}), "base"]);