Files
OSACA/osaca/parser/parser_RISCV.py
Metehan Dundar a8fca2afdb Format code with black and fix flake8 linting issues
- Applied black formatting with line length 99
- Fixed flake8 linting issues (E265 block comments)
- All 115 tests still pass after formatting
- Code style is now consistent across the codebase

Changes:
- osaca/parser/base_parser.py: improved line breaks and comment formatting
- osaca/osaca.py: added missing blank line
- osaca/db_interface.py: reformatted long lines and comments
- osaca/parser/parser_RISCV.py: extensive formatting improvements
- osaca/semantics/kernel_dg.py: improved formatting and readability
- osaca/semantics/hw_model.py: fixed shebang and formatting
- osaca/semantics/marker_utils.py: removed TODO comment and formatting
2025-07-11 22:28:29 +02:00

1007 lines
38 KiB
Python

#!/usr/bin/env python3
import re
import pyparsing as pp
from osaca.parser import BaseParser
from osaca.parser.instruction_form import InstructionForm
from osaca.parser.operand import Operand
from osaca.parser.directive import DirectiveOperand
from osaca.parser.memory import MemoryOperand
from osaca.parser.label import LabelOperand
from osaca.parser.register import RegisterOperand
from osaca.parser.identifier import IdentifierOperand
from osaca.parser.immediate import ImmediateOperand
class ParserRISCV(BaseParser):
_instance = None
# Singleton pattern, as this is created very many times
def __new__(cls):
if cls._instance is None:
cls._instance = super(ParserRISCV, cls).__new__(cls)
return cls._instance
def __init__(self):
super().__init__()
# Initialize parser, but don't set 'isa' directly as an attribute
self._isa_str = "riscv"
def isa(self):
"""Return the ISA string."""
return self._isa_str
def start_marker(self):
"""Return the OSACA start marker for RISC-V assembly."""
# Parse the RISC-V start marker (li a1, 111 followed by NOP)
# This matches how start marker is defined in marker_utils.py for RISC-V
marker_str = (
"li a1, 111 # OSACA START MARKER\n"
".byte 19,0,0,0 # OSACA START MARKER\n"
)
return self.parse_file(marker_str)
def end_marker(self):
"""Return the OSACA end marker for RISC-V assembly."""
# Parse the RISC-V end marker (li a1, 222 followed by NOP)
# This matches how end marker is defined in marker_utils.py for RISC-V
marker_str = (
"li a1, 222 # OSACA END MARKER\n" ".byte 19,0,0,0 # OSACA END MARKER\n"
)
return self.parse_file(marker_str)
def construct_parser(self):
"""Create parser for RISC-V ISA."""
# Comment - RISC-V uses # for comments
symbol_comment = "#"
self.comment = pp.Literal(symbol_comment) + pp.Group(
pp.ZeroOrMore(pp.Word(pp.printables))
).setResultsName(self.comment_id)
# Define RISC-V assembly identifier
decimal_number = pp.Combine(
pp.Optional(pp.Literal("-")) + pp.Word(pp.nums)
).setResultsName("value")
hex_number = pp.Combine(
pp.Optional(pp.Literal("-")) + pp.Literal("0x") + pp.Word(pp.hexnums)
).setResultsName("value")
# RISC-V specific relocation attributes
reloc_type = (
pp.Literal("%hi")
| pp.Literal("%lo")
| pp.Literal("%pcrel_hi")
| pp.Literal("%pcrel_lo")
| pp.Literal("%tprel_hi")
| pp.Literal("%tprel_lo")
| pp.Literal("%tprel_add")
).setResultsName("reloc_type")
reloc_expr = pp.Group(
reloc_type
+ pp.Suppress("(")
+ pp.Word(pp.alphas + pp.nums + "_").setResultsName("symbol")
+ pp.Suppress(")")
).setResultsName("relocation")
# First character of an identifier
first = pp.Word(pp.alphas + "_.", exact=1)
# Rest of the identifier
rest = pp.Word(pp.alphanums + "_.")
# PLT suffix (@plt) for calls to shared libraries
plt_suffix = pp.Optional(pp.Literal("@") + pp.Word(pp.alphas))
identifier = pp.Group(
(pp.Combine(first + pp.Optional(rest) + plt_suffix)).setResultsName("name")
+ pp.Optional(
pp.Suppress(pp.Literal("+"))
+ (hex_number | decimal_number).setResultsName("offset")
)
).setResultsName(self.identifier)
# Immediate with optional relocation
immediate = pp.Group(
reloc_expr | (hex_number ^ decimal_number) | identifier
).setResultsName(self.immediate_id)
# Label
self.label = pp.Group(
identifier.setResultsName("name") + pp.Literal(":") + pp.Optional(self.comment)
).setResultsName(self.label_id)
# Directive
directive_option = pp.Combine(
pp.Word(pp.alphas + "#@.%", exact=1)
+ pp.Optional(pp.Word(pp.printables + " ", excludeChars=","))
)
directive_parameter = (
pp.quotedString | directive_option | identifier | hex_number | decimal_number
)
commaSeparatedList = pp.delimitedList(pp.Optional(directive_parameter), delim=",")
self.directive = pp.Group(
pp.Literal(".")
+ pp.Word(pp.alphanums + "_").setResultsName("name")
+ (pp.OneOrMore(directive_parameter) ^ commaSeparatedList).setResultsName("parameters")
+ pp.Optional(self.comment)
).setResultsName(self.directive_id)
# LLVM-MCA markers
self.llvm_markers = pp.Group(
pp.Literal("#")
+ pp.Combine(
pp.CaselessLiteral("LLVM-MCA-")
+ (pp.CaselessLiteral("BEGIN") | pp.CaselessLiteral("END"))
)
+ pp.Optional(self.comment)
).setResultsName(self.comment_id)
##############################
# Instructions
# Mnemonic
mnemonic = pp.Word(pp.alphanums + ".").setResultsName("mnemonic")
# Register:
# RISC-V has two main types of registers:
# 1. Integer registers (x0-x31 or ABI names)
# 2. Floating-point registers (f0-f31 or ABI names)
# Integer register ABI names
integer_reg_abi = (
pp.CaselessLiteral("zero")
| pp.CaselessLiteral("ra")
| pp.CaselessLiteral("sp")
| pp.CaselessLiteral("gp")
| pp.CaselessLiteral("tp")
| pp.Regex(r"[tas][0-9]+") # t0-t6, a0-a7, s0-s11
).setResultsName("name")
# Integer registers x0-x31
integer_reg_x = pp.CaselessLiteral("x").setResultsName("prefix") + pp.Word(
pp.nums
).setResultsName("name")
# Floating point registers
fp_reg_abi = pp.Regex(r"f[tas][0-9]+").setResultsName(
"name"
) # ft0-ft11, fa0-fa7, fs0-fs11
fp_reg_f = pp.CaselessLiteral("f").setResultsName("prefix") + pp.Word(
pp.nums
).setResultsName("name")
# Control and status registers (CSRs)
csr_reg = pp.Combine(
pp.CaselessLiteral("csr") + pp.Word(pp.alphanums + "_")
).setResultsName("name")
# Vector registers (for the "V" extension)
vector_reg = pp.CaselessLiteral("v").setResultsName("prefix") + pp.Word(
pp.nums
).setResultsName("name")
# Combined register definition
register = pp.Group(
integer_reg_x | integer_reg_abi | fp_reg_f | fp_reg_abi | vector_reg | csr_reg
).setResultsName(self.register_id)
self.register = register
# Memory addressing mode in RISC-V: offset(base_register)
memory = pp.Group(
pp.Optional(immediate.setResultsName("offset"))
+ pp.Suppress(pp.Literal("("))
+ register.setResultsName("base")
+ pp.Suppress(pp.Literal(")"))
).setResultsName(self.memory_id)
# Combine to instruction form
operand_first = pp.Group(register ^ immediate ^ memory ^ identifier)
operand_rest = pp.Group(register ^ immediate ^ memory ^ identifier)
# Handle additional vector parameters
additional_params = pp.ZeroOrMore(
pp.Suppress(pp.Literal(","))
+ pp.Word(pp.alphas + pp.nums).setResultsName("vector_param", listAllMatches=True)
)
# Main instruction parser
self.instruction_parser = (
mnemonic
+ pp.Optional(operand_first.setResultsName("operand1"))
+ pp.Optional(pp.Suppress(pp.Literal(",")))
+ pp.Optional(operand_rest.setResultsName("operand2"))
+ pp.Optional(pp.Suppress(pp.Literal(",")))
+ pp.Optional(operand_rest.setResultsName("operand3"))
+ pp.Optional(pp.Suppress(pp.Literal(",")))
+ pp.Optional(operand_rest.setResultsName("operand4"))
+ pp.Optional(additional_params)
+ pp.Optional(self.comment)
)
def parse_line(self, line, line_number=None):
"""
Parse line and return instruction form.
:param str line: line of assembly code
:param line_number: identifier of instruction form, defaults to None
:type line_number: int, optional
:return: `dict` -- parsed asm line (comment, label, directive or
instruction form)
"""
instruction_form = InstructionForm(
mnemonic=None,
operands=[],
directive_id=None,
comment_id=None,
label_id=None,
line=line,
line_number=line_number,
)
result = None
# 1. Parse comment
try:
result = self.process_operand(self.comment.parseString(line, parseAll=True).asDict())
instruction_form.comment = " ".join(result[self.comment_id])
except pp.ParseException:
pass
# 1.2 check for llvm-mca marker
try:
result = self.process_operand(
self.llvm_markers.parseString(line, parseAll=True).asDict()
)
instruction_form.comment = " ".join(result[self.comment_id])
except pp.ParseException:
pass
# 2. Parse label
if result is None:
try:
# returns tuple with label operand and comment, if any
result = self.process_operand(self.label.parseString(line, parseAll=True).asDict())
instruction_form.label = result[0].name
if result[1] is not None:
instruction_form.comment = " ".join(result[1])
except pp.ParseException:
pass
# 3. Parse directive
if result is None:
try:
# returns directive with label operand and comment, if any
result = self.process_operand(
self.directive.parseString(line, parseAll=True).asDict()
)
instruction_form.directive = DirectiveOperand(
name=result[0].name, parameters=result[0].parameters
)
if result[1] is not None:
instruction_form.comment = " ".join(result[1])
except pp.ParseException:
pass
# 4. Parse instruction
if result is None:
try:
result = self.parse_instruction(line)
except (pp.ParseException, KeyError) as e:
raise ValueError(
"Unable to parse {!r} on line {}".format(line, line_number)
) from e
instruction_form.mnemonic = result.mnemonic
instruction_form.operands = result.operands
instruction_form.comment = result.comment
return instruction_form
def parse_instruction(self, instruction):
"""
Parse instruction in asm line.
:param str instruction: Assembly line string.
:returns: `dict` -- parsed instruction form
"""
# Store current instruction for context in operand processing
if instruction.startswith("vsetvli"):
self.current_instruction = "vsetvli"
else:
# Extract mnemonic for context
parts = instruction.split("#")[0].strip().split()
self.current_instruction = parts[0] if parts else None
# Special handling for vector instructions like vsetvli with many parameters
if instruction.startswith("vsetvli"):
# Split into mnemonic and operands part
parts = (
instruction.split("#")[0].strip().split(None, 1)
) # Split on first whitespace only
mnemonic = parts[0]
# Split operands by commas
if len(parts) > 1:
operand_part = parts[1]
operands_list = [op.strip() for op in operand_part.split(",")]
# Process each operand
operands = []
for op in operands_list:
if (
op.startswith("x")
or op in ["zero", "ra", "sp", "gp", "tp"]
or re.match(r"[tas][0-9]+", op)
):
operands.append(RegisterOperand(name=op))
else:
# Vector parameters get appropriate attributes
if op.startswith("e"): # Element width
operands.append(IdentifierOperand(name=op))
elif op.startswith("m"): # LMUL setting
operands.append(IdentifierOperand(name=op))
elif op in ["ta", "tu", "ma", "mu"]: # Tail/mask policies
operands.append(IdentifierOperand(name=op))
else:
operands.append(IdentifierOperand(name=op))
# Get comment if present
comment = None
if "#" in instruction:
comment = instruction.split("#", 1)[1].strip()
return InstructionForm(mnemonic=mnemonic, operands=operands, comment_id=comment)
# Regular instruction parsing
try:
result = self.instruction_parser.parseString(instruction, parseAll=True).asDict()
operands = []
# Process operands
for i in range(1, 5):
operand_key = f"operand{i}"
if operand_key in result:
operand = self.process_operand(result[operand_key])
(
operands.extend(operand)
if isinstance(operand, list)
else operands.append(operand)
)
# Handle vector parameters as identifiers with appropriate attributes
if "vector_param" in result:
if isinstance(result["vector_param"], list):
for param in result["vector_param"]:
if param.startswith("e"): # Element width
operands.append(IdentifierOperand(name=param))
elif param.startswith("m"): # LMUL setting
operands.append(IdentifierOperand(name=param))
else:
operands.append(IdentifierOperand(name=param))
else:
operands.append(IdentifierOperand(name=result["vector_param"]))
return_dict = InstructionForm(
mnemonic=result["mnemonic"],
operands=operands,
comment_id=(
" ".join(result[self.comment_id]) if self.comment_id in result else None
),
)
return return_dict
except Exception:
# For special vector instructions or ones with % in them
if "%" in instruction or instruction.startswith("v"):
parts = instruction.split("#")[0].strip().split(None, 1)
mnemonic = parts[0]
operands = []
if len(parts) > 1:
operand_part = parts[1]
operands_list = [op.strip() for op in operand_part.split(",")]
for op in operands_list:
# Process '%hi(data)' to 'data' for certain operands
if op.startswith("%") and "(" in op and ")" in op:
reloc_type = op[: op.index("(")]
symbol = op[op.index("(") + 1 : op.index(")")]
operands.append(
ImmediateOperand(
imd_type="reloc",
value=None,
reloc_type=reloc_type,
symbol=symbol,
)
)
else:
operands.append(IdentifierOperand(name=op))
comment = None
if "#" in instruction:
comment = instruction.split("#", 1)[1].strip()
return InstructionForm(mnemonic=mnemonic, operands=operands, comment_id=comment)
else:
raise
def process_operand(self, operand):
"""Post-process operand"""
# structure memory addresses
if self.memory_id in operand:
return self.process_memory_address(operand[self.memory_id])
# add value attribute to immediates
if self.immediate_id in operand:
return self.process_immediate(operand[self.immediate_id])
if self.label_id in operand:
return self.process_label(operand[self.label_id])
if self.identifier in operand:
return self.process_identifier(operand[self.identifier])
if self.register_id in operand:
return self.process_register_operand(operand[self.register_id])
if self.directive_id in operand:
return self.process_directive_operand(operand[self.directive_id])
return operand
def process_directive_operand(self, operand):
return (
DirectiveOperand(
name=operand["name"],
parameters=operand["parameters"],
),
operand["comment"] if "comment" in operand else None,
)
def process_register_operand(self, operand):
"""Process register operands, including ABI name to x-register mapping
and vector attributes"""
# If already has prefix (x#, f#, v#), process with appropriate attributes
if "prefix" in operand:
prefix = operand["prefix"].lower()
# Special handling for vector registers
if prefix == "v":
return RegisterOperand(
prefix=prefix,
name=operand["name"],
regtype="vector",
# Vector registers can have different element widths (e8,e16,e32,e64)
width=operand.get("width", None),
# Number of elements (m1,m2,m4,m8)
lanes=operand.get("lanes", None),
# For vector mask registers
mask=operand.get("mask", False),
# For tail agnostic/undisturbed policies
zeroing=operand.get("zeroing", False),
)
# For floating point registers
elif prefix == "f":
return RegisterOperand(
prefix=prefix,
name=operand["name"],
regtype="float",
width=64, # RISC-V typically uses 64-bit float registers
)
# For integer registers
elif prefix == "x":
return RegisterOperand(
prefix=prefix,
name=operand["name"],
regtype="int",
width=64, # RV64 uses 64-bit registers
)
# Handle ABI names by converting to x-register numbers
name = operand["name"].lower()
# ABI name mapping for integer registers
abi_to_x = {
"zero": "x0",
"ra": "x1",
"sp": "x2",
"gp": "x3",
"tp": "x4",
"t0": "x5",
"t1": "x6",
"t2": "x7",
"s0": "x8",
"s1": "x9",
"a0": "x10",
"a1": "x11",
"a2": "x12",
"a3": "x13",
"a4": "x14",
"a5": "x15",
"a6": "x16",
"a7": "x17",
"s2": "x18",
"s3": "x19",
"s4": "x20",
"s5": "x21",
"s6": "x22",
"s7": "x23",
"s8": "x24",
"s9": "x25",
"s10": "x26",
"s11": "x27",
"t3": "x28",
"t4": "x29",
"t5": "x30",
"t6": "x31",
}
# Integer register ABI names
if name in abi_to_x:
return RegisterOperand(
prefix="x",
name=abi_to_x[name],
regtype="int",
width=64, # RV64 uses 64-bit registers
)
# Floating point register ABI names
elif name.startswith("f") and name[1] in ["t", "a", "s"]:
if name[1] == "a": # fa0-fa7
idx = int(name[2:])
return RegisterOperand(prefix="f", name=str(idx + 10), regtype="float", width=64)
elif name[1] == "s": # fs0-fs11
idx = int(name[2:])
if idx <= 1:
return RegisterOperand(
prefix="f", name=str(idx + 8), regtype="float", width=64
)
else:
return RegisterOperand(
prefix="f", name=str(idx + 16), regtype="float", width=64
)
elif name[1] == "t": # ft0-ft11
idx = int(name[2:])
if idx <= 7:
return RegisterOperand(prefix="f", name=str(idx), regtype="float", width=64)
else:
return RegisterOperand(
prefix="f", name=str(idx + 20), regtype="float", width=64
)
# CSR registers
elif name.startswith("csr"):
return RegisterOperand(prefix="", name=name, regtype="csr")
# If no mapping found, return as is
return RegisterOperand(prefix="", name=name)
def process_memory_address(self, memory_address):
"""Post-process memory address operand with RISC-V specific attributes"""
# Process offset
offset = memory_address.get("offset", None)
if isinstance(offset, list) and len(offset) == 1:
offset = offset[0]
if offset is not None and "value" in offset:
offset = ImmediateOperand(value=int(offset["value"], 0))
if isinstance(offset, dict) and "identifier" in offset:
offset = self.process_identifier(offset["identifier"])
# Process base register
base = memory_address.get("base", None)
if base is not None:
base = self.process_register_operand(base)
# Determine data type from instruction context if available
# RISC-V load/store instructions encode the data width in the mnemonic
# e.g., lw (word), lh (half), lb (byte), etc.
data_type = None
if hasattr(self, "current_instruction"):
mnemonic = self.current_instruction.lower()
if any(x in mnemonic for x in ["b", "bu"]): # byte operations
data_type = "byte"
elif any(x in mnemonic for x in ["h", "hu"]): # halfword operations
data_type = "halfword"
elif any(x in mnemonic for x in ["w", "wu"]): # word operations
data_type = "word"
elif "d" in mnemonic: # doubleword operations
data_type = "doubleword"
# Create memory operand with enhanced attributes
return MemoryOperand(
offset=offset,
base=base,
index=None, # RISC-V doesn't use index registers
scale=1, # RISC-V doesn't use scaling
data_type=data_type,
# Handle vector memory operations
mask=memory_address.get("mask", None), # For vector masked loads/stores
src=memory_address.get("src", None), # Source register type for stores
dst=memory_address.get("dst", None), # Destination register type for loads
)
def process_label(self, label):
"""Post-process label asm line"""
return (
LabelOperand(name=label["name"]["name"]),
label["comment"] if self.comment_id in label else None,
)
def process_identifier(self, identifier):
"""Post-process identifier operand"""
return IdentifierOperand(
name=identifier["name"] if "name" in identifier else None,
offset=identifier["offset"] if "offset" in identifier else None,
)
def process_immediate(self, immediate):
"""Post-process immediate operand with RISC-V specific handling"""
# Handle relocations
if "relocation" in immediate:
reloc = immediate["relocation"]
return ImmediateOperand(
imd_type="reloc",
value=None,
reloc_type=reloc["reloc_type"],
symbol=reloc["symbol"],
)
# Handle identifiers
if "identifier" in immediate:
return self.process_identifier(immediate["identifier"])
# Handle numeric values with validation
if "value" in immediate:
value = int(immediate["value"], 0) # Convert to integer, handling hex/decimal
# Determine immediate type and validate range based on instruction type
if hasattr(self, "current_instruction"):
mnemonic = self.current_instruction.lower()
# I-type instructions (12-bit signed immediate)
if any(
x in mnemonic
for x in [
"addi",
"slti",
"xori",
"ori",
"andi",
"slli",
"srli",
"srai",
]
):
if not -2048 <= value <= 2047:
raise ValueError(
f"Immediate value {value} out of range for I-type "
f"instruction (-2048 to 2047)"
)
return ImmediateOperand(imd_type="I", value=value)
# S-type instructions (12-bit signed immediate for store)
elif any(x in mnemonic for x in ["sb", "sh", "sw", "sd"]):
if not -2048 <= value <= 2047:
raise ValueError(
f"Immediate value {value} out of range for S-type "
f"instruction (-2048 to 2047)"
)
return ImmediateOperand(imd_type="S", value=value)
# B-type instructions (13-bit signed immediate for branches, must be even)
elif any(x in mnemonic for x in ["beq", "bne", "blt", "bge", "bltu", "bgeu"]):
if not -4096 <= value <= 4095 or value % 2 != 0:
raise ValueError(
f"Immediate value {value} out of range or not even "
f"for B-type instruction (-4096 to 4095, must be even)"
)
return ImmediateOperand(imd_type="B", value=value)
# U-type instructions (20-bit upper immediate)
elif any(x in mnemonic for x in ["lui", "auipc"]):
if not 0 <= value <= 1048575:
raise ValueError(
f"Immediate value {value} out of range for U-type "
f"instruction (0 to 1048575)"
)
return ImmediateOperand(imd_type="U", value=value)
# J-type instructions (21-bit signed immediate for jumps, must be even)
elif any(x in mnemonic for x in ["jal"]):
if not -1048576 <= value <= 1048575 or value % 2 != 0:
raise ValueError(
f"Immediate value {value} out of range or not even "
f"for J-type instruction (-1048576 to 1048575, must be even)"
)
return ImmediateOperand(imd_type="J", value=value)
# Vector instructions might have specific immediate ranges
elif mnemonic.startswith("v"):
# Handle vector specific immediates (implementation specific)
return ImmediateOperand(imd_type="V", value=value)
# Default case - no specific validation
return ImmediateOperand(imd_type="int", value=value)
return immediate
def get_full_reg_name(self, register):
"""Return one register name string including all attributes"""
if register.prefix and register.name:
return register.prefix + str(register.name)
return str(register.name)
def normalize_imd(self, imd):
"""Normalize immediate to decimal based representation"""
if isinstance(imd, IdentifierOperand):
return imd
elif imd.value is not None:
if isinstance(imd.value, str):
# hex or bin, return decimal
return int(imd.value, 0)
else:
return imd.value
# identifier
return imd
def parse_register(self, register_string):
"""
Parse register string and return register dictionary.
:param str register_string: register representation as string
:returns: dict with register info
"""
# Remove any leading/trailing whitespace
register_string = register_string.strip()
# Check for integer registers (x0-x31)
x_match = re.match(r"^x([0-9]|[1-2][0-9]|3[0-1])$", register_string)
if x_match:
reg_num = int(x_match.group(1))
return {
"class": "register",
"register": {"prefix": "x", "name": str(reg_num)},
}
# Check for floating-point registers (f0-f31)
f_match = re.match(r"^f([0-9]|[1-2][0-9]|3[0-1])$", register_string)
if f_match:
reg_num = int(f_match.group(1))
return {
"class": "register",
"register": {"prefix": "f", "name": str(reg_num)},
}
# Check for vector registers (v0-v31)
v_match = re.match(r"^v([0-9]|[1-2][0-9]|3[0-1])$", register_string)
if v_match:
reg_num = int(v_match.group(1))
return {
"class": "register",
"register": {"prefix": "v", "name": str(reg_num)},
}
# Check for ABI names
abi_names = {
"zero": 0,
"ra": 1,
"sp": 2,
"gp": 3,
"tp": 4,
"t0": 5,
"t1": 6,
"t2": 7,
"s0": 8,
"fp": 8,
"s1": 9,
"a0": 10,
"a1": 11,
"a2": 12,
"a3": 13,
"a4": 14,
"a5": 15,
"a6": 16,
"a7": 17,
"s2": 18,
"s3": 19,
"s4": 20,
"s5": 21,
"s6": 22,
"s7": 23,
"s8": 24,
"s9": 25,
"s10": 26,
"s11": 27,
"t3": 28,
"t4": 29,
"t5": 30,
"t6": 31,
}
if register_string in abi_names:
return {
"class": "register",
"register": {"prefix": "", "name": register_string},
}
# If no match is found
return None
def is_gpr(self, register):
"""Check if register is a general purpose register"""
# Integer registers: x0-x31 or ABI names
if register.prefix == "x":
return True
if not register.prefix and register.name in ["zero", "ra", "sp", "gp", "tp"]:
return True
if not register.prefix and register.name[0] in ["t", "a", "s"]:
return True
return False
def is_vector_register(self, register):
"""Check if register is a vector register"""
# Vector registers: v0-v31
if register.prefix == "v":
return True
return False
def is_flag_dependend_of(self, flag_a, flag_b):
"""Check if ``flag_a`` is dependent on ``flag_b``"""
# RISC-V doesn't have explicit flags like x86 or AArch64
return flag_a.name == flag_b.name
def is_reg_dependend_of(self, reg_a, reg_b):
"""Check if ``reg_a`` is dependent on ``reg_b``"""
if not isinstance(reg_a, Operand):
reg_a = RegisterOperand(name=reg_a["name"])
# Get canonical register names
reg_a_canonical = self._get_canonical_reg_name(reg_a)
reg_b_canonical = self._get_canonical_reg_name(reg_b)
# Same register type and number means dependency
return reg_a_canonical == reg_b_canonical
def _get_canonical_reg_name(self, register):
"""Get the canonical form of a register (x-form for integer, f-form for FP)"""
# If already in canonical form (x# or f#)
if register.prefix in ["x", "f", "v"] and register.name.isdigit():
return f"{register.prefix}{register.name}"
# ABI name mapping for integer registers
abi_to_x = {
"zero": "x0",
"ra": "x1",
"sp": "x2",
"gp": "x3",
"tp": "x4",
"t0": "x5",
"t1": "x6",
"t2": "x7",
"s0": "x8",
"s1": "x9",
"a0": "x10",
"a1": "x11",
"a2": "x12",
"a3": "x13",
"a4": "x14",
"a5": "x15",
"a6": "x16",
"a7": "x17",
"s2": "x18",
"s3": "x19",
"s4": "x20",
"s5": "x21",
"s6": "x22",
"s7": "x23",
"s8": "x24",
"s9": "x25",
"s10": "x26",
"s11": "x27",
"t3": "x28",
"t4": "x29",
"t5": "x30",
"t6": "x31",
}
# For integer register ABI names
name = register.name.lower()
if name in abi_to_x:
return abi_to_x[name]
# For FP register ABI names like fa0, fs1, etc.
if name.startswith("f") and len(name) > 1:
if name[1] == "a": # fa0-fa7
idx = int(name[2:])
return f"f{idx + 10}"
elif name[1] == "s": # fs0-fs11
idx = int(name[2:])
if idx <= 1:
return f"f{idx + 8}"
else:
return f"f{idx + 16}"
elif name[1] == "t": # ft0-ft11
idx = int(name[2:])
if idx <= 7:
return f"f{idx}"
else:
return f"f{idx + 20}"
# Return as is if no mapping found
return f"{register.prefix}{register.name}"
def get_reg_type(self, register):
"""Get register type"""
# Return register prefix if exists
if register.prefix:
return register.prefix
# Determine type from ABI name
name = register.name.lower()
if name in ["zero", "ra", "sp", "gp", "tp"] or name[0] in ["t", "a", "s"]:
return "x" # Integer register
elif name.startswith("f"):
return "f" # Floating point register
elif name.startswith("csr"):
return "csr" # Control and Status Register
return "unknown"
def normalize_instruction_form(self, instruction_form, isa_model, arch_model):
"""
Normalize instruction form for RISC-V instructions.
:param instruction_form: instruction form to normalize
:param isa_model: ISA model to use for normalization
:param arch_model: architecture model to use for normalization
"""
if instruction_form.normalized:
return
if instruction_form.mnemonic is None:
instruction_form.normalized = True
return
# Normalize the mnemonic if needed
if instruction_form.mnemonic:
# Handle any RISC-V specific mnemonic normalization
# For example, convert aliases or pseudo-instructions to their base form
pass
# Normalize the operands if needed
for i, operand in enumerate(instruction_form.operands):
if isinstance(operand, ImmediateOperand):
# Normalize immediate operands
instruction_form.operands[i] = self.normalize_imd(operand)
elif isinstance(operand, RegisterOperand):
# Convert register names to canonical form if needed
pass
instruction_form.normalized = True
def get_regular_source_operands(self, instruction_form):
"""Get source operand of given instruction form assuming regular src/dst behavior."""
# For RISC-V, the first operand is typically the destination,
# and the rest are sources
if len(instruction_form.operands) == 1:
return [instruction_form.operands[0]]
else:
return [op for op in instruction_form.operands[1:]]
def get_regular_destination_operands(self, instruction_form):
"""Get destination operand of given instruction form assuming regular src/dst behavior."""
# For RISC-V, the first operand is typically the destination
if len(instruction_form.operands) == 1:
return []
else:
return instruction_form.operands[:1]
def process_immediate_operand(self, operand):
"""Process immediate operands, converting them to ImmediateOperand objects"""
if isinstance(operand, (int, str)):
# For raw integer values or string immediates
return ImmediateOperand(
imd_type="int",
value=str(operand) if isinstance(operand, int) else operand,
)
elif isinstance(operand, dict) and "imd" in operand:
# For immediate operands from instruction definitions
return ImmediateOperand(
imd_type=operand["imd"],
value=operand.get("value"),
identifier=operand.get("identifier"),
shift=operand.get("shift"),
)
else:
# For any other immediate format
return ImmediateOperand(imd_type="int", value=str(operand))