odin-blend2d

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

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"]);