922 lines
27 KiB
JavaScript
922 lines
27 KiB
JavaScript
|
// 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
|
||
|
|
||
|
|
||
|
(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"]);
|