Files
asmjit/tools/testgen-a32.js
2025-11-29 09:13:58 +01:00

418 lines
13 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
"use strict";
const core = require("./tablegen.js");
const isa = new core.asmdb.aarch32.ISA();
const LLVM_MC = "/home/petr/workspace/3rdparty/llvm-project-build/bin/llvm-mc";
function lsbFromMul(mul) {
for (let i = 0; i < 32; i++)
if ((mul & (1 << i)) != 0)
return i;
return 32;
}
class TestsGenerator {
constructor(isa) {
this.isa = isa;
}
generate() {
const child_process = require("child_process");
const tests = {
"gp": [],
"vec": []
};
function flatten(inputArray) {
const outputArray = [inputArray];
let i = 0;
while (i < outputArray.length) {
let didFlatten = false;
const items = outputArray[i];
for (let j = 0; j < items.length; j++) {
if (Array.isArray(items[j])) {
const flattenedItems = [];
for (let k = 0; k < items[j].length; k++) {
const copy = items.slice();
copy[j] = items[j][k];
flattenedItems.push(copy);
}
outputArray.splice(i, 1, ...flattenedItems);
didFlatten = true;
break;
}
}
i += Number(!didFlatten);
}
return outputArray;
}
function makeShiftOp(sop, op) {
if (sop === "sop")
return ["lsl " + op, "lsr " + op, "asr " + op, "ror " + op];
else if (sop === "lsl_or_asr")
return ["lsl " + op, "asr " + op];
else
return sop + " " + op;
}
function encodingFromText(s) {
let m = s.match(/@\s*encoding\:\s*\[0x([\w]{2}),0x([\w]{2}),0x([\w]{2}),0x([\w]{2})\]/);
if (m)
return ('"' + m[1] + m[2] + m[3] + m[4] + '"').toUpperCase();
else
return null;
}
function codeFromInstruction(s) {
s = s.trim();
var sIdx = s.indexOf(" ");
var name = s.substr(0, sIdx !== -1 ? sIdx : s.length).replace(/\./g, "_");
var operands = s.substr(name.length).trim();
var sops = operands.replace(/(v\d+)\.(\d+)?([a-z]+)\[(\d+)\]/g, "$1.$3$2($4)")
.replace(/([d|s|q]\d+)\[(\d+)\]/g, "$1.at($2)")
.replace(/\[(\w+),\s*[#]?([-]?\w+)\]!/g, "ptr_pre($1, $2)")
.replace(/\[(\w+)\],\s*[#]?([-]?\w+)/g, "ptr_post($1, $2)")
.replace(/\[(\w+),\s*[#]?([-]?\w+)\]/g, "ptr($1, $2)")
.replace(/\[(\w+)\]/g, "ptr($1)")
.replace(/\beq\b/g, "Cond::kEQ")
.replace(/#((?:0x)?[-]?[\dA-Fa-f]+)\b/g, "$1")
.replace(/(lsl|lsr|asr|ror)\s+([r]?\d+)/g, "$1($2)")
.replace(/[{}]/g, "");
if (name === "and")
name = "and_";
return name + "(" + sops + ")";
}
for (let inst of isa.instructions) {
if (inst.encoding !== "A32")
continue;
let category = "gp";
let elements = [""];
const dt1Array = inst.dt.length ? inst.dt : ["any"];
const dt2Array = inst.dt2.length ? inst.dt2 : ["any"];
const dataTypes = [];
for (let dt2 of dt2Array) {
for (let dt1 of dt1Array) {
if (dt2 !== "any")
dataTypes.push(`.${dt1}.${dt2}`);
else if (dt1 !== "any")
dataTypes.push(`.${dt1}`);
else
dataTypes.push(``);
}
}
for (let dataType of dataTypes) {
let instruction = inst.name + dataType;
let ops = [];
let proceed = true;
for (let i = 0; i < inst.operands.length; i++) {
const operand = inst.operands[i];
let regId = i + 1;
if (/^(ldaexd|ldrd|ldrexd|strd)$/.test(inst.name)) {
regId--;
}
else if (/^(stlexd|strexd)$/.test(inst.name)) {
if (i == 0)
regId = 7;
else
regId = i - 1;
}
switch (operand.type) {
case "reg": {
if (operand.regType === "r" && operand.shiftOp) {
ops.push(makeShiftOp(operand.shiftOp, `r${i + 1}`));
}
else if (operand.regType === "r") {
ops.push(`r${regId}`);
}
else if (operand.regType === "s") {
ops.push(`s${regId}`);
category = "vec";
}
else if (operand.regType === "d") {
ops.push(`d${regId}`);
category = "vec";
}
else if (operand.regType === "v") {
ops.push(`q${regId}`);
category = "vec";
}
else {
proceed = false;
}
if (operand.element) {
ops[ops.length - 1] += "[@element@]";
switch (dataType) {
case ".8":
case ".s8":
case ".u8":
elements = [0, 1, 2, 3, 4, 5, 6, 7];
break;
case ".16":
case ".s16":
case ".u16":
case ".f16":
if (inst.name === "vcmla" || inst.name === "vfmsl" || inst.name === "vfmal")
elements = [0, 1];
else
elements = [0, 1, 2, 3];
break;
case ".32":
case ".s32":
case ".u32":
case ".f32":
if (inst.name === "vcmla")
elements = [0];
else
elements = [0, 1];
break;
case ".64":
case ".s64":
case ".u64":
case ".f64":
elements = [0, 1];
break;
case ".bf16":
if (inst.name === "vdot")
elements = [0, 1];
else
elements = [0, 1, 2, 3];
break;
}
}
break;
}
case "imm": {
if (inst.name === "vcadd")
ops.push(["#90", "#270"]);
else if (inst.name === "vcmla")
ops.push(["#0", "#90", "#180", "#270"]);
else if (operand.shiftOp)
ops.push(makeShiftOp(operand.shiftOp, "#8"));
else if (operand.imm === "zero")
ops.push("#0");
else if (operand.imm === "immA")
ops.push(["#0xFF", "0xFF00", "0xFF000000", "0xF000000F"]);
else if (operand.imm === "immZ")
ops.push("#1");
else if (operand.imm === "immV" && instruction.endsWith("16"))
ops.push(["#0x1F", "#0x80", "#0xFF", "0x1F00", "0xFF00"]);
else if (operand.imm === "immV" && instruction.endsWith("32"))
ops.push(["#0x1F", "#0x80", "#0xFF", "0x1F00", "0xFF00", "0x1F0000", "0xFF0000", "0xFF000000", "0xFFFF0000", "0x0000FFFF", "0xFFFFFF00", "0x00FFFFFF", "0xC0F00000"]);
else if (operand.imm === "immV" && instruction.endsWith("64"))
ops.push(["#00FFFF0000000000", "#0xFF00FF0000000000", "#0x00000000FFFFFFFF"]);
else if (operand.imm === "lsb")
ops.push("#3");
else if (operand.imm === "width")
ops.push("#5");
else if (operand.imm === "sat")
ops.push("#8");
else if (operand.imm === "imm")
ops.push("#0");
else if (operand.imm === "n")
ops.push("#3");
else
proceed = false;
break;
}
case "mem": {
const combinations = [];
var off = "4";
if (operand.offset && operand.offset.exp) {
const e = operand.offset.exp;
if (e.op === "==") {
off = e.right.toString();
if (off.indexOf("sz") !== -1 && dataType) {
const sz = dataType.match(/(\d+)/);
off = eval(off.replace("sz", parseInt(lsbFromMul(sz[1] / 8))));
}
}
}
if (operand.memModes.offset) {
if (operand.index)
combinations.push(`[r${regId}, r${regId + 1}]`);
else if (operand.offset)
combinations.push(`[r${regId}, #${off}]`);
else
combinations.push(`[r${regId}]`);
}
if (operand.memModes.preIndex) {
if (operand.index)
combinations.push(`[r${regId}, r${regId + 1}]!`);
else if (operand.offset)
combinations.push(`[r${regId}, #${off}]!`);
}
if (operand.memModes.postIndex) {
if (operand.index)
combinations.push(`[r${regId}], r${regId + 1}`);
else if (operand.offset)
combinations.push(`[r${regId}], #${off}`);
}
ops.push(combinations);
break;
}
default: {
proceed = false;
break;
}
}
}
if (!proceed) {
console.log(`IGNORING: ${inst.name}`);
continue;
}
const opsArray = flatten(ops);
const args = [
"--arch=arm",
"-mattr=+v8.5a",
"-mattr=+aes",
"-mattr=+bf16",
"-mattr=+crc",
"-mattr=+crypto",
"-mattr=+hwdiv-arm",
"-mattr=+fp16fml",
"-mattr=+fullfp16",
"-mattr=+i8mm",
"-mattr=+mp",
"-mattr=+trustzone",
"-mattr=+virtualization",
"-show-encoding",
"-output-asm-variant=0"
];
for (let element of elements) {
for (let opsRow of opsArray) {
let line = instruction;
opsRow = opsRow.map(function(s) { return s.replace("@element@", element) });
let o = opsRow.slice();
switch (inst.name) {
case "pop":
case "push": {
o[0] = "{" + o[0];
o[o.length - 1] += "}";
break;
}
case "vcls": {
line = line.replace(".u", ".s");
break;
}
case "vtbx":
case "vtbl": {
o[1] = "{" + o[1];
o[o.length - 2] += "}";
break;
}
case "rfe":
case "rfeda":
case "rfedb":
case "rfeib": {
o[0] = o[0].replace(/[\[\]]/g, "");
break;
}
case "vld1r":
case "vld2r":
case "vld3r":
case "vld4r": {
line = line.replace("vld1r", "vld1")
.replace("vld2r", "vld2")
.replace("vld3r", "vld3")
.replace("vld4r", "vld4");
for (let i = 0; i < o.length - 1; i++)
o[i] += "[]";
// fallthrough;
}
case "vld1":
case "vld2":
case "vld3":
case "vld4":
case "vst1":
case "vst2":
case "vst3":
case "vst4": {
o[o.length - 1] = o[o.length - 1].replace(/,\s*#\d+$/g, "!");
o[0] = "{" + o[0];
o[o.length - 2] += "}";
break;
}
}
try {
const out = child_process.execFileSync(LLVM_MC, args, {
input: line + " " + o.join(", "),
encoding: "utf-8"
});
const encoding = encodingFromText(out);
const code = codeFromInstruction(instruction + " " + opsRow.join(", "));
tests[category].push(`TEST_INSTRUCTION(${encoding}, ${code});`);
}
catch (ex) {
console.log(`FATAL: ${line} ${opsRow.join(", ")}`);
}
}
}
}
}
console.log("static void ASMJIT_NOINLINE testA32AssemblerGp(AssemblerTester<a32::Assembler>& tester) noexcept {");
console.log(" using namespace a32;");
console.log("");
for (let test of tests.gp) {
console.log(" " + test);
}
console.log("}");
console.log("");
console.log("static void ASMJIT_NOINLINE testA32AssemblerVec(AssemblerTester<a32::Assembler>& tester) noexcept {");
console.log(" using namespace a32;");
console.log("");
for (let test of tests.vec)
console.log(" " + test);
console.log("}");
}
}
new TestsGenerator(isa).generate();