// This file is part of AsmJit project <https://asmjit.com>
//
// See asmjit.h or LICENSE.md for license and copyright information
// SPDX-License-Identifier: (Zlib or Unlicense)

(function($scope, $as) {
"use strict";

function FAIL(msg) { throw new Error("[AArch64] " + msg); }

// Import
// ======

const base = $scope.base ? $scope.base : require("./base.js");
const exp = $scope.exp ? $scope.exp : require("./exp.js")

const hasOwn = Object.prototype.hasOwnProperty;
const dict = base.dict;
const NONE = base.NONE;
const Parsing = base.Parsing;
const MapUtils = base.MapUtils;

// Export
// ======

const arm = $scope[$as] = dict();

// Database
// ========

arm.dbName = "isa_aarch64.json";

// asmdb.aarch64.Utils
// ===================

// Can be used to assign the number of bits each part of the opcode occupies.
// NOTE: THUMB instructions that use halfword must always specify the width
// of all registers as many instructions accept only LO (r0..r7) registers.
const FieldInfo = {
  "P"     : { "bits": 1 },
  "U"     : { "bits": 1 },
  "W"     : { "bits": 1 },
  "S"     : { "bits": 1 },
  "R"     : { "bits": 1 },
  "H"     : { "bits": 1 },
  "F"     : { "bits": 1 },
  "post"  : { "bits": 1 },
  "!post" : { "bits": 1 },
  "op"    : { "bits": 1 }, // TODO: This should be fixed.
  "s"     : { "bits": 1 },
  "sz"    : { "bits": 2 },
  "msz"   : { "bits": 2 },
  "sop"   : { "bits": 2 },
  "cond"  : { "bits": 4 },
  "nzcv"  : { "bits": 4 },
  "cmode" : { "bits": 4 },
  "CRn"   : { "bits": 4 },
  "CRm"   : { "bits": 4 },

  "Rx"    : { "bits": 5, "read": true , "write": true  },
  "Rx2"   : { "bits": 5, "read": true , "write": true  },
  "Rdn"   : { "bits": 5, "read": true , "write": true  },
  "Rd"    : { "bits": 5, "read": false, "write": true  },
  "Rd2"   : { "bits": 5, "read": false, "write": true  },
  "Rs"    : { "bits": 5, "read": true , "write": false },
  "Rs2"   : { "bits": 5, "read": true , "write": false },
  "Rn"    : { "bits": 5, "read": true , "write": false },
  "Rm"    : { "bits": 5, "read": true , "write": false },
  "Ra"    : { "bits": 5, "read": true , "write": false },
  "Rt"    : { "bits": 5, "read": true , "write": false },
  "Rt2"   : { "bits": 5, "read": true , "write": false },

  "Wx"    : { "bits": 5, "read": true , "write": true  },
  "Wx2"   : { "bits": 5, "read": true , "write": true  },
  "Wdn"   : { "bits": 5, "read": true , "write": true  },
  "Wd"    : { "bits": 5, "read": false, "write": true  },
  "Wd2"   : { "bits": 5, "read": false, "write": true  },
  "Ws"    : { "bits": 5, "read": true , "write": false },
  "Ws2"   : { "bits": 5, "read": true , "write": false },
  "Wn"    : { "bits": 5, "read": true , "write": false },
  "Wm"    : { "bits": 5, "read": true , "write": false },
  "Wa"    : { "bits": 5, "read": true , "write": false },
  "Wt"    : { "bits": 5, "read": true , "write": false },
  "Wt2"   : { "bits": 5, "read": true , "write": false },

  "Xx"    : { "bits": 5, "read": true , "write": true  },
  "Xx2"   : { "bits": 5, "read": true , "write": true  },
  "Xdn"   : { "bits": 5, "read": true , "write": true  },
  "Xd"    : { "bits": 5, "read": false, "write": true  },
  "Xd2"   : { "bits": 5, "read": false, "write": true  },
  "Xs"    : { "bits": 5, "read": true , "write": false },
  "Xs2"   : { "bits": 5, "read": true , "write": false },
  "Xn"    : { "bits": 5, "read": true , "write": false },
  "Xm"    : { "bits": 5, "read": true , "write": false },
  "Xa"    : { "bits": 5, "read": true , "write": false },
  "Xt"    : { "bits": 5, "read": true , "write": false },
  "Xt2"   : { "bits": 5, "read": true , "write": false },

  "Bx"    : { "bits": 5, "read": true , "write": true  },
  "Bx2"   : { "bits": 5, "read": true , "write": true  },
  "Bdn"   : { "bits": 5, "read": true , "write": true  },
  "Bd"    : { "bits": 5, "read": false, "write": true  },
  "Bd2"   : { "bits": 5, "read": false, "write": true  },
  "Bs"    : { "bits": 5, "read": true , "write": false },
  "Bs2"   : { "bits": 5, "read": true , "write": false },
  "Bn"    : { "bits": 5, "read": true , "write": false },
  "Bm"    : { "bits": 5, "read": true , "write": false },
  "Ba"    : { "bits": 5, "read": true , "write": false },

  "Hx"    : { "bits": 5, "read": true , "write": true  },
  "Hx2"   : { "bits": 5, "read": true , "write": true  },
  "Hdn"   : { "bits": 5, "read": true , "write": true  },
  "Hd"    : { "bits": 5, "read": false, "write": true  },
  "Hd2"   : { "bits": 5, "read": false, "write": true  },
  "Hs"    : { "bits": 5, "read": true , "write": false },
  "Hs2"   : { "bits": 5, "read": true , "write": false },
  "Hn"    : { "bits": 5, "read": true , "write": false },
  "Hm"    : { "bits": 5, "read": true , "write": false },
  "Ha"    : { "bits": 5, "read": true , "write": false },

  "Sx"    : { "bits": 5, "read": true , "write": true  },
  "Sx2"   : { "bits": 5, "read": true , "write": true  },
  "Sdn"   : { "bits": 5, "read": true , "write": true  },
  "Sd"    : { "bits": 5, "read": false, "write": true  },
  "Sd2"   : { "bits": 5, "read": false, "write": true  },
  "Ss"    : { "bits": 5, "read": true , "write": false },
  "Ss2"   : { "bits": 5, "read": true , "write": false },
  "Sn"    : { "bits": 5, "read": true , "write": false },
  "Sm"    : { "bits": 5, "read": true , "write": false },
  "Sa"    : { "bits": 5, "read": true , "write": false },

  "Dx"    : { "bits": 5, "read": true , "write": true  },
  "Dx2"   : { "bits": 5, "read": true , "write": true  },
  "Ddn"   : { "bits": 5, "read": true , "write": true  },
  "Dd"    : { "bits": 5, "read": false, "write": true  },
  "Dd2"   : { "bits": 5, "read": false, "write": true  },
  "Ds"    : { "bits": 5, "read": true , "write": false },
  "Ds2"   : { "bits": 5, "read": true , "write": false },
  "Dn"    : { "bits": 5, "read": true , "write": false },
  "Dn2"   : { "bits": 5, "read": true , "write": false },
  "Dm"    : { "bits": 5, "read": true , "write": false },
  "Da"    : { "bits": 5, "read": true , "write": false },

  "Qx"    : { "bits": 5, "read": true , "write": true  },
  "Qx2"   : { "bits": 5, "read": true , "write": true  },
  "Qdn"   : { "bits": 5, "read": true , "write": true  },
  "Qd"    : { "bits": 5, "read": false, "write": true  },
  "Qd2"   : { "bits": 5, "read": false, "write": true  },
  "Qs"    : { "bits": 5, "read": true , "write": false },
  "Qs2"   : { "bits": 5, "read": true , "write": false },
  "Qn"    : { "bits": 5, "read": true , "write": false },
  "Qn2"   : { "bits": 5, "read": true , "write": false },
  "Qm"    : { "bits": 5, "read": true , "write": false },
  "Qa"    : { "bits": 5, "read": true , "write": false },

  "Vx"    : { "bits": 5, "read": true , "write": true  },
  "Vx2"   : { "bits": 5, "read": true , "write": true  },
  "Vdn"   : { "bits": 5, "read": true , "write": true  },
  "Vd"    : { "bits": 5, "read": false, "write": true  },
  "Vd2"   : { "bits": 5, "read": false, "write": true  },
  "Vs"    : { "bits": 5, "read": true , "write": false },
  "Vs2"   : { "bits": 5, "read": true , "write": false },
  "Vn"    : { "bits": 5, "read": true , "write": false },
  "Vm"    : { "bits": 5, "read": true , "write": false },
  "Va"    : { "bits": 5, "read": true , "write": false },

  "Zx"    : { "bits": 5, "read": true , "write": true  },
  "Zx2"   : { "bits": 5, "read": true , "write": true  },
  "Zda"   : { "bits": 5, "read": true , "write": true  },
  "Zdn"   : { "bits": 5, "read": true , "write": true  },
  "Zdn2"  : { "bits": 5, "read": true , "write": true  },
  "Zd"    : { "bits": 5, "read": false, "write": true  },
  "Zd2"   : { "bits": 5, "read": false, "write": true  },
  "Zs"    : { "bits": 5, "read": true , "write": false },
  "Zs2"   : { "bits": 5, "read": true , "write": false },
  "Zn"    : { "bits": 5, "read": true , "write": false },
  "Zm"    : { "bits": 5, "read": true , "write": false },
  "Zk"    : { "bits": 5, "read": true , "write": false },
  "Za"    : { "bits": 5, "read": true , "write": false },

  "Pdn"   : { "bits": 4, "read": true , "write": true  },
  "Pdm"   : { "bits": 4, "read": true , "write": true  },
  "Pd"    : { "bits": 4, "read": false, "write": true  },
  "Ps"    : { "bits": 4, "read": true , "write": false },
  "Pn"    : { "bits": 4, "read": true , "write": false },
  "Pm"    : { "bits": 4, "read": true , "write": false },
  "Pg"    : { "bits": 4, "read": true , "write": false }
};

arm.FieldInfo = FieldInfo;

// AArch64 utilities.
class Utils {
  static splitInstructionSignature(s) {
    const names = s.match(/^[\w\|]+/)[0];
    s = s.substring(names.length);

    const opOffset = s.indexOf(" ")
    const suffix = s.substring(0, opOffset).trim();
    const operands = opOffset === -1 ? "" : s.substring(opOffset + 1).trim();

    return {
      names: names.split("|").map((base)=>{ return base + suffix}),
      operands: operands
    }
  }

  static parseShiftOrExtendOp(s) {
    const space = s.indexOf(" ");
    if (space === -1)
      return "";

    const ops = s.substring(0, space).trim();
    for (let op of ops.split("|"))
      if (!/^(sop|extend|lsl|lsr|asr|uxtw|sxtw|sxtx|mul)$/.test(op))
        return "";

    return ops;
  }
}
arm.Utils = Utils;

function normalizeNumber(n) {
  return n < 0 ? 0x100000000 + n : n;
}

function decomposeOperand(s) {
  let type = null;
  let element = null;
  let consecutive = 0;
  let maskType = "";

  const elementM = s.match(/\[#(\w+)\]$/);
  if (elementM) {
    element = elementM[1];
    s = s.substring(0, s.length - elementM[0].length);
  }

  const typeM = s.match(/\.(\w+)$/);
  if (typeM) {
    type = typeM[1];
    s = s.substring(0, s.length - typeM[0].length);
  }

  const maskM = s.match(/\/(M|Z|MZ)$/);
  if (maskM) {
    maskType = maskM[1];
    s = s.substring(0, s.length - maskM[0].length);
  }

  if (s.endsWith("++")) {
    consecutive = 2;
    s = s.substring(0, s.length - 2);
  }
  else if (s.endsWith("+")) {
    consecutive = 1;
    s = s.substring(0, s.length - 1);
  }

  let m = s.match(/==|\!=|>=|<=|\*/);
  let restrict = false;

  if (m) {
    restrict = s.substring(m.index);
    s = s.substring(0, m.index);
  }

  return {
    data       : s,
    maskType   : maskType,
    type       : type,
    element    : element,
    restrict   : restrict,
    consecutive: consecutive
  };
}

function splitOpcodeFields(s) {
  const arr = s.split("|");
  const out = [];

  for (let i = 0; i < arr.length; i++) {
    const val = arr[i];
    if (/^[0-1A-Z]{2,}$/.test(val))
      out.push.apply(out, val.match(/([0-1]+)|[A-Z]/g));
    else
      out.push(val);
  }

  return out.map((field)=>{return field.trim(); });
}

// asmdb.aarch64.Operand
// =====================

// ARM operand.
class Operand extends base.Operand {
  constructor(def) {
    super(def);

    // Register.
    this.sp = "";   // GP register stack access: ["", "WSP" or "SP"].
    this.mask = ""; // Masking specifier.
  }

  hasMemModes() {
    return Object.keys(this.memModes).length !== 0;
  }

  get name() {
    switch (this.type) {
      case "reg": return this.reg;
      case "mem": return this.mem;
      case "imm": return this.imm;
      case "rel": return this.rel;
      default   : return "";
    }
  }

  get scale() {
    if (this.restrict && this.restrict.startsWith("*"))
      return parseInt(this.restrict.substring(1), 10);
    else
      return 0;
  }
}
arm.Operand = Operand;

// asmdb.aarch64.Instruction
// =========================

function patternFromOperand(key) { return key; }

// ARM instruction.
class Instruction extends base.Instruction {
  constructor(db, data) {
    super(db, data);

    this.name = data.name;
    this.it = dict();                // THUMB's 'it' flags.
    this.apsr = dict();
    this.fpcsr = dict();
    this.calc = dict();              // Calculations required to generate opcode.
    this.immCond = [];               // Immediate value conditions (array of conditions).

    this._assignOperands(data.operands);
    this._assignOpcode(data.op);

    for (let k in data) {
      if (k === "name" || k == "op" || k === "operands")
        continue;
      this._assignAttribute(k, data[k]);
    }

    this._updateOperandsInfo();
    this._postProcess();
  }

  _assignAttribute(key, value) {
    switch (key) {
      case "it":
        for (let it of value.split(" "))
          this.it[it.trim()] = true;
        break;

      case "apsr":
      case "fpcsr":
        this._assignAttributeKeyValue(key, value);
        break;

      case "imm":
        this.imm = exp.parse(value);
        break;

      case "calc":
        for (let calcKey in value)
          this.calc[calcKey] = exp.parse(value[calcKey]);
        break;

      default:
        super._assignAttribute(key, value);
    }
  }

  _assignAttributeKeyValue(name, content) {
    const attributes = content.trim().split(/[ ]+/);

    for (let i = 0; i < attributes.length; i++) {
      const attr = attributes[i].trim();
      if (!attr)
        continue;

      const eq = attr.indexOf("=");
      let key = eq === -1 ? attr : attr.substring(0, eq);
      let val = eq === -1 ? true : attr.substring(eq + 1);

      // If the key contains "|" it's a definition of multiple attributes.
      if (key.indexOf("|") !== -1) {
        const dot = key.indexOf(".");

        const base = dot === -1 ? "" : key.substring(0, dot + 1);
        const keys = (dot === -1 ? key : key.substring(dot + 1)).split("|");

        for (let j = 0; j < keys.length; j++)
          this[name][base + keys[j]] = val;
      }
      else {
        this[name][key] = val;
      }
    }
  }

  _assignOperands(s) {
    if (!s) return;

    // Split into individual operands and push them to `operands`.
    const arr = base.Parsing.splitOperands(s);
    for (let i = 0; i < arr.length; i++) {
      let def = arr[i].trim();
      const op = new Operand(def);

      const sp = def.match(/^(\w+)\|(SP|WSP)$/);
      if (sp) {
        def = sp[1];
        op.sp = sp[2];
      }

      const consecutive = def.match(/(\d+)x\{(.*)\}([+]?[+]?)/);
      if (consecutive)
        def = consecutive[2];

      op.sign = false;
      op.element = null;
      op.shiftOp = "";
      op.shiftImm = null;
      op.shiftCond = "";

      // Handle {optional} attribute.
      if (Parsing.isOptional(def)) {
        op.optional = true;
        def = Parsing.clearOptional(def);
      }

      // Handle commutativity <-> symbol.
      if (Parsing.isCommutative(def)) {
        op.commutative = true;
        def = Parsing.clearCommutative(def);
      }

      // Handle shift operation.
      let shiftOp = Utils.parseShiftOrExtendOp(def);
      if (shiftOp) {
        op.shiftOp = shiftOp;
        def = def.substring(shiftOp.length + 1);
      }

      if (def.startsWith("[")) {
        op.type = "mem";
        op.memModes = dict();

        op.base = null;
        op.index = null;
        op.offset = null;

        let mem = def;
        let didHaveMemMode = false;

        for (;;) {
          if (mem.endsWith("!")) {
            op.memModes.preIndex = true;
            mem = mem.substring(0, mem.length - 1);

            didHaveMemMode = true;
            break;
          }

          if (mem.endsWith("@")) {
            op.memModes.postIndex = true;
            mem = mem.substring(0, mem.length - 1);

            didHaveMemMode = true;
            break;
          }

          if (mem.endsWith("{!}")) {
            op.memModes.offset = true;
            op.memModes.preIndex = true;
            mem = mem.substring(0, mem.length - 3);

            didHaveMemMode = true;
            continue;
          }

          if (mem.endsWith("{@}")) {
            op.memModes.offset = true;
            op.memModes.postIndex = true;
            mem = mem.substring(0, mem.length - 3);

            didHaveMemMode = true;
            continue;
          }

          break;
        }

        if (!mem.endsWith("]"))
          FAIL(`Unknown memory operand '${mem}' in '${def}'`);

        let parts = mem.substring(1, mem.length - 1).split(",").map(function(s) { return s.trim() });
        for (let i = 0; i < parts.length; i++) {
          const part = parts[i];

          const m = part.match(/^\{(([a-z]+)(\|[a-z]+)*)\s+#(\w+)\s*(\*\s*\d+\s*)?\}$/);
          if (m) {
            op.shiftOp = m[1];
            op.shiftImm = m[2];
            if (m[3])
              op.shiftCond = m[3]
            continue;
          }

          if (i === 0) {
            op.base = dict();
            op.base.field = part;
            op.base.exp = null;

            const m = part.match(/^([A-Za-z]\w*(?:\.\w+)?)/);
            if (m && m[1].length < part.length) {
              op.base.exp = exp.parse(part);
              op.base.field = m[1];
            }
          }
          else if (part.startsWith("#")) {
            let p = part.substring(1);
            let u = "1";

            let offExp = null;
            let offMul = 1;

            if (p.startsWith("+/-")) {
              u = "U";
              p = p.substring(3);
            }

            const expMatch = p.match(/^([A-Za-z]\w*)==/);
            if (expMatch) {
              offExp = exp.parse(p);
              p = p.substring(0, expMatch[1].length);
            }

            const mulMatch = p.match(/\s*\*\s*(\d+)$/);
            if (mulMatch) {
              offMul = parseInt(mulMatch[1]);
              p = p.substring(0, mulMatch.index);
            }

            op.offset = dict();
            op.offset.field = p;
            op.offset.u = u;
            op.offset.exp = offExp;
            op.offset.mul = offMul;
          }
          else {
            let p = part;
            let u = "1";

            if (p.startsWith("+/-")) {
              u = "U";
              p = p.substring(3);
            }

            op.index = dict();
            op.index.field = p;
            op.index.u = u;

            const m = p.match(/^([A-Za-z\|]\w*(?:\.\w+)?)/);
            if (m && m[1].length < p.length) {
              op.index.exp = exp.parse(p);
              op.index.field = m[1];
            }
          }
        }

        if (!op.hasMemModes() && (op.offset || op.index))
          op.memModes.offset = true;

        op.mem = mem;
      }
      else if (def.startsWith("#")) {
        const obj = decomposeOperand(def);
        const imm = obj.data;

        op.type = "imm";
        op.imm = imm.substring(1);        // Immediate operand name.
        op.immSize = 0;                   // Immediate size in bits.
        op.restrict = obj.restrict;       // Immediate condition.
      }
      else {
        // Some instructions use Reg! to specify that the register increments.
        if (def.endsWith("!")) {
          def = def.substring(0, def.length - 1)
          op.regInc = true
        }

        const obj = decomposeOperand(def);
        const reg = obj.data;

        const type = reg.substring(0, 1).toLowerCase();
        const info = FieldInfo[reg];

        if (!info)
          FAIL(`Unknown register operand '${reg}' in '${def}'`);

        op.type        = info.list ? "reg-list" : "reg";
        op.reg         = reg;                // Register name (as specified in manual).
        op.regType     = type;               // Register type.
        op.regList     = !!info.list;        // Register list.
        op.maskType    = obj.maskType;       // Mask type.
        op.elementType = obj.type            // Element type or t, ta, tb.
        op.read        = info.read;          // Register access (read).
        op.write       = info.write;         // Register access (write).
        op.element     = obj.element;        // Register element[] access.
        op.restrict    = obj.restrict;       // Register condition.
        op.consecutive = obj.consecutive;
      }

      this.operands.push(op);

      if (consecutive) {
        const count = parseInt(consecutive[1]);
        for (let n = 2; n <= count; n++) {
          const def = consecutive[3].replace(op.reg, op.reg + n);
          const opN = new Operand(def);
          opN.type = "reg";
          opN.reg = op.reg + n;
          opN.regType = op.regType;
          opN.read = op.read;
          opN.write = op.write;
          opN.element = op.element;
          opN.consecutive = consecutive[3].length;
          opN.artificial = true;
          this.operands.push(opN);
        }
      }
    }
  }

  _assignOpcode(s) {
    this.opcodeString = s;

    let opcodeIndex = 0;
    let opcodeValue = 0;

    let patternMap = {};

    // Split opcode into its fields.
    const arr = splitOpcodeFields(s);
    const dup = dict();

    const fields = this.fields;
    const pattern = [];

    const fieldMap = Object.create(null);
    for (let field of arr) {
      fieldMap[field] = true;
    }

    for (let i = arr.length - 1; i >= 0; i--) {
      let key = arr[i].trim();
      let m;

      if (/^[0-1]+$/.test(key)) {
        // This part of the opcode is RAW bits, they contribute to the `opcodeValue`.
        opcodeValue |= parseInt(key, 2) << opcodeIndex;
        opcodeIndex += key.length;
        pattern.unshift("_".repeat(key.length));
      }
      else {
        pattern.unshift(patternFromOperand(key));
        patternMap[patternFromOperand(key)] = true;

        let size = 0;
        let mask = 0;
        let bits = 0;
        let from = -1;

        let lbit = key.startsWith("'");
        let hbit = key.endsWith("'");

        if ((m = key.match(/\[\s*(\d+)\s*\:\s*(\d+)\s*\]$/))) {
          const a = parseInt(m[1], 10);
          const b = parseInt(m[2], 10);
          if (a < b)
            FAIL(`Invalid bit range '${key}' in opcode '${s}'`);
          from = b;
          size = a - b + 1;
          mask = ((1 << size) - 1) << b;
          key = key.substring(0, m.index).trim();
        }
        else if ((m = key.match(/\[\s*(\d+)\s*\]$/))) {
          from = parseInt(m[1], 10);
          size = 1;
          mask = 1 << from;
          key = key.substring(0, m.index).trim();
        }
        else if ((m = key.match(/\:\s*(\d+)$/))) {
          size = parseInt(m[1], 10);
          bits = size;
          key = key.substring(0, m.index).trim();
        }
        else {
          const key_ = key;

          if (lbit || hbit) {
            from = 0;

            if (lbit && hbit)
              FAIL(`Couldn't recognize the format of '${key}' in opcode '${s}'`);

            if (lbit) {
              key = key.substring(1);
            }

            if (hbit) {
              key = key.substring(0, key.length - 1);
              from = 4;
            }

            size = 1;
          }
          else if (FieldInfo[key]) {
            // Sizes of some standard fields can be assigned automatically.
            size = FieldInfo[key].bits;
            bits = size;

            if (fieldMap["'" + key])
              from = 1;
          }
          else if (key.length === 1) {
            // Sizes of one-letter fields (like 'U', 'F', etc...) is 1 if not specified.
            size = 1;
            bits = 1;
          }
          else {
            FAIL(`Couldn't recognize the size of '${key}' in opcode '${s}'`);
          }

          if (dup[key_] === true) {
            bits = 0;
            lbit = 0;
            hbit = 0;
          }
          else {
            dup[key_] = true;
          }
        }

        let field = fields[key];
        if (!field) {
          field = {
            index: opcodeIndex,
            values: [],
            bits: 0,
            mask: 0,
            lbit: 0,
            hbit: 0 // Only 1 if a single quote (') was used.
          }
          fields[key] = field;
        }

        if (from === -1)
          from = field.bits;

        field.mask |= mask;
        field.bits += bits;
        field.lbit += lbit;
        field.hbit += hbit;
        field.values.push({
          index: opcodeIndex,
          from: from,
          size: size
        });

        opcodeIndex += size;
      }
    }

    for (let i = 0; i < pattern.length; i++)
      if (pattern[i] === 'U')
        pattern[i] = "_";

    // Normalize all fields.
    for (let key in fields) {
      const field = fields[key];

      // There should be either number of bits or mask, there shouldn't be both.
      if (!field.bits && !field.mask)
        FAIL(`Part '${key}' of opcode '${s}' contains neither size nor mask`);

      if (field.bits && field.mask)
        FAIL(`Part '${key}' of opcode '${s}' contains both size and mask`);

      if (field.bits)
        field.mask = ((1 << field.bits) - 1);
      else if (field.mask)
        field.bits = 32 - Math.clz32(field.mask);

      // Handle field that used single-quote.
      if (field.lbit) {
        field.mask = (field.mask << 1) | 0x1;
        field.bits++;
      }

      if (field.hbit) {
        field.mask |= 1 << field.bits;
        field.bits++;
      }

      const op = this.operandByName(key);
      if (op && op.isImm())
        op.immSize = field.bits;
    }

    // Check if the opcode value has the correct number of bits.
    if (opcodeIndex !== 32)
      FAIL(`The number of bits '${opcodeIndex}' used by the opcode '${s}' doesn't match 32`);
    this.opcodeValue = normalizeNumber(opcodeValue);
  }

  _assignSpecificAttribute(key, value) {
    switch (key) {
      case "it": {
        const values = String(value).split("|");
        for (let i = 0; i < values.length; i++) {
          const value = values[i];
          switch (value) {
            case "in"  : this.it.IN   = true; break;
            case "out" : this.it.OUT  = true; break;
            case "any" : this.it.IN   = true;
                         this.it.OUT  = true; break;
            case "last": this.it.LAST = true; break;
            case "def" : this.it.DEF  = true; break;
            default:
              this.report(`${this.name}: Unhandled IT value '${value}'`);
          }
        }
        return true;
      }
    }

    return false;
  }

  _postProcess() {}

  operandByName(name) {
    const operands = this.operands;
    for (let i = 0; i < operands.length; i++) {
      const op = operands[i];
      if (op.name === name)
        return op;
    }
    return null;
  }
}
arm.Instruction = Instruction;

// asmdb.aarch64.ISA
// =================

function mergeGroupData(data, group) {
  for (let k in group) {
    switch (k) {
      case "group":
      case "data":
        break;

      case "ext":
        data[k] = (data[k] ? data[k] + " " : "") + group[k];
        break;

      default:
        if (data[k] === undefined)
          data[k] = group[k]
        break;
    }
  }
}

class ISA extends base.ISA {
  constructor(data) {
    super(data);
    this.addData(data || NONE);
  }

  _addInstructions(groups) {
    for (let group of groups) {
      for (let inst of group.data) {
        const sgn = Utils.splitInstructionSignature(inst.inst);
        const data = MapUtils.cloneExcept(inst, { "inst": true });

        mergeGroupData(data, group)

        for (let j = 0; j < sgn.names.length; j++) {
          data.name = sgn.names[j];
          data.operands = sgn.operands;
          if (j > 0)
            data.aliasOf = sgn.names[0];
          this._addInstruction(new Instruction(this, data));
        }
      }
    }

    return this;
  }
}
arm.ISA = ISA;

}).apply(this, typeof module === "object" && module && module.exports
  ? [module, "exports"] : [this.asmdb || (this.asmdb = {}), "aarch64"]);