import collections
import glob
import logging
import os
import re
import sys
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.environ.setdefault("RISCV_OPCODES", os.path.join(REPO_ROOT, "riscv-opcodes"))
os.environ.setdefault("RISCV_UNIFIED_DB", os.path.join(REPO_ROOT, "riscv-unified-db"))
from constants import *
from docenizer_riscv import load_inst_docs
logging.basicConfig(level=logging.INFO, format="%(levelname)s:: %(message)s")
SRC_RISCV = os.path.join(REPO_ROOT, "src", "riscv")
GENERATED_MARKER = "/* Automatically generated by parse_opcodes */"
EXCLUDED_EXTENSIONS = {
"rv32_zilsd",
}
RV64_ONLY_NAMES = {"ssamoswap_d"}
UNSUPPORTED_TYPED_ENCODINGS = {
"CRlistCSpimm",
"CSreg1CSreg2",
"MopRT30MopRT2726MopRT2120RdRs1",
"MopRrT30MopRrT2726RdRs1Rs2",
"RdRs1N0",
}
VECTOR_FP_SCALAR_RS1 = {
"vfadd_vf", "vfdiv_vf", "vfmacc_vf", "vfmadd_vf", "vfmax_vf",
"vfmerge_vfm", "vfmin_vf", "vfmsac_vf", "vfmsub_vf", "vfmul_vf",
"vfnmacc_vf", "vfnmadd_vf", "vfnmsac_vf", "vfnmsub_vf", "vfrdiv_vf",
"vfrsub_vf", "vfsgnj_vf", "vfsgnjn_vf", "vfsgnjx_vf",
"vfslide1down_vf", "vfslide1up_vf", "vfsub_vf", "vfwadd_vf",
"vfwadd_wf", "vfwmacc_vf", "vfwmaccbf16_vf", "vfwmsac_vf",
"vfwmul_vf", "vfwnmacc_vf", "vfwnmsac_vf", "vfwsub_vf", "vfwsub_wf",
"vmfeq_vf", "vmfge_vf", "vmfgt_vf", "vmfle_vf", "vmflt_vf", "vmfne_vf",
"vfmv_v_f",
}
FP_SPECIAL_GP_FIELDS = {
"fcvtmod_w_d": {"rd"},
"fmv_d_x": {"rs1"},
"fmv_h_x": {"rs1"},
"fmv_s_x": {"rs1"},
"fmv_w_x": {"rs1"},
"fmv_x_d": {"rd"},
"fmv_x_h": {"rd"},
"fmv_x_s": {"rd"},
"fmv_x_w": {"rd"},
"fmvh_x_d": {"rd"},
"fmvh_x_q": {"rd"},
"fmvp_d_x": {"rs1", "rs2"},
"fmvp_q_x": {"rs1", "rs2"},
}
OPERAND_ORDER_BY_ENCODING = {
"CImm12": ["c_imm12"],
"CIndex": ["c_index"],
"CMopT": ["c_mop_t"],
"CRlistCSpimm": ["c_rlist", "c_spimm"],
"CRs1N0": ["c_rs1_n0"],
"CRs2CUimm8spS": ["c_rs2", "c_uimm8sp_s"],
"CRs2CUimm9spS": ["c_rs2", "c_uimm9sp_s"],
"CSreg1CSreg2": ["c_sreg1", "c_sreg2"],
"CsrZimm5": ["csr", "zimm5"],
"Empty": [],
"FmPredSuccRs1Rd": ["fm", "pred", "succ", "rs1", "rd"],
"Imm20": ["imm20"],
"Jimm20": ["jimm20"],
"MopRT30MopRT2726MopRT2120RdRs1": ["mop_r_t_30", "mop_r_t_27_26", "mop_r_t_21_20", "rd", "rs1"],
"MopRrT30MopRrT2726RdRs1Rs2": ["mop_rr_t_30", "mop_rr_t_27_26", "rd", "rs1", "rs2"],
"Rd": ["rd"],
"RdCsr": ["rd", "csr"],
"RdCsrZimm5": ["rd", "csr", "zimm5"],
"RdImm20": ["rd", "imm20"],
"RdJimm20": ["rd", "jimm20"],
"RdN0": ["rd_n0"],
"RdN0CRs2N0": ["rd_n0", "c_rs2_n0"],
"RdPCNzuimm10": ["rd_p", "c_nzuimm10"],
"RdPRs1PCUimm1": ["rd_p", "rs1_p", "c_uimm1"],
"RdPRs1PCUimm2": ["rd_p", "rs1_p", "c_uimm2"],
"RdRs1": ["rd", "rs1"],
"RdRs1AqRl": ["rd", "rs1", "aq", "rl"],
"RdRs1Csr": ["rd", "rs1", "csr"],
"RdRs1Imm12": ["rd", "rs1", "imm12"],
"RdRs1N0": ["rd_rs1_n0"],
"RdRs1N0CRs2N0": ["rd_rs1_n0", "c_rs2_n0"],
"RdRs1P": ["rd_rs1_p"],
"RdRs1PCNzuimm5": ["rd_rs1_p", "c_nzuimm5"],
"RdRs1PRs2P": ["rd_rs1_p", "rs2_p"],
"RdRs1Rm": ["rd", "rs1", "rm"],
"RdRs1Rnum": ["rd", "rs1", "rnum"],
"RdRs1Rs2": ["rd", "rs1", "rs2"],
"RdRs1Rs2AqRl": ["rd", "rs1", "rs2", "aq", "rl"],
"RdRs1Rs2Bs": ["rd", "rs1", "rs2", "bs"],
"RdRs1Rs2EqRs1": ["rd", "rs1", "rs2_eq_rs1"],
"RdRs1Rs2Rm": ["rd", "rs1", "rs2", "rm"],
"RdRs1Rs2Rs3Rm": ["rd", "rs1", "rs2", "rs3", "rm"],
"RdRs1Shamtd": ["rd", "rs1", "shamtd"],
"RdRs1Shamtw": ["rd", "rs1", "shamtw"],
"RdRs2": ["rd", "rs2"],
"RdZimm5": ["rd", "zimm5"],
"Rs1": ["rs1"],
"Rs1Csr": ["rs1", "csr"],
"Rs1N0": ["rs1_n0"],
"Rs1Rs2": ["rs1", "rs2"],
"Bimm12HiRs1Bimm12lo": ["rs1", "bimm12lohi"],
"Bimm12HiRs1Rs2Bimm12lo": ["rs1", "rs2", "bimm12lohi"],
"Bimm12HiRs2Bimm12lo": ["rs2", "bimm12lohi"],
"Bimm12HiRs2Rs1Bimm12lo": ["rs1", "rs2", "bimm12lohi"],
"CNzimm10hiCNzimm10lo": ["c_nzimm10lohi"],
"CNzimm6hiCNzimm6lo": ["c_nzimm6lohi"],
"Imm12HiRs1Rs2Imm12lo": ["rs1", "rs2", "imm12lohi"],
"Imm12Rs1Rd": ["rs1", "rd", "imm12"],
"NfVmRs1Vd": ["vd", "rs1", "vm", "nf"],
"NfVmRs1Vs3": ["vs3", "rs1", "vm", "nf"],
"NfVmRs2Rs1Vd": ["vd", "rs1", "rs2", "vm", "nf"],
"NfVmRs2Rs1Vs3": ["vs3", "rs1", "rs2", "vm", "nf"],
"NfVmVs2Rs1Vd": ["vd", "rs1", "vs2", "vm", "nf"],
"NfVmVs2Rs1Vs3": ["vs3", "rs1", "vs2", "vm", "nf"],
"RdCUimm8sphiCUimm8splo": ["rd", "c_uimm8splohi"],
"RdCUimm9sphiCUimm9splo": ["rd", "c_uimm9splohi"],
"RdN0CImm6loCImm6hi": ["rd_n0", "c_imm6lohi"],
"RdN0CUimm8sphiCUimm8splo": ["rd_n0", "c_uimm8splohi"],
"RdN0CUimm9sphiCUimm9splo": ["rd_n0", "c_uimm9splohi"],
"RdN2CNzimm18hiCNzimm18lo": ["rd_n2", "c_nzimm18lohi"],
"RdPRs1PCUimm7loCUimm7hi": ["rd_p", "rs1_p", "c_uimm7lohi"],
"RdPRs1PCUimm8loCUimm8hi": ["rd_p", "rs1_p", "c_uimm8lohi"],
"RdRs1N0CImm6loCImm6hi": ["rd_rs1_n0", "c_imm6lohi"],
"RdRs1N0CNzimm6loCNzimm6hi": ["rd_rs1_n0", "c_nzimm6lohi"],
"RdRs1N0CNzuimm6hiCNzuimm6lo": ["rd_rs1_n0", "c_nzuimm6lohi"],
"RdRs1N0CNzuimm6lo": ["rd_rs1_n0", "c_nzuimm6lohi"],
"RdRs1PCImm6hiCImm6lo": ["rd_rs1_p", "c_imm6lohi"],
"RdRs1PCNzuimm6loCNzuimm6hi": ["rd_rs1_p", "c_nzuimm6lohi"],
"Rs1Imm12hi": ["rs1", "imm12lohi"],
"Rs1PCBimm9loCBimm9hi": ["rs1_p", "c_bimm9lohi"],
"Rs1PRs2PCUimm7loCUimm7hi": ["rs1_p", "rs2_p", "c_uimm7lohi"],
"Rs1PRs2PCUimm8hiCUimm8lo": ["rs1_p", "rs2_p", "c_uimm8lohi"],
"Rs1PRs2PCUimm8loCUimm8hi": ["rs1_p", "rs2_p", "c_uimm8lohi"],
"Rs1Rd": ["rd", "rs1"],
"Rs1Vd": ["vd", "rs1"],
"Rs1Vs3": ["vs3", "rs1"],
"Rs2PRs1PCUimm1": ["rs1_p", "rs2_p", "c_uimm1"],
"Rs2PRs1PCUimm2": ["rs1_p", "rs2_p", "c_uimm2"],
"Rs2Rs1Rd": ["rd", "rs1", "rs2"],
"Simm5Vd": ["vd", "simm5"],
"VmVd": ["vd", "vm"],
"VmVs2Rd": ["rd", "vs2", "vm"],
"VmVs2Rs1Vd": ["vd", "vs2", "rs1", "vm"],
"VmVs2Simm5Vd": ["vd", "vs2", "simm5", "vm"],
"VmVs2Vd": ["vd", "vs2", "vm"],
"VmVs2Vs1Vd": ["vd", "vs1", "vs2", "vm"],
"VmVs2Zimm5Vd": ["vd", "vs2", "zimm5", "vm"],
"Vs1Vd": ["vd", "vs1"],
"Vs2Rd": ["rd", "vs2"],
"Vs2Rs1Vd": ["vd", "rs1", "vs2"],
"Vs2Simm5Vd": ["vd", "vs2", "simm5"],
"Vs2Vd": ["vd", "vs2"],
"Vs2Vs1Vd": ["vd", "vs1", "vs2"],
"Vs2Zimm5Vd": ["vd", "vs2", "zimm5"],
"Zimm10Zimm5Rd": ["rd", "zimm5", "zimm10"],
"Zimm11Rs1Rd": ["rd", "rs1", "zimm11"],
"Zimm6HiVmVs2Zimm6loVd": ["vd", "vs2", "zimm6lohi", "vm"],
}
FIXED_ZERO_FIELDS = {
"fence_i": ["imm12", "rs1", "rd"],
"fence_tso": ["rs1", "rd"],
}
CSR_ACCESS = {
"csrrw": "X", "csrrs": "X", "csrrc": "X",
"csrrwi": "X", "csrrsi": "X", "csrrci": "X",
"csrr": "R", "csrw": "W", "csrs": "X", "csrc": "X",
"csrwi": "W", "csrsi": "X", "csrci": "X",
}
FSR_ACCESS = {
"fsflags": ("FFLAGS", "FFLAGS"),
"fsflagsi": ("FFLAGS", "FFLAGS"),
"frflags": ("FFLAGS", "0"),
"fsrm": ("FRM", "FRM"),
"fsrmi": ("FRM", "FRM"),
"frrm": ("FRM", "0"),
"fscsr": ("FFLAGS | FRM", "FFLAGS | FRM"),
"frcsr": ("FFLAGS | FRM", "0"),
}
FENCE_NAMES = {"fence", "fence_i", "fence_tso", "pause"}
VOLATILE_NAMES = {"ecall", "ebreak", "mret", "sret", "mnret", "dret", "wfi"}
VXSAT_PREFIXES = ("vsadd", "vssub", "vaadd", "vasub", "vsmul", "vnclip")
RA_BIT = 1 << 1
SP_BIT = 1 << 2
V0_BIT = 1 << 32
IMPLICIT_REG_NAMES = {
"c_jal": (0, RA_BIT), "c_jalr": (0, RA_BIT), "c_addi16sp": (SP_BIT, SP_BIT),
"c_addi4spn": (SP_BIT, 0),
"c_lwsp": (SP_BIT, 0), "c_ldsp": (SP_BIT, 0),
"c_flwsp": (SP_BIT, 0), "c_fldsp": (SP_BIT, 0),
"c_swsp": (SP_BIT, 0), "c_sdsp": (SP_BIT, 0),
"c_fswsp": (SP_BIT, 0), "c_fsdsp": (SP_BIT, 0),
"sspush_x1": (1 << 1, 0), "sspush_x5": (1 << 5, 0),
"sspopchk_x1": (1 << 1, 0), "sspopchk_x5": (1 << 5, 0),
"c_sspush_x1": (1 << 1, 0), "c_sspopchk_x5": (1 << 5, 0),
}
FP_LOADS = {"flw", "fld", "flh", "flq"}
FP_STORES = {"fsw", "fsd", "fsh", "fsq"}
C_MEM = {
"c_lw": ("LOAD", 4), "c_lwsp": ("LOAD", 4),
"c_ld": ("LOAD", 8), "c_ldsp": ("LOAD", 8),
"c_flw": ("LOAD", 4), "c_flwsp": ("LOAD", 4),
"c_fld": ("LOAD", 8), "c_fldsp": ("LOAD", 8),
"c_sw": ("STORE", 4), "c_swsp": ("STORE", 4),
"c_sd": ("STORE", 8), "c_sdsp": ("STORE", 8),
"c_fsw": ("STORE", 4), "c_fswsp": ("STORE", 4),
"c_fsd": ("STORE", 8), "c_fsdsp": ("STORE", 8),
}
def process_enc_line(line, ext):
single_dict = {}
encoding = ["-"] * 32
[name, remaining] = line.split(" ", 1)
name = name.replace(".", "_")
remaining = remaining.lstrip()
for s2, s1, entry in fixed_ranges.findall(remaining):
msb = int(s2)
lsb = int(s1)
if msb < lsb:
logging.error(
f'{line.split(" ")[0]:<10} has position {msb} less than position {lsb} in it\'s encoding'
)
raise SystemExit(1)
entry_value = int(entry, 0)
if entry_value >= (1 << (msb - lsb + 1)):
logging.error(
f'{line.split(" ")[0]:<10} has an illegal value {entry_value} assigned as per the bit width {msb - lsb}'
)
raise SystemExit(1)
for ind in range(lsb, msb + 1):
if encoding[31 - ind] != "-":
logging.error(
f'{line.split(" ")[0]:<10} has {ind} bit overlapping in it\'s opcodes'
)
raise SystemExit(1)
bit = str((entry_value >> (ind - lsb)) & 1)
encoding[31 - ind] = bit
remaining = fixed_ranges.sub(" ", remaining)
for lsb, value, drop in single_fixed.findall(remaining):
lsb = int(lsb, 0)
value = int(value, 0)
if encoding[31 - lsb] != "-":
logging.error(
f'{line.split(" ")[0]:<10} has {lsb} bit overlapping in it\'s opcodes'
)
raise SystemExit(1)
encoding[31 - lsb] = str(value)
match = "".join(encoding).replace("-", "0")
mask = "".join(encoding).replace("0", "1").replace("-", "0")
args = single_fixed.sub(" ", remaining).split()
encoding_args = encoding.copy()
for a in args:
if a not in arg_lut:
parts = a.split("=")
if len(parts) == 2:
existing_arg, new_arg = parts
if existing_arg in arg_lut:
arg_lut[a] = arg_lut[existing_arg]
else:
logging.error(
f" Found field {existing_arg} in variable {a} in instruction {name} whose mapping in arg_lut does not exist"
)
raise SystemExit(1)
else:
logging.error(
f" Found variable {a} in instruction {name} whose mapping in arg_lut does not exist"
)
raise SystemExit(1)
(msb, lsb) = arg_lut[a]
for ind in range(lsb, msb + 1):
if encoding_args[31 - ind] != "-":
logging.error(
f" Found variable {a} in instruction {name} overlapping {encoding_args[31 - ind]} variable in bit {ind}"
)
raise SystemExit(1)
encoding_args[31 - ind] = a
single_dict["encoding"] = "".join(encoding)
single_dict["variable_fields"] = args
single_dict["extension"] = [os.path.basename(ext)]
single_dict["match"] = hex(int(match, 2))
single_dict["mask"] = hex(int(mask, 2))
return (name, single_dict)
def same_base_isa(ext_name, ext_name_list):
type1 = ext_name.split("_")[0]
for ext_name1 in ext_name_list:
type2 = ext_name1.split("_")[0]
if (
type1 == type2
or (type2 == "rv" and (type1 == "rv32" or type1 == "rv64"))
or (type1 == "rv" and (type2 == "rv32" or type2 == "rv64"))
):
return True
return False
def overlaps(x, y):
x = x.rjust(len(y), "-")
y = y.rjust(len(x), "-")
for i in range(0, len(x)):
if not (x[i] == "-" or y[i] == "-" or x[i] == y[i]):
return False
return True
def overlap_allowed(a, x, y):
return x in a and y in a[x] or y in a and x in a[y]
def extension_overlap_allowed(x, y):
return overlap_allowed(overlapping_extensions, x, y)
def instruction_overlap_allowed(x, y):
return overlap_allowed(overlapping_instructions, x, y)
def create_inst_dict(file_filter, include_pseudo=False):
opcodes_dir = RISCV_OPCODES
instr_dict = {}
file_names = []
for fil in file_filter:
file_names += glob.glob(f"{opcodes_dir}/extensions/{fil}")
file_names = sorted(
f for f in file_names if os.path.basename(f) not in EXCLUDED_EXTENSIONS
)
file_names.sort(reverse=True)
logging.debug("Collecting standard instructions first")
for f in file_names:
logging.debug(f"Parsing File: {f} for standard instructions")
if not os.path.isdir(f):
with open(f) as fp:
lines = (line.rstrip() for line in fp) lines = list(line for line in lines if line) lines = list(
line for line in lines if not line.startswith("#")
)
for line in lines:
if "$import" in line or "$pseudo" in line:
continue
logging.debug(f" Processing line: {line}")
(name, single_dict) = process_enc_line(line, f)
ext_name = os.path.basename(f)
if name in instr_dict:
var = instr_dict[name]["extension"]
if same_base_isa(ext_name, var):
err_msg = f"instruction : {name} from "
err_msg += f"{ext_name} is already "
err_msg += f"added from {var} in same base ISA"
logging.error(err_msg)
raise SystemExit(1)
elif instr_dict[name]["encoding"] != single_dict["encoding"]:
err_msg = f"instruction : {name} from "
err_msg += f"{ext_name} is already "
err_msg += f"added from {var} but each have different encodings in different base ISAs"
logging.error(err_msg)
raise SystemExit(1)
instr_dict[name]["extension"].extend(single_dict["extension"])
else:
for key in instr_dict:
item = instr_dict[key]
if (
overlaps(item["encoding"], single_dict["encoding"])
and not extension_overlap_allowed(
ext_name, item["extension"][0]
)
and not instruction_overlap_allowed(name, key)
and same_base_isa(ext_name, item["extension"])
):
err_msg = f"instruction : {name} in extension "
err_msg += f"{ext_name} overlaps instruction {key} "
err_msg += f'in extension {item["extension"]}'
logging.error(err_msg)
raise SystemExit(1)
if name not in instr_dict:
instr_dict[name] = single_dict
logging.debug("Collecting pseudo instructions now")
for f in file_names:
logging.debug(f"Parsing File: {f} for pseudo_ops")
if not os.path.isdir(f):
with open(f) as fp:
lines = (line.rstrip() for line in fp) lines = list(line for line in lines if line) lines = list(
line for line in lines if not line.startswith("#")
)
for line in lines:
if "$pseudo" not in line:
continue
logging.debug(f" Processing line: {line}")
(ext, orig_inst, pseudo_inst, line) = pseudo_regex.findall(line)[0]
ext_file = f"{opcodes_dir}/extensions/{ext}"
if not os.path.exists(ext_file):
ext1_file = f"{opcodes_dir}/extensions/unratified/{ext}"
if not os.path.exists(ext1_file):
logging.error(
f"Pseudo op {pseudo_inst} in {f} depends on {ext} which is not available"
)
raise SystemExit(1)
else:
ext_file = ext1_file
found = False
for oline in open(ext_file):
if not re.findall(f"^\\s*{orig_inst}\\s+", oline):
continue
else:
found = True
break
if not found:
logging.error(
f"Orig instruction {orig_inst} not found in {ext}. Required by pseudo_op {pseudo_inst} present in {f}"
)
raise SystemExit(1)
(name, single_dict) = process_enc_line(pseudo_inst + " " + line, f)
if orig_inst.replace(".", "_") not in instr_dict or include_pseudo:
if name not in instr_dict:
instr_dict[name] = single_dict
logging.debug(f" including pseudo_ops:{name}")
else:
if single_dict["match"] != instr_dict[name]["match"]:
instr_dict[name + "_pseudo"] = single_dict
else:
ext_name = single_dict["extension"]
if (ext_name not in instr_dict[name]["extension"]) & (
name + "_pseudo" not in instr_dict
):
instr_dict[name]["extension"].extend(ext_name)
else:
logging.debug(
f" Skipping pseudo_op {pseudo_inst} since original instruction {orig_inst} already selected in list"
)
logging.debug("Collecting imported instructions")
for f in file_names:
logging.debug(f"Parsing File: {f} for imported ops")
with open(f) as fp:
lines = (line.rstrip() for line in fp) lines = list(line for line in lines if line) lines = list(
line for line in lines if not line.startswith("#")
)
for line in lines:
if "$import" not in line:
continue
logging.debug(f" Processing line: {line}")
(import_ext, reg_instr) = imported_regex.findall(line)[0]
import_ext_file = f"{opcodes_dir}/extensions/{import_ext}"
if not os.path.exists(import_ext_file):
ext1_file = f"{opcodes_dir}/extensions/unratified/{import_ext}"
if not os.path.exists(ext1_file):
logging.error(
f"Instruction {reg_instr} in {f} cannot be imported from {import_ext}"
)
raise SystemExit(1)
else:
ext_file = ext1_file
else:
ext_file = import_ext_file
found = False
for oline in open(ext_file):
if not re.findall(f"^\\s*{reg_instr}\\s+", oline):
continue
else:
found = True
break
if not found:
logging.error(
f"imported instruction {reg_instr} not found in {ext_file}. Required by {line} present in {f}"
)
logging.error(f"Note: you cannot import pseudo/imported ops.")
raise SystemExit(1)
(name, single_dict) = process_enc_line(oline, f)
if name in instr_dict:
var = instr_dict[name]["extension"]
if instr_dict[name]["encoding"] != single_dict["encoding"]:
err_msg = f"imported instruction : {name} in "
err_msg += f"{os.path.basename(f)} is already "
err_msg += f"added from {var} but each have different encodings for the same instruction"
logging.error(err_msg)
raise SystemExit(1)
instr_dict[name]["extension"].extend(single_dict["extension"])
else:
instr_dict[name] = single_dict
return instr_dict
def to_camel_case(text):
s = text.replace("-", " ").replace("_", " ")
s = s.split()
if len(text) == 0:
return text
return s[0] + "".join(i.capitalize() for i in s[1:])
def immediates(used_fields=None):
immediate_map = dict()
for name, _ in arg_lut.items():
if used_fields is not None and name not in used_fields:
continue
if "imm" in name:
has_lo_or_hi = False
if "lo" in name or "hi" in name:
name = name.replace("lo", "").replace("hi", "")
has_lo_or_hi = True
name += "lohi"
encoder = f"encode_immediate(&{name.upper()}, {name} as _)"
typ = f"{"u32" if 'u' in name else "i32"}"
immediate_map[name] = (encoder, typ)
return immediate_map
def sanitize_field(arg):
return arg.replace("=", "_eq_").replace(" ", "_")
def encoding_name(fields):
name = to_camel_case("_".join(sanitize_field(f).title() for f in fields))
return name if name else "Empty"
def enum_name(instr_name):
return instr_name.upper().replace("_", "")
def doc_key(instr_name):
s = instr_name.lower().replace("c_", "c.").replace("cm_", "cm.")
return s.replace("_", ".")
def emitter_arity(fields):
seen = set()
count = 0
for field in fields:
field = sanitize_field(field)
if "imm" in field:
if "lo" in field or "hi" in field:
field = field.replace("hi", "").replace("lo", "") + "lohi"
if field in seen:
continue
seen.add(field)
count += 1
return count
R, W, X = "R", "W", "X"
MEM_BASE = "B" NONE = "0"
WRITE_FIELDS = {"rd", "rd_n0", "rd_n2", "rd_p", "vd"}
RW_FIELDS = {"rd_rs1", "rd_rs1_n0", "rd_rs1_p"}
READ_VEC_FIELDS = {"vs1", "vs2", "vs3"}
READ_GP_PREFIXES = ("rs1", "rs2", "rs3", "c_rs1", "c_rs2", "c_sreg")
def base_field(field):
return field.split("_eq_")[0]
def field_access(field):
base = base_field(field)
if base in WRITE_FIELDS:
return W
if base in RW_FIELDS:
return X
if base in READ_VEC_FIELDS or base.startswith(READ_GP_PREFIXES):
return R
return None
def operand_spec(name, fields, operand_orders):
fields = [sanitize_field(f) for f in fields]
if name in FIXED_ZERO_FIELDS:
assert fields == FIXED_ZERO_FIELDS[name], f"{name}: reserved fields changed: {fields}"
return []
arity = emitter_arity(fields)
if arity == 0:
return []
shape = variant_of(fields)
assert shape in operand_orders, f"{name}: unreviewed public operand order for {shape}"
spec = list(operand_orders[shape])
spec += ["imm"] * (arity - len(spec))
assert len(spec) == arity, f"{name}: {spec} has arity {len(spec)}, expected {arity}"
return spec
def variant_of(fields):
return encoding_name(fields)
def vector_mem(name):
if re.match(r"^vl\d+re\d+_v$", name):
return ("LOAD", 0)
if re.match(r"^vs\d+r_v$", name):
return ("STORE", 0)
m = re.match(r"^(vl|vs)\w*?[ei](\d+)(?:ff)?_v$", name)
if m:
return ("LOAD" if m.group(1) == "vl" else "STORE", max(1, int(m.group(2)) // 8))
return None
def scalar_mem(name, match):
opcode, funct3, funct5 = match & 0x7F, (match >> 12) & 7, (match >> 27) & 0x1F
if opcode == 0x03: return ("LOAD", (1, 2, 4, 8, 1, 2, 4, 0)[funct3])
if opcode == 0x07: return ("LOAD", (1 << funct3) if 1 <= funct3 <= 4 else 0)
if opcode == 0x23: return ("STORE", (1, 2, 4, 8, 0, 0, 0, 0)[funct3])
if opcode == 0x27: return ("STORE", (1 << funct3) if 1 <= funct3 <= 4 else 0)
if opcode == 0x2F: width = (1 << funct3) if funct3 <= 4 else 0
if funct5 == 0b00010:
return ("LOAD", width)
if funct5 == 0b00011:
return ("STORE", width)
return ("RMW", width)
return None
def mem_access(name, match):
if name in C_MEM:
return C_MEM[name]
if name.startswith("v"):
vec = vector_mem(name)
if vec is not None:
return vec
if name.startswith(("c_", "cm_")):
return None
return scalar_mem(name, match)
def is_fp_mnemonic(name):
return name.startswith("f") and not name.startswith("fence")
def cpu_flags(name, fields, mem):
read_flags, write_flags = set(), set()
if name in FSR_ACCESS:
for flags, target in ((FSR_ACCESS[name][0], read_flags), (FSR_ACCESS[name][1], write_flags)):
target.update(f for f in flags.split(" | ") if f != "0")
return read_flags, write_flags
if is_fp_mnemonic(name):
if mem is None and not name.startswith(("fmv", "fsgnj", "fclass")):
write_flags.add("FFLAGS")
if "rm" in fields:
read_flags.add("FRM")
if name.startswith("v"):
if name.startswith(VXSAT_PREFIXES):
write_flags.add("VXSAT")
return read_flags, write_flags
def implicit_regs(name, fields):
read_mask, write_mask = IMPLICIT_REG_NAMES.get(name, (0, 0))
if "vm" in fields:
read_mask |= V0_BIT
return read_mask, write_mask
def operand_classes(name, spec, mem, spec_access):
is_fp = is_fp_mnemonic(name) and name not in FSR_ACCESS
is_c_fp = name.startswith("c_f")
classes = []
for field, access in zip(spec, spec_access):
base = base_field(field)
if field == "imm" or access is None or base == "csr":
classes.append("IMM")
continue
if base in ("vd", "vs1", "vs2", "vs3"):
classes.append("VEC")
continue
cls = "GP"
if is_fp or is_c_fp:
cls = "FP"
if access == MEM_BASE: cls = "GP"
elif base == "rd" and name.startswith(("feq_", "flt_", "fle_", "fclass_")):
cls = "GP"
elif base == "rd" and re.match(r"^fcvt_(w|wu|l|lu)_", name):
cls = "GP"
elif base == "rs1" and re.match(r"^fcvt_[sdqh]_(w|wu|l|lu)$", name):
cls = "GP"
elif base == "rs2" and name in FP_STORES:
cls = "FP"
elif base in FP_SPECIAL_GP_FIELDS.get(name, ()):
cls = "GP"
if name in ("vmv_x_s", "vcpop_m", "vfirst_m") and base == "rd":
cls = "GP"
elif name == "vmv_s_x" and base == "rs1":
cls = "GP"
elif name == "vfmv_f_s" and base == "rd":
cls = "FP"
elif name == "vfmv_s_f" and base == "rs1":
cls = "FP"
elif base == "rs1" and name in VECTOR_FP_SCALAR_RS1:
cls = "FP"
classes.append(cls)
return classes
def derive_effects(instr_dict, operand_orders):
stats = collections.Counter()
effects = {}
for name, single in instr_dict.items():
fields = single["variable_fields"]
match = int(single["match"], 0)
spec = operand_spec(name, fields, operand_orders)
if name in FENCE_NAMES or name in VOLATILE_NAMES:
spec_access = [None] * len(spec)
stats["override"] += 1
else:
spec_access = [field_access(f) for f in spec]
stats["rule"] += 1
if name in CSR_ACCESS:
stats["override"] += 1
stats["rule"] -= 1
mem = mem_access(name, match)
if mem is not None:
for i, f in enumerate(spec):
if base_field(f).startswith(("rs1",)) and spec_access[i] == R:
spec_access[i] = MEM_BASE
break
stats[f"mem_{mem[0].lower()}"] += 1
if mem[1] == 0:
stats["mem_width_unknown"] += 1
csr = CSR_ACCESS.get(name)
if csr is not None:
for i, f in enumerate(spec):
if base_field(f) == "csr":
spec_access[i] = csr
read_flags, write_flags = cpu_flags(name, fields, mem)
implicit = implicit_regs(name, fields)
classes = operand_classes(name, spec, mem, spec_access)
spec_access = [a if a is not None else NONE for a in spec_access]
effects[name] = {
"spec": spec,
"access": spec_access,
"classes": classes,
"mem": mem,
"read_flags": read_flags,
"write_flags": write_flags,
"implicit": implicit,
"volatile": name in VOLATILE_NAMES,
}
return effects, stats
def doc_comment(instr_name, spec, docs, params=None, access=None):
key = doc_key(instr_name)
doc = docs.get(key) or docs.get(key.removesuffix(".rv32"))
lines = []
if doc and (doc.get("long_name") or doc.get("description")):
if doc.get("long_name"):
lines.append(doc["long_name"])
if doc.get("description"):
lines.append("")
lines.extend(
line.replace("[", r"\[")
.replace("]", r"\]")
.replace("<", "<")
.replace(">", ">")
for line in doc["description"].split("\n")
)
else:
lines.append(f"RISC-V `{key}` instruction.")
lines.extend(["", "# Forms"])
if doc and doc.get("assembly"):
lines.append(f"Assembly: `{key} {doc['assembly']}`")
else:
lines.append(f"Assembly: `{' '.join([key] + spec)}`")
if params is not None:
lines.append(f"Rust: `{instr_name.lower()}({', '.join(params)})`")
lines.extend(["", "# Arguments"])
for param, field, effect in zip(params, spec, access):
lines.append(f"- `{param}` — {argument_doc(field, effect, param)}")
return lines, bool(doc and (doc.get("long_name") or doc.get("description") or doc.get("assembly")))
def parameter_name(field):
field = base_field(field)
if field.startswith(("rd", "c_rd")):
return "rd"
if field.startswith(("rs1", "c_rs1")):
return "rs1"
if field.startswith(("rs2", "c_rs2")):
return "rs2"
if field.startswith("rs3"):
return "rs3"
if field.startswith("c_sreg1"):
return "rs1"
if field.startswith("c_sreg2"):
return "rs2"
if field.startswith(("imm", "bimm", "jimm", "c_imm", "c_nzimm", "c_nzuimm", "c_uimm")):
return "imm"
return field.removeprefix("c_")
def parameter_names(fields):
names, used = [], set()
for field in fields:
name = parameter_name(field)
if name in used:
suffix = 2
while f"{name}{suffix}" in used:
suffix += 1
name = f"{name}{suffix}"
names.append(name)
used.add(name)
return names
def emitter_parameter_spec(fields, effect):
spec = list(effect["spec"])
if "aqrl" in spec and "aq" in fields and "rl" in fields:
spec[spec.index("aqrl")] = "aq"
for i, field in enumerate(spec):
if field == "imm":
spec[i] = "rl"
break
return spec
def argument_doc(field, effect, param):
field = base_field(field)
if param == "rl" and field == "rl":
return "Release-order bit; retained for the existing emitter API."
if field.startswith("rd"):
return "Destination register." if effect == W else "Destination/source register."
if field.startswith("rs") or field.startswith("sreg"):
return "Memory base register." if effect == MEM_BASE else "Source register."
if field in ("csr",):
return "Control and status register number."
if field == "aq":
return "Acquire-order bit."
if field == "rm":
return "Rounding mode."
if field == "vm":
return "Vector mask control."
if field == "nf":
return "Vector segment field count."
if field.startswith(("vd", "vs")):
return "Vector register operand."
if "imm" in field or field.startswith(("zimm", "shamt", "bs", "rnum", "fm", "pred", "succ")):
return "Immediate encoding value."
return "Instruction operand."
def rust_doc(lines, indent=""):
return "".join(f"{indent}/// {line}\n" if line else f"{indent}///\n" for line in lines)
ATTRIBUTION = """\
/* Automatically generated by parse_opcodes (meta/riscv.py). Do not edit by hand.
* Derived from riscv-opcodes (BSD-3-Clause) and riscv-unified-db
* (BSD-3-Clause-Clear); see meta/README.md for the input pins. */
"""
def make_encoders(instr_dict, docs, effects):
out = f"""\
//! Typed RISC-V emitter traits generated by `meta/riscv.py`.
//!
//! Invalid operand categories fail at compile time:
//!
//! ```compile_fail
//! use asmkit::riscv::{{Assembler, FaddSEmitter, Gp}};
//! fn require<T: FaddSEmitter<Gp, Gp, Gp, Gp>>() {{}}
//! require::<Assembler<'static>>();
//! ```
//!
//! ```compile_fail
//! use asmkit::riscv::{{Assembler, VaddVvEmitter, Vp}};
//! fn require<T: VaddVvEmitter<Vp, Vp, Vp, Vp>>() {{}}
//! require::<Assembler<'static>>();
//! ```
//!
//! ```compile_fail
//! use asmkit::Sym;
//! use asmkit::riscv::{{Assembler, Gp, JalEmitter}};
//! fn require<T: JalEmitter<Gp, Sym>>() {{}}
//! require::<Assembler<'static>>();
//! ```
//!
//! ```
//! use asmkit::Imm;
//! use asmkit::riscv::{{Assembler, FcvtmodWDEmitter, FmvWXEmitter, FmvXWEmitter, Fp, Gp, VfaddVfEmitter, Vp}};
//! fn require<T: FcvtmodWDEmitter<Gp, Fp> + FmvWXEmitter<Fp, Gp> + FmvXWEmitter<Gp, Fp> + VfaddVfEmitter<Vp, Vp, Fp, Imm>>() {{}}
//! require::<Assembler<'static>>();
//! ```
//!
//! ```compile_fail
//! use asmkit::Imm;
//! use asmkit::riscv::{{Assembler, Gp, VfaddVfEmitter, Vp}};
//! fn require<T: VfaddVfEmitter<Vp, Vp, Gp, Imm>>() {{}}
//! require::<Assembler<'static>>();
//! ```
//!
//! ```compile_fail
//! use asmkit::Label;
//! use asmkit::riscv::{{Assembler, Gp, JalrEmitter, LbEmitter}};
//! fn require<T: JalrEmitter<Gp, Gp, Label> + LbEmitter<Gp, Gp, Label>>() {{}}
//! require::<Assembler<'static>>();
//! ```
use super::{{assembler::*, opcodes::*, operands::*}};
use crate::core::operand::*;
{ATTRIBUTION}
"""
traits, impls, forwarders, impl_keys = [], [], [], []
label_positions = {
**{name: 1 for name in ("bgez", "bltz", "bnez", "beqz", "blez", "bgtz")},
**{name: 2 for name in ("beq", "bne", "blt", "bge", "bltu", "bgeu",
"bleu", "bgtu", "ble", "bgt")},
**{name: 0 for name in ("c_j", "c_jal", "jal_pseudo", "j")},
**{name: 1 for name in ("auipc", "jal", "c_beqz", "c_bnez")},
}
doc_hits = 0
for i in instr_dict:
if variant_of(instr_dict[i]["variable_fields"]) in UNSUPPORTED_TYPED_ENCODINGS:
continue
effect = effects[i]
fields = instr_dict[i]["variable_fields"]
spec = emitter_parameter_spec(fields, effect) if fields else []
params = parameter_names(spec)
comment, hit = doc_comment(i, spec, docs, params, effect["access"][:len(spec)])
doc_hits += hit
trait = f"{to_camel_case(i.title())}Emitter"
generics = ", ".join(f"T{n}" for n in range(len(params)))
trait_args = f"<{generics}>" if generics else ""
trait_params = ", ".join(f"{param}: T{n}" for n, param in enumerate(params))
traits.append(
rust_doc(comment)
+ f"pub trait {trait}{trait_args} {{\n"
+ f" fn {i.lower()}(&mut self{', ' if trait_params else ''}{trait_params});\n"
+ "}"
)
classes = effect["classes"][:len(spec)]
concrete = [{"GP": "Gp", "FP": "Fp", "VEC": "Vp", "IMM": "Imm"}[c] for c in classes]
variants = [concrete]
label_pos = label_positions.get(i)
if label_pos is not None:
labeled = list(concrete)
labeled[label_pos] = "Label"
variants.append(labeled)
for variant in variants:
type_args, bounds, typed_params, ops = [], [], [], []
for n, (param, typ) in enumerate(zip(params, variant)):
if typ == "Imm":
type_args.append(f"U{n}")
bounds.append(f"U{n}: Into<Imm>")
typed_params.append(f"{param}: U{n}")
ops.append(f"Into::<Imm>::into({param}).as_operand()")
else:
type_args.append(typ)
typed_params.append(f"{param}: {typ}")
ops.append(f"{param}.as_operand()")
impl_generics = f"<{', '.join(bounds)}>" if bounds else ""
impl_args = f"<{', '.join(type_args)}>" if type_args else ""
impl_keys.append((trait, tuple(type_args)))
impls.append(
f"impl{impl_generics} {trait}{impl_args} for Assembler<'_> {{\n"
+ f" fn {i.lower()}(&mut self{', ' if typed_params else ''}{', '.join(typed_params)}) {{\n"
+ f" self.emit_n(Opcode::{enum_name(i)} as i64, &[{', '.join(ops)}]);\n"
+ " }\n}"
)
forwarders.append(
rust_doc(comment, " ")
+ f" pub fn {i.lower()}{trait_args}(&mut self{', ' if trait_params else ''}{trait_params})\n"
+ f" where\n Self: {trait}{trait_args},\n {{\n"
+ f" <Self as {trait}{trait_args}>::{i.lower()}(self{', ' if params else ''}{', '.join(params)});\n"
+ " }"
)
assert len(impl_keys) == len(set(impl_keys)), "duplicate typed emitter impl"
out += "\n\n".join(traits)
out += "\n\n" + "\n\n".join(impls)
out += "\n\nimpl Assembler<'_> {\n" + "\n".join(forwarders) + "\n}\n"
logging.info(f"emitter docs: {doc_hits}/{len(instr_dict)} instructions documented")
return out
def make_opcodes(instr_dict, docs, effects):
out = GENERATED_MARKER + "\n" + ATTRIBUTION + "\n"
for i in instr_dict:
out += f'pub const MATCH_{i.upper().replace(".", "_")}: u32 = {(instr_dict[i]["match"])};\n'
out += f'pub const MASK_{i.upper().replace(".", "_")}: u32 = {(instr_dict[i]["mask"])};\n'
for num, name in csrs + csrs32:
out += f"pub const CSR_{name.upper()}: u16 = {hex(num)};\n"
for num, name in causes:
out += f'pub const CAUSE_{name.upper().replace(" ", "_")}: u8 = {hex(num)};\n'
def is_short(instruction):
return int(instruction["match"], 0) & 0x3 != 0x3
short_count = sum(is_short(instruction) for instruction in instr_dict.values())
rv32ext, rv64ext = set(), set()
for i in instr_dict:
for ext in instr_dict[i]["extension"]:
if ext.startswith("rv32"):
rv32ext.add(i)
elif ext.startswith("rv64"):
rv64ext.add(i)
elif i in RV64_ONLY_NAMES:
rv64ext.add(i)
else:
rv32ext.add(i)
rv64ext.add(i)
def match_table(name, ty, exts=None, compressed=False):
s = f"pub static {name}: [{ty}; {len(instr_dict)}] = [\n"
for i in instr_dict:
if exts is not None and i not in exts:
s += f"0xffff_ffff, /* {i} */\n"
elif compressed:
if is_short(instr_dict[i]):
s += f"{int(instr_dict[i]['mask' if 'MASK' in name else 'match'], 16) & 0xFFFF},\n"
else:
s += "0,\n"
else:
s += f"{instr_dict[i]['match' if 'MATCH' in name else 'mask']}, /* {i} */\n"
return s + "];\n"
out += match_table("OPCODE32_MATCH", "u32", rv32ext)
out += match_table("OPCODE32_MASK", "u32", rv32ext)
out += match_table("OPCODE64_MATCH", "u32", rv64ext)
out += match_table("OPCODE64_MASK", "u32", rv64ext)
out += match_table("OPCODE_MATCH", "u32")
out += match_table("OPCODE_MASK", "u32")
out += match_table("OPCODE_MASK_COMPRESSED", "u16", compressed=True)
out += match_table("OPCODE_MATCH_COMPRESSED", "u16", compressed=True)
out += f"pub static OPCODE_XLEN: [u8; {len(instr_dict)}] = [\n"
for i in instr_dict:
xlen = (1 if i in rv32ext else 0) | (2 if i in rv64ext else 0)
out += f"{xlen}, /* {i} */\n"
out += "];\n"
out += f"\npub static ALL_OPCODES: [Opcode; {len(instr_dict)}] = [\n"
for i in instr_dict:
out += f"Opcode::{enum_name(i)},\n"
out += "];\n"
out += f"\npub static SHORT_OPCODE: [bool; {len(instr_dict)}] = [\n"
for i in instr_dict:
out += f"{str(is_short(instr_dict[i])).lower()},\n"
out += "];\n"
out += f"pub const SHORT_OPCODES: [Opcode; {short_count}] = [\n"
for i in instr_dict:
if is_short(instr_dict[i]):
out += f"Opcode::{enum_name(i)},\n"
out += "];\n"
out += "#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]\n#[repr(u32)]\n"
out += "pub enum Opcode {\n"
doc_hits = 0
for i in instr_dict:
fields = instr_dict[i]["variable_fields"]
spec = effects[i]["spec"] if fields else []
comment, hit = doc_comment(i, spec, docs)
doc_hits += hit
out += rust_doc(comment, " ")
out += f" {enum_name(i)},\n"
out += " Invalid,\n}\n\n"
logging.info(f"opcode docs: {doc_hits}/{len(instr_dict)} instructions documented")
out += "pub const OPCODE_STR: &[&str] = &[\n"
for i in instr_dict:
out += f' "{doc_key(i)}",\n'
out += ' "<invalid>",\n];\n'
out += """
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Inst {
value: u32,
}
impl Inst {
pub const fn encode(&self) -> InstructionValue {
InstructionValue::new(self.value)
}
pub const fn new(op: Opcode) -> Self {
match op {
Opcode::Invalid => unreachable!(),
"""
for i in instr_dict:
enc_match = int(instr_dict[i]["match"], 0)
out += f""" Opcode::{enum_name(i)} => Inst {{
value: {hex(enc_match)},
}},
"""
out += " }\n }\n}\n"
encodings = dict()
for i in instr_dict:
args = instr_dict[i]["variable_fields"]
encoding = encoding_name(args)
encodings.setdefault(encoding, []).append(enum_name(i))
out += """
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum Encoding {
"""
for e in sorted(encodings):
out += f" {e},\n"
out += "}\n"
out += """
impl Opcode {
pub fn encoding(self) -> Encoding {
use Opcode::*;
match self {
Opcode::Invalid => unreachable!(),
"""
for e in sorted(encodings):
ops = encodings[e]
out += " " + "\n | ".join(ops) + f"\n => Encoding::{e},\n"
out += " }\n }\n}\n"
insn_value = """
/// InstructionValue contains the 32-bit instruction value and also provides access into the desired field.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct InstructionValue {
pub value: u32,
}
impl InstructionValue {
pub const fn new(value: u32) -> Self {
Self { value }
}
pub const fn field<const FIELD_START: usize, const FIELD_SIZE: usize>(self) -> u32 {
(self.value >> FIELD_START) & ((1 << FIELD_SIZE) - 1)
}
"""
for name, rng in arg_lut.items():
sanitized_name = name.replace(" ", "_").replace("=", "_eq_")
begin, end = rng[1], rng[0]
mask = ((1 << (end - begin + 1)) - 1) << begin
field = f"INSN_FIELD_{sanitized_name.upper()}"
out += f"pub const {field}: u32 = {hex(mask)};\n"
out += f"pub const {field}_START: u32 = {begin};\n"
out += f"pub const {field}_SIZE: u32 = {(end - begin) + 1};\n"
if "imm" in sanitized_name:
sanitized_name += "_raw"
insn_value += f""" pub const fn {sanitized_name}(self) -> u32 {{
(self.value >> {field}_START) & ((1 << {field}_SIZE) - 1)
}}
pub const fn set_{sanitized_name}(mut self, value: u32) -> Self {{
let mask = {field};
self.value &= !mask;
self.value |= (value & ((1 << {field}_SIZE) - 1)) << {field}_START;
self
}}
"""
used_fields = set()
for single in instr_dict.values():
used_fields.update(single["variable_fields"])
imms = immediates(used_fields)
for name in imms.keys():
encoder, ty = imms[name]
sanitized_name = name.replace(" ", "_").replace("=", "_eq_")
insn_value += f"""
/// {name}
pub const fn {sanitized_name}(self) -> {ty} {{
decode_immediate(&{sanitized_name.upper()}, self.value as _) as _
}}
pub const fn set_{sanitized_name}(mut self, {name}: {ty}) -> Self {{
self.value |= encode_immediate(&{sanitized_name.upper()}, {name} as _);
self
}}
"""
insn_value += "}\n"
return out + insn_value
def make_instdb(instr_dict, effects, stats):
def pattern_key(e):
return tuple((e["access"] + [NONE] * 6)[:6])
def signature_key(e):
return tuple((e["classes"] + ["ANY"] * 6)[:6])
patterns, signatures, implicits = [], [], [(0, 0)]
pattern_index, signature_index, implicit_index = {}, {}, {(0, 0): 0}
for e in effects.values():
for table, index, key in (
(patterns, pattern_index, pattern_key(e)),
(signatures, signature_index, signature_key(e)),
):
if key not in index:
index[key] = len(table)
table.append(key)
if e["implicit"] not in implicit_index:
implicit_index[e["implicit"]] = len(implicits)
implicits.append(e["implicit"])
features = sorted({ext for single in instr_dict.values() for ext in single["extension"]})
feature_index = {feature: index for index, feature in enumerate(features)}
feature_words = (len(features) + 63) // 64
def feature_variant(feature):
return to_camel_case(feature.replace("_", " ").title())
def feature_mask(single):
words = [0] * feature_words
for feature in single["extension"]:
index = feature_index[feature]
words[index // 64] |= 1 << (index % 64)
return words
feature_representatives = []
for feature in features:
feature_representatives.append(next(
name for name, single in instr_dict.items() if feature in single["extension"]
))
out = f"""\
//! RISC-V instruction info database: per-opcode effects.
//!
//! For every [`Opcode`] this records the operand read/write pattern (indexed into
//! [`RW_PATTERN_TABLE`]), the operand-class signature for debug asserts
//! ([`SIGNATURE_TABLE`]), the memory access kind and width, implicit fixed-register
//! effects ([`IMPLICIT_REG_TABLE`]), and implicit CSR flag effects
//! (`read_flags`/`write_flags`, `CpuRwFlags::RISCV_*` bits).
//!
//! Modeling notes:
//! - Public operand order comes only from the generator's reviewed declarative
//! encoding-shape table; unknown shapes fail generation.
//! - The `csr` immediate operand of CSR instructions carries R/W bits describing
//! the CSR access itself.
//! - Vector instructions with a `vm` field conservatively read v0 (vm=0 masking);
//! reads/writes of vtype/vl/vxrm are not modeled (`CpuRwFlags` has no bits).
//! - `mem_width` is the access width in bytes; 0 means no memory access or a width
//! that is not instruction-fixed (whole-register vector loads/stores).
//!
//! Automatically generated by parse_opcodes (meta/riscv.py). Do not edit by hand.
//! Derived from riscv-opcodes (BSD-3-Clause) and riscv-unified-db
//! (BSD-3-Clause-Clear); see meta/README.md for the input pins.
use crate::core::rwinfo::{{CpuRwFlags, OpRwFlags}};
use super::opcodes::Opcode;
/// RISC-V extension identifiers from the pinned riscv-opcodes input files.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum CpuFeature {{
"""
for feature in features:
out += f" {feature_variant(feature)},\n"
out += f"""}}
pub const CPU_FEATURE_COUNT: usize = {len(features)};
pub const CPU_FEATURE_WORDS: usize = {feature_words};
pub const CPU_FEATURE_NAMES: [&str; CPU_FEATURE_COUNT] = [
"""
for feature in features:
out += f' "{feature}",\n'
out += f"""\
];
pub const ALL_CPU_FEATURES: [CpuFeature; CPU_FEATURE_COUNT] = [
"""
for feature in features:
out += f" CpuFeature::{feature_variant(feature)},\n"
out += """\
];
/// Base-I database inputs enabled by [`Environment::baseline`](crate::Environment::baseline).
pub const BASELINE_CPU_FEATURES: &[CpuFeature] = &[
"""
for feature in ("rv_i", "rv32_i", "rv64_i", "rv_system"):
out += f" CpuFeature::{feature_variant(feature)},\n"
out += """\
];
impl CpuFeature {
pub const fn name(self) -> &'static str {
CPU_FEATURE_NAMES[self as usize]
}
}
/// Accepted source-extension alternatives per opcode. At least one bit must be enabled.
#[rustfmt::skip]
pub static OPCODE_FEATURE_MASKS: [[u64; CPU_FEATURE_WORDS]; """ + str(len(instr_dict)) + "] = [\n"
for name, single in instr_dict.items():
words = ", ".join(f"0x{word:016x}" for word in feature_mask(single))
out += f" [{words}], // {doc_key(name)}\n"
out += "];\n\n"
out += """/// Missing-feature diagnostics, indexed by `Opcode as usize`.
pub static OPCODE_FEATURE_CONTEXT: [&str; """ + str(len(instr_dict)) + "] = [\n"
for name, single in instr_dict.items():
alternatives = ", ".join(single["extension"])
out += f' "{doc_key(name)} requires one of: {alternatives}",\n'
out += "];\n\n"
out += """/// One opcode carrying each represented extension, for generated coverage checks.
pub static CPU_FEATURE_REPRESENTATIVE: [Opcode; CPU_FEATURE_COUNT] = [
"""
for name in feature_representatives:
out += f" Opcode::{enum_name(name)},\n"
out += f"""\
];
/// Pattern value: no effects.
pub const NONE: u32 = 0;
/// Pattern value: operand is read (`OpRwFlags::READ`).
pub const R: u32 = OpRwFlags::READ.bits();
/// Pattern value: operand is written (`OpRwFlags::WRITE`).
pub const W: u32 = OpRwFlags::WRITE.bits();
/// Pattern value: operand is read and written (`OpRwFlags::RW`).
pub const X: u32 = OpRwFlags::RW.bits();
/// Pattern value: memory address base register, read (`OpRwFlags::READ | MEM_BASE_READ`).
pub const B: u32 = OpRwFlags::READ.bits() | OpRwFlags::MEM_BASE_READ.bits();
/// No memory access (`InstInfo::mem_access`).
pub const MEM_NONE: u8 = 0;
/// Memory load (`InstInfo::mem_access`).
pub const MEM_LOAD: u8 = 1;
/// Memory store (`InstInfo::mem_access`).
pub const MEM_STORE: u8 = 2;
/// Atomic memory read-modify-write (`InstInfo::mem_access`).
pub const MEM_READ_MODIFY_WRITE: u8 = 3;
/// `InstInfo::flags` bit: machine-level side effects, no allocatable effects
/// (ecall/ebreak/xret/wfi).
pub const FLAG_VOLATILE: u8 = 0x1;
/// Signature value: operand class unconstrained (`SIGNATURE_TABLE`).
pub const ANY: u8 = 0;
/// Signature value: general-purpose register (`SIGNATURE_TABLE`).
pub const GP: u8 = 1;
/// Signature value: floating-point register (`SIGNATURE_TABLE`).
pub const FP: u8 = 2;
/// Signature value: vector register (`SIGNATURE_TABLE`).
pub const VEC: u8 = 3;
/// Signature value: immediate or label (`SIGNATURE_TABLE`).
pub const IMM: u8 = 4;
/// Per-opcode instruction information.
#[derive(Clone, Copy, Debug)]
pub struct InstInfo {{
/// Index into [`RW_PATTERN_TABLE`].
pub rw_info_index: u8,
/// Index into [`SIGNATURE_TABLE`].
pub signature_index: u8,
/// `MEM_*` access kind.
pub mem_access: u8,
/// Access width in bytes (0 = none or not instruction-fixed).
pub mem_width: u8,
/// Index into [`IMPLICIT_REG_TABLE`].
pub implicit_index: u8,
/// `FLAG_*` bits.
pub flags: u8,
/// Implicit CSR flag reads (`CpuRwFlags::RISCV_*` bits).
pub read_flags: u32,
/// Implicit CSR flag writes (`CpuRwFlags::RISCV_*` bits).
pub write_flags: u32,
}}
/// Implicit fixed-register effects. Bits 0..=31 name GP registers x0..x31; bit 32
/// names the vector mask register v0.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ImplicitRegEffects {{
/// Mask of implicitly read fixed registers.
pub read: u64,
/// Mask of implicitly written fixed registers.
pub write: u64,
}}
/// Deduplicated operand read/write patterns (`OpRwFlags` values per position).
#[rustfmt::skip]
pub static RW_PATTERN_TABLE: [[u32; 6]; {len(patterns)}] = [
"""
for p in patterns:
out += f" [{', '.join(p)}],\n"
out += "];\n\n"
out += f"""/// Deduplicated operand-class signatures (`ANY`/`GP`/`FP`/`VEC`/`IMM` per position).
#[rustfmt::skip]
pub static SIGNATURE_TABLE: [[u8; 6]; {len(signatures)}] = [
"""
for s in signatures:
out += f" [{', '.join(s)}],\n"
out += "];\n\n"
out += f"""/// Deduplicated implicit fixed-register effects (index 0 = none).
pub static IMPLICIT_REG_TABLE: [ImplicitRegEffects; {len(implicits)}] = [
"""
for read_mask, write_mask in implicits:
out += f" ImplicitRegEffects {{ read: {hex(read_mask)}, write: {hex(write_mask)} }},\n"
out += "];\n\n"
out += f"""/// Per-opcode effects, indexed by `Opcode as usize`.
#[rustfmt::skip]
pub static INST_INFO_TABLE: [InstInfo; {len(instr_dict)}] = [
"""
mem_const = {"LOAD": "MEM_LOAD", "STORE": "MEM_STORE", "RMW": "MEM_READ_MODIFY_WRITE"}
for i in instr_dict:
e = effects[i]
mem_access, mem_width = e["mem"] if e["mem"] is not None else ("NONE", 0)
mem_access = mem_const.get(mem_access, "MEM_NONE")
flags = "FLAG_VOLATILE" if e["volatile"] else "0"
read_flags = " | ".join(
f"CpuRwFlags::RISCV_{f}.bits()" for f in sorted(e["read_flags"])
) or "0"
write_flags = " | ".join(
f"CpuRwFlags::RISCV_{f}.bits()" for f in sorted(e["write_flags"])
) or "0"
out += (
f" InstInfo {{ rw_info_index: {pattern_index[pattern_key(e)]}, "
f"signature_index: {signature_index[signature_key(e)]}, "
f"mem_access: {mem_access}, mem_width: {mem_width}, "
f"implicit_index: {implicit_index[e['implicit']]}, flags: {flags}, "
f"read_flags: {read_flags}, write_flags: {write_flags} }}, // {doc_key(i)}\n"
)
out += "];\n\n"
out += """impl Opcode {
/// Returns the effects database entry for this opcode.
pub fn inst_info(self) -> &'static InstInfo {
&INST_INFO_TABLE[self as usize]
}
/// Returns the implicit fixed-register effects of this opcode.
pub fn implicit_reg_effects(self) -> &'static ImplicitRegEffects {
&IMPLICIT_REG_TABLE[self.inst_info().implicit_index as usize]
}
}
"""
return out
def write_opcodes(instr_dict, docs, effects):
path = os.path.join(SRC_RISCV, "opcodes.rs")
with open(path) as fp:
current = fp.read()
marker = current.index(GENERATED_MARKER)
head = current[:marker]
with open(path, "w") as fp:
fp.write(head + make_opcodes(instr_dict, docs, effects))
logging.info(f"wrote {path}")
def main():
extensions = [a for a in sys.argv[1:] if not a.startswith("-")]
if not extensions:
extensions = ["rv*"]
docs = load_inst_docs()
logging.info(f"loaded {len(docs)} unified-db docs")
instr_dict = create_inst_dict(extensions, include_pseudo=True)
instr_dict = collections.OrderedDict(sorted(instr_dict.items()))
logging.info(f"{len(instr_dict)} instructions after exclusions {sorted(EXCLUDED_EXTENSIONS)}")
encoding_shapes = {variant_of(single["variable_fields"]) for single in instr_dict.values()}
assert encoding_shapes == set(OPERAND_ORDER_BY_ENCODING), (
"encoding-shape review table mismatch: "
f"missing={sorted(encoding_shapes - set(OPERAND_ORDER_BY_ENCODING))}, "
f"stale={sorted(set(OPERAND_ORDER_BY_ENCODING) - encoding_shapes)}"
)
assert UNSUPPORTED_TYPED_ENCODINGS <= encoding_shapes
assert RV64_ONLY_NAMES <= set(instr_dict), "RV64-only instruction missing from opcode source"
effects, stats = derive_effects(instr_dict, OPERAND_ORDER_BY_ENCODING)
write_opcodes(instr_dict, docs, effects)
emitter_path = os.path.join(SRC_RISCV, "emitter.rs")
emitter_text = make_encoders(instr_dict, docs, effects)
assert "todo!" not in emitter_text and "unimplemented!" not in emitter_text
assert not re.search(r"\bop\d+\s*:", emitter_text)
assert "pub trait AddEmitter<T0, T1, T2>" in emitter_text
assert "impl<U2: Into<Imm>> AddiEmitter<Gp, Gp, U2>" in emitter_text
assert "impl<U3: Into<Imm>, U4: Into<Imm>> AmoaddBEmitter" in emitter_text
assert "BeqEmitter<Gp, Gp, Label>" in emitter_text
assert "VfaddVfEmitter<Vp, Vp, Fp, U3>" in emitter_text
assert "FcvtmodWDEmitter<Gp, Fp>" in emitter_text
assert "FmvWXEmitter<Fp, Gp>" in emitter_text
assert "FmvXWEmitter<Gp, Fp>" in emitter_text
assert "pub trait FenceIEmitter {" in emitter_text
assert "pub trait FenceTsoEmitter {" in emitter_text
assert "fn fence_i(&mut self);" in emitter_text
assert "fn fence_tso(&mut self);" in emitter_text
assert "impl JalrEmitter<Gp, Gp, Label> for Assembler" not in emitter_text
assert "impl LbEmitter<Gp, Gp, Label> for Assembler" not in emitter_text
assert "impl Assembler<'_>" in emitter_text
for name, instruction in instr_dict.items():
if variant_of(instruction["variable_fields"]) not in UNSUPPORTED_TYPED_ENCODINGS:
continue
trait = f"{to_camel_case(name.title())}Emitter"
assert f"pub trait {trait}" not in emitter_text, name
assert f"fn {name.lower()}(" not in emitter_text, name
assert f"pub fn {name.lower()}" not in emitter_text, name
with open(emitter_path, "w") as fp:
fp.write(emitter_text)
logging.info(f"wrote {emitter_path}")
typed_count = sum(
variant_of(instruction["variable_fields"]) not in UNSUPPORTED_TYPED_ENCODINGS
for instruction in instr_dict.values()
)
logging.info(
f"typed emitter coverage: {typed_count}/{len(instr_dict)} instructions; "
f"{len(instr_dict) - typed_count} suppressed"
)
instdb_path = os.path.join(SRC_RISCV, "instdb.rs")
with open(instdb_path, "w") as fp:
fp.write(make_instdb(instr_dict, effects, stats))
logging.info(f"wrote {instdb_path}")
logging.info(f"effects derivation stats: {dict(stats)}")
if __name__ == "__main__":
main()