import argparse
import json
import os
import re
from pathlib import Path
from docenizer_arm64 import collect_instruction_docs
SCRIPT_DIR = Path(__file__).resolve().parent
REPO_ROOT = SCRIPT_DIR.parent
INPUT_PATH = SCRIPT_DIR / "arm64.txt"
ASMJIT_ISA_PATH = SCRIPT_DIR / "asmjit" / "db" / "isa_aarch64.json"
A64_ROWS_PATH = SCRIPT_DIR / "a64_rows.json"
DEFAULT_OUTPUT = REPO_ROOT / "src" / "aarch64" / "emitter.rs"
FEATURE_BEGIN = "// @generated AArch64 target features begin"
FEATURE_END = "// @generated AArch64 target features end"
DOCS_INPUT = os.environ.get("ASMKIT_ARM64_DOCS", "asm-docs-arm")
CC_VARIANTS = ["eq", "ne", "cs", "hs", "cc", "lo", "mi", "pl", "vs", "vc", "hi", "ls", "ge", "lt", "gt", "le", "al"]
RUST_KEYWORDS = {"yield"}
HEADER = """//! AArch64 emitter traits generated by `meta/arm64.py` from `meta/arm64.txt`
//! and AsmJit's pinned `db/isa_aarch64.json`. Do not edit by hand; regenerate
//! instead.
//!
//! Each trait represents one mnemonic and operand arity. `Assembler` forwards
//! every implementation to `emit_n`; the Rust parameter names describe the
//! corresponding AArch64 assembly operand where that is known.
#![allow(non_snake_case, non_camel_case_types)]
use super::{assembler::*, instdb::*, operands::*};
use crate::core::globals::CondCode;
use crate::core::operand::*;
"""
class Opcode:
def __init__(self, name, inst_type):
self.name = name
self.variants = []
self.inst_type = inst_type
def parse_opcodes(path):
opcodes = {}
inst_type = ""
with open(path, encoding="utf-8") as source:
for raw_line in source:
line = raw_line.strip()
if not line:
continue
if line.startswith("ASMJIT_INST_"):
inst_type = line[len("ASMJIT_INST_"):]
if "(" not in line:
continue
elems = [elem.strip() for elem in line.split("(", 1)[1].split(")", 1)[0].split(",")]
if len(elems) < 2:
continue
name, inst_id, operands = elems[0], elems[1], elems[2:]
if name not in opcodes:
opcodes[name] = Opcode(name, inst_type)
opcodes[name].variants.append((inst_id, operands))
elif len(operands) != len(opcodes[name].variants[0][1]):
split_name = f"{name}_{len(operands)}"
opcodes.setdefault(split_name, Opcode(split_name, inst_type)).variants.append((inst_id, operands))
else:
opcodes[name].variants.append((inst_id, operands))
return opcodes
def load_opcode_docs(inputfolder):
if not inputfolder or not Path(inputfolder).is_dir():
return {}
try:
return collect_instruction_docs(inputfolder)
except Exception as exc:
print(f"Warning: failed to load ARM64 docs from {inputfolder}: {exc}")
return {}
def load_isa_forms(path):
with open(path, encoding="utf-8") as source:
data = json.load(source)
forms = {}
for category in data["instructions"]:
for record in category["data"]:
form = record["inst"]
if re.search(r"\bZ[A-Za-z]|\bP[gdnm](?:[./,\s]|$)", form):
continue
mnemonic, _, _ = form.partition(" ")
for name in mnemonic.split("|"):
name = name.replace(".<cond>", "").lower()
forms.setdefault(name, []).append(form)
return forms
def rust_feature_name(name):
return "".join(part.title() for part in name.lower().split("_"))
def opcode_mask_value(pattern):
register_fields = {
"Ra", "Rd", "Rd2", "Rm", "Rn", "Rs", "Rs2", "Rt", "Rt2",
"Va", "Vd", "Vd2", "Vm", "Vn", "Vs", "Vs2", "Vx",
}
field_widths = {"cond": 4, "nzcv": 4, "cmode": 4, "sz": 1,
"W": 1, "W1": 1, "s": 1, "sop": 2}
mask = value = 0
remaining = 32
for raw_field in pattern.split("|"):
field = raw_field.replace(" ", "")
fixed = re.fullmatch(r"[01]+", field)
if fixed:
width = len(field)
elif match := re.search(r":(\d+)$", field):
width = int(match.group(1))
elif match := re.search(r"\[(\d+)(?::(\d+))?\]$", field):
high = int(match.group(1))
low = int(match.group(2) or match.group(1))
width = high - low + 1
elif field in register_fields:
width = 5
else:
width = field_widths[field]
remaining -= width
assert remaining >= 0, f"opcode pattern is wider than 32 bits: {pattern}"
if fixed:
field_mask = (1 << width) - 1
mask |= field_mask << remaining
value |= int(field, 2) << remaining
assert remaining == 0, f"opcode pattern is not 32 bits: {pattern}"
return mask, value
def feature_operand_signature(operand):
if operand.startswith("["):
return "OperandType::Mem as u32"
if operand.startswith("#") or operand == "PC":
return "OperandType::Imm as u32"
operand = operand.lstrip("{").split("|", 1)[0]
if match := re.match(r"^([WX])", operand):
reg_type = {"W": "Gp32", "X": "Gp64"}[match.group(1)]
return f"feature_reg_signature(RegType::{reg_type}, VecElementType::None, false)"
if match := re.match(r"^([BHSDQ])", operand):
reg_type = {
"B": "Vec8", "H": "Vec16", "S": "Vec32",
"D": "Vec64", "Q": "Vec128",
}[match.group(1)]
return f"feature_reg_signature(RegType::{reg_type}, VecElementType::None, false)"
if match := re.match(r"^V[a-z]+\.(\d+)([BHSD])", operand):
lanes, element = int(match.group(1)), match.group(2)
total_bits = lanes * {"B": 8, "H": 16, "S": 32, "D": 64}[element]
reg_type = {32: "Vec32", 64: "Vec64", 128: "Vec128"}[total_bits]
return f"feature_reg_signature(RegType::{reg_type}, VecElementType::{element}, false)"
if match := re.match(r"^V[a-z]+\.([BHSD])\[#", operand):
return (
"feature_reg_signature(RegType::Vec128, "
f"VecElementType::{match.group(1)}, true)"
)
raise AssertionError(f"unsupported feature-form operand: {operand}")
def generate_target_features(isa_path, rows_path):
with open(isa_path, encoding="utf-8") as source:
isa = json.load(source)
with open(rows_path, encoding="utf-8") as source:
rows = json.load(source)["rows"]
by_mnemonic = {}
for category in isa["instructions"]:
categories = set(category["category"].split())
if categories & {"SVE", "SME"}:
continue
required = set()
if "ASIMD" in categories:
required.add("ASIMD")
if category.get("ext"):
required.add(category["ext"])
for record in category["data"]:
mnemonic = record["inst"].partition(" ")[0].lower()
for name in mnemonic.split("|"):
by_mnemonic.setdefault(("ASIMD" in categories, name), []).append(
(frozenset(required), record)
)
requirements = []
base_requirements = []
form_overrides = []
form_offsets = [0]
mixed_count = 0
for row in rows:
inst_id = row["id"]
vector = inst_id.endswith("_v")
mnemonic = inst_id[:-2].lower() if vector else inst_id.lower()
records = by_mnemonic.get((vector, mnemonic), ())
record_requirements = [set(required) for required, _ in records]
required = set().union(*record_requirements) if record_requirements else set()
base_required = (
set.intersection(*record_requirements) if record_requirements else set()
)
requirements.append(sorted(required))
base_requirements.append(sorted(base_required))
mixed = len({required for required, _ in records}) > 1
mixed_count += mixed
if not mixed:
form_offsets.append(len(form_overrides))
continue
parsed_records = []
exact_forms = {}
for form_required, record in records:
mask, value = opcode_mask_value(record["op"])
signatures = [feature_operand_signature(op) for op in form_operands(record["inst"])]
assert len(signatures) <= 6, f"too many operands in feature form: {record['inst']}"
signatures.extend(["0"] * (6 - len(signatures)))
key = mask, value, tuple(signatures)
previous = exact_forms.setdefault(key, form_required)
assert previous == form_required, (
f"conflicting requirements for {inst_id} form {record['inst']}: "
f"{sorted(previous)} vs {sorted(form_required)}"
)
parsed_records.append((form_required, record, mask, value, signatures))
overrides = []
for form_required, record, mask, value, signatures in parsed_records:
if set(form_required) == base_required:
continue
assert base_required < set(form_required)
overrides.append((mask, value, signatures, sorted(form_required), record["inst"]))
overrides.sort(key=lambda override: override[0].bit_count(), reverse=True)
form_overrides.extend(overrides)
form_offsets.append(len(form_overrides))
assert len(form_overrides) <= 0xFFFF, "AArch64 form-offset table no longer fits in u16"
features = sorted({feature for required in requirements for feature in required})
feature_index = {feature: index for index, feature in enumerate(features)}
assert len(features) <= 64, "AArch64 feature mask no longer fits in u64"
representatives = [
next(index for index, required in enumerate(requirements) if feature in required)
for feature in features
]
lines = [
FEATURE_BEGIN,
"/// AArch64 architectural features present in the pinned AsmJit ISA metadata.",
"#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]",
"#[repr(u8)]",
"pub enum CpuFeature {",
]
lines.extend(f" {rust_feature_name(feature)}," for feature in features)
lines.extend([
"}",
"",
f"pub const CPU_FEATURE_COUNT: usize = {len(features)};",
"pub const CPU_FEATURE_NAMES: [&str; CPU_FEATURE_COUNT] = [",
])
lines.extend(f' "{feature}",' for feature in features)
lines.extend([
"];",
"",
"pub const ALL_CPU_FEATURES: [CpuFeature; CPU_FEATURE_COUNT] = [",
])
lines.extend(f" CpuFeature::{rust_feature_name(feature)}," for feature in features)
lines.extend([
"];",
"",
"impl CpuFeature {",
" pub const fn name(self) -> &'static str {",
" CPU_FEATURE_NAMES[self as usize]",
" }",
"}",
"",
"const FEATURE_FORM_SIGNATURE_MASK: u32 = OperandSignature::OP_TYPE_MASK",
" | OperandSignature::REG_TYPE_MASK",
" | Vec::SIGNATURE_REG_ELEMENT_TYPE_MASK",
" | Vec::SIGNATURE_REG_ELEMENT_FLAG_MASK;",
"",
"const fn feature_reg_signature(",
" reg_type: RegType,",
" element_type: VecElementType,",
" element_access: bool,",
") -> u32 {",
" OperandType::Reg as u32",
" | (reg_type as u32) << OperandSignature::REG_TYPE_SHIFT",
" | (element_type as u32) << Vec::SIGNATURE_REG_ELEMENT_TYPE_SHIFT",
" | (element_access as u32) << Vec::SIGNATURE_REG_ELEMENT_FLAG_SHIFT",
"}",
"",
"struct InstFeatureForm {",
" opcode_mask: u32,",
" opcode_value: u32,",
" operand_signatures: [u32; 6],",
" required: u64,",
" context: &'static str,",
"}",
"",
"impl InstFeatureForm {",
" fn matches(&self, opcode: u32, ops: &[&Operand]) -> bool {",
" if opcode & self.opcode_mask != self.opcode_value {",
" return false;",
" }",
" self.operand_signatures.iter().enumerate().all(|(index, expected)| {",
" let actual = ops.get(index).map_or(0, |op| op.signature.bits());",
" actual & FEATURE_FORM_SIGNATURE_MASK == *expected",
" })",
" }",
"}",
"",
"/// Conservative required-feature masks, indexed by `InstId as usize`.",
"pub static INST_FEATURE_MASKS: [u64; InstId::_Count as usize] = [",
])
for row, required in zip(rows, requirements):
mask = sum(1 << feature_index[feature] for feature in required)
lines.append(f" 0x{mask:016x}, // {row['id']}")
lines.extend([
"];",
"",
"/// Requirements common to every form, indexed by `InstId as usize`.",
"static INST_BASE_FEATURE_MASKS: [u64; InstId::_Count as usize] = [",
])
for row, required in zip(rows, base_requirements):
mask = sum(1 << feature_index[feature] for feature in required)
lines.append(f" 0x{mask:016x}, // {row['id']}")
lines.extend([
"];",
"",
"static INST_BASE_FEATURE_CONTEXT: [&str; InstId::_Count as usize] = [",
])
for row, required in zip(rows, base_requirements):
context = (
f"{row['id'].removesuffix('_v').lower()} requires: {', '.join(required)}"
if required
else ""
)
lines.append(f" {json.dumps(context)},")
lines.extend([
"];",
"",
"static INST_FEATURE_FORM_OFFSETS: [u16; InstId::_Count as usize + 1] = [",
])
lines.extend(f" {offset}," for offset in form_offsets)
lines.extend([
"];",
"",
f"static INST_FEATURE_FORMS: [InstFeatureForm; {len(form_overrides)}] = [",
])
for mask, value, signatures, required, form in form_overrides:
required_mask = sum(1 << feature_index[feature] for feature in required)
context = f"{form} requires: {', '.join(required)}"
lines.append(" InstFeatureForm {")
lines.append(f" opcode_mask: 0x{mask:08x},")
lines.append(f" opcode_value: 0x{value:08x},")
lines.append(f" operand_signatures: [{', '.join(signatures)}],")
lines.append(f" required: 0x{required_mask:016x},")
lines.append(f" context: {json.dumps(context)},")
lines.append(" },")
lines.extend([
"];",
"",
"fn required_features_for_form(",
" inst_id: usize,",
" opcode: u32,",
" ops: &[&Operand],",
") -> (u64, &'static str) {",
" let start = INST_FEATURE_FORM_OFFSETS[inst_id] as usize;",
" let end = INST_FEATURE_FORM_OFFSETS[inst_id + 1] as usize;",
" for form in &INST_FEATURE_FORMS[start..end] {",
" if form.matches(opcode, ops) {",
" return (form.required, form.context);",
" }",
" }",
" (INST_BASE_FEATURE_MASKS[inst_id], INST_BASE_FEATURE_CONTEXT[inst_id])",
"}",
"",
"/// One instruction carrying each represented feature.",
"pub static CPU_FEATURE_REPRESENTATIVE: [InstId; CPU_FEATURE_COUNT] = [",
])
lines.extend(f" InstId::{rows[index]['id']}," for index in representatives)
lines.extend(["];", FEATURE_END, ""])
return (
"\n".join(lines),
len(features),
sum(bool(required) for required in requirements),
mixed_count,
len(form_overrides),
)
def update_generated_section(path, generated):
text = path.read_text(encoding="utf-8")
if FEATURE_BEGIN in text:
start = text.index(FEATURE_BEGIN)
end = text.index(FEATURE_END, start) + len(FEATURE_END)
text = text[:start] + generated.rstrip() + text[end:]
else:
text = text.rstrip() + "\n\n" + generated
path.write_text(text, encoding="utf-8")
def trait_name(name):
camel_case_name = "".join(word.capitalize() for word in name.split("_"))
if name == "mvn_":
return "Mvn_"
if name.startswith("mvn__"):
return f"Mvn_{name[len('mvn__'):]}"
return camel_case_name
def rust_method_name(name):
return f"r#{name}" if name in RUST_KEYWORDS else name
def canonical_name(name):
return re.sub(r"(_\d+|_)+$", "", name).lower()
def split_operands(text):
parts, start, depth = [], 0, 0
for index, char in enumerate(text):
if char in "[{(":
depth += 1
elif char in "]})":
depth -= 1
elif char == "," and not depth:
parts.append(text[start:index].strip())
start = index + 1
tail = text[start:].strip()
return parts + ([tail] if tail else [])
def form_operands(form):
_, _, operands = form.partition(" ")
return split_operands(operands)
def parameter_name(operand, index):
if operand.startswith("["):
return "addr"
register = re.match(r"[BHDQRSVWXZ][a-z]*([dnmst])([0-9]*)", operand)
if register:
return f"r{register.group(1)}{register.group(2)}"
immediate = re.search(r"#([A-Za-z][A-Za-z0-9_]*)", operand)
if immediate:
name = immediate.group(1).lower()
if name.startswith("rel"):
return "target"
if re.fullmatch(r"imm[a-z]*", name):
return "imm"
if re.fullmatch(r"op[0-9]+", name):
return f"system_{name}"
return {"cond": "condition", "nzcv": "flags", "n": "shift"}.get(name, name)
if operand.startswith("{"):
return "modifier"
if operand == "PC":
return "pc"
return f"operand_{index + 1}"
def parameter_names(opcode, forms):
arity = len(opcode.variants[0][1])
candidates = forms.get(canonical_name(opcode.name), [])
selected = [form for form in candidates if len(form_operands(form)) >= arity]
names = []
for index, operand_type in enumerate(opcode.variants[0][1]):
alternatives = []
for form in selected:
operands = form_operands(form)
if index >= len(operands):
continue
name = parameter_name(operands[index], index)
if name not in alternatives:
alternatives.append(name)
if alternatives:
names.append("_or_".join(alternatives))
elif operand_type in {"Label", "Sym"}:
names.append("target")
elif operand_type == "Mem":
names.append("addr")
elif operand_type == "Imm":
names.append("imm" if index == 0 else f"imm{index + 1}")
elif index == 0:
names.append("rd")
elif index == 1:
names.append("rn")
else:
names.append(f"rm{index - 1}")
used = set()
for index, name in enumerate(names):
if name in used:
suffix = 2
while f"{name}_{suffix}" in used:
suffix += 1
names[index] = f"{name}_{suffix}"
used.add(names[index])
return names, candidates
def doc_lines(opcode, forms, opcode_docs, indent=""):
canonical = canonical_name(opcode.name)
display = opcode.name.upper()
names, matching = parameter_names(opcode, forms)
lines = [f"{indent}/// Emits the `{display}` instruction."]
if matching:
rendered = "; ".join(f"`{form.upper()}`" for form in matching[:4])
suffix = " (and related forms)." if len(matching) > 4 else "."
lines.append(f"{indent}/// Assembly forms: {rendered}{suffix}")
if names:
lines.append(f"{indent}/// Operands: " + ", ".join(f"`{name}`" for name in names) + ".")
doc = opcode_docs.get(canonical.upper())
if doc:
tooltip = " ".join(doc["tooltip"].split())
lines.append(f"{indent}/// {tooltip}")
if doc.get("url"):
lines.append(f"{indent}/// Reference: [Arm documentation]({doc['url']})")
return lines
def params(types, names):
return ", ".join(f"{name}: {typ}" for name, typ in zip(names, types))
def generate(opcodes, forms, opcode_docs):
chunks = [HEADER]
for opcode in opcodes.values():
names, _ = parameter_names(opcode, forms)
generic_types = [f"T{index}" for index in range(len(names))]
generics = f"<{', '.join(generic_types)}>" if generic_types else ""
method = rust_method_name(opcode.name)
chunks.extend(doc_lines(opcode, forms, opcode_docs))
chunks.append(f"pub trait {trait_name(opcode.name)}Emitter{generics} {{")
signature = params(generic_types, names)
if opcode.inst_type.startswith("1cc("):
chunks.append(f" fn {method}(&mut self{', ' if signature else ''}{signature});")
for cc in CC_VARIANTS:
chunks.append(f" fn {method}_{cc}(&mut self{', ' if signature else ''}{signature});")
else:
chunks.append(f" fn {method}(&mut self{', ' if signature else ''}{signature});")
chunks.append("}\n")
for opcode in opcodes.values():
names, _ = parameter_names(opcode, forms)
method = rust_method_name(opcode.name)
for inst_id, operand_types in opcode.variants:
type_args = f"<{', '.join(operand_types)}>" if operand_types else ""
signature = params(operand_types, names)
operands = ", ".join(f"{name}.as_operand()" for name in names)
chunks.append(f"impl {trait_name(opcode.name)}Emitter{type_args} for Assembler<'_> {{")
if opcode.inst_type.startswith("1cc("):
chunks.append(f" fn {method}(&mut self{', ' if signature else ''}{signature}) {{")
chunks.append(f" self.emit_n(InstId::{inst_id}, &[{operands}]);")
chunks.append(" }")
for cc in CC_VARIANTS:
chunks.append(f" fn {method}_{cc}(&mut self{', ' if signature else ''}{signature}) {{")
chunks.append(f" self.emit_n(InstId::{inst_id}.with_cc(CondCode::{cc.upper()}), &[{operands}]);")
chunks.append(" }")
else:
chunks.append(f" fn {method}(&mut self{', ' if signature else ''}{signature}) {{")
chunks.append(f" self.emit_n(InstId::{inst_id}, &[{operands}]);")
chunks.append(" }")
chunks.append("}\n")
chunks.append("impl Assembler<'_> {")
for opcode in opcodes.values():
names, _ = parameter_names(opcode, forms)
generic_types = [f"T{index}" for index in range(len(names))]
generics = f"<{', '.join(generic_types)}>" if generic_types else ""
signature = params(generic_types, names)
method = rust_method_name(opcode.name)
trait = f"{trait_name(opcode.name)}Emitter{generics}"
if opcode.inst_type.startswith("1cc("):
method_names = [method] + [f"{method}_{cc}" for cc in CC_VARIANTS]
else:
method_names = [method]
for inherent_method in method_names:
chunks.extend(doc_lines(opcode, forms, opcode_docs, indent=" "))
chunks.append(f" pub fn {inherent_method}{generics}(&mut self{', ' if signature else ''}{signature})")
chunks.append(f" where Self: {trait},")
chunks.append(" {")
chunks.append(f" <Self as {trait}>::{inherent_method}(self{', ' if names else ''}{', '.join(names)});")
chunks.append(" }")
chunks.append("}")
return "\n".join(chunks) + "\n"
def main():
parser = argparse.ArgumentParser(description="Generates src/aarch64/emitter.rs")
parser.add_argument("output", nargs="?", default=DEFAULT_OUTPUT, help="path of the generated Rust file")
parser.add_argument("--docs-inputfolder", default=DOCS_INPUT,
help="optional Arm XML docs directory (env: ASMKIT_ARM64_DOCS)")
parser.add_argument("--no-docs", action="store_true", help="skip optional Arm XML docs")
parser.add_argument("--check", action="store_true", help="print generation validation")
parser.add_argument("--features-output", type=Path,
help="update the generated target-feature section in this Rust file")
args = parser.parse_args()
opcodes = parse_opcodes(INPUT_PATH)
forms = load_isa_forms(ASMJIT_ISA_PATH)
docs = {} if args.no_docs else load_opcode_docs(args.docs_inputfolder)
text = generate(opcodes, forms, docs)
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(text, encoding="utf-8")
if args.features_output:
feature_text, feature_count, gated_count, mixed_count, form_count = generate_target_features(
ASMJIT_ISA_PATH, A64_ROWS_PATH)
update_generated_section(args.features_output, feature_text)
print(
f"Updated {args.features_output} ({feature_count} features, "
f"{gated_count} gated InstIds, {mixed_count} mixed InstIds, "
f"{form_count} form overrides)"
)
if args.check:
documented = sum(bool(forms.get(canonical_name(opcode.name))) for opcode in opcodes.values())
print(f"Generated {len(opcodes)} traits and {documented}/{len(opcodes)} ISA-documented mnemonics")
assert not re.search(r"\\bop[0-9]+\\s*:", text)
assert "isa_aarch64.json" in text
print(f"Wrote {output} ({len(text.splitlines())} lines)")
if __name__ == "__main__":
main()