""" The base class for RISCV ASM statements is :py:class:`Statement`. It is inherited by :py:class:`Comment`, :py:class:`Label` and :py:class:`Instruction`. In turn, :py:class:`Instruction` is inherited by :py:class:`Instru3A` (for regular non-branching 3-address instructions), :py:class:`AbsoluteJump` and :py:class:`ConditionalJump`. """ from dataclasses import dataclass from typing import (List, Dict, TypeVar) from Lib.Operands import (Operand, Renamer, Temporary, Condition) from Lib.Errors import MiniCInternalError def regset_to_string(registerset) -> str: """Utility function: pretty-prints a set of locations.""" return "{" + ",".join(str(x) for x in registerset) + "}" # Temporary until we can use Typing.Self in python 3.11 TStatement = TypeVar("TStatement", bound="Statement") @dataclass(unsafe_hash=True) class Statement: """A Statement, which is an instruction, a comment or a label.""" def defined(self) -> List[Operand]: """Operands defined (written) in this instruction""" return [] def used(self) -> List[Operand]: """Operands used (read) in this instruction""" return [] def substitute(self: TStatement, subst: Dict[Operand, Operand]) -> TStatement: """Return a new instruction, cloned from this one, replacing operands that appear as key in subst by their value.""" raise Exception( "substitute: Operands {} are not present in instruction {}" .format(subst, self)) def with_args(self: TStatement, new_args: List[Operand]) -> TStatement: """Return a new instruction, cloned from this one, where operands have been replaced by new_args.""" raise Exception( "substitute: Operands {} are not present in instruction {}" .format(new_args, self)) def printIns(self, stream): """ Print the statement on the given output. Should never be called on the base class. """ raise NotImplementedError @dataclass(unsafe_hash=True) class Comment(Statement): """A comment.""" comment: str def __str__(self): # use only for print_dot ! return "# {}".format(self.comment) def printIns(self, stream): print(' # ' + self.comment, file=stream) @dataclass(unsafe_hash=True) class Label(Statement, Operand): """A label is both a Statement and an Operand.""" name: str def __str__(self): return ("lbl_{}".format(self.name)) def __repr__(self): return ("{}".format(self.name)) def printIns(self, stream): print(str(self) + ':', file=stream) @dataclass(init=False) class Instruction(Statement): ins: str _read_only: bool def is_read_only(self): """ True if the instruction only reads from its operands. Otherwise, the first operand is considered as the destination and others are source. """ return self._read_only def rename(self, renamer: Renamer) -> None: raise NotImplementedError def args(self) -> List[Operand]: """List of operands the instruction takes""" raise NotImplementedError def defined(self): if self.is_read_only(): defs = [] else: defs = [self.args()[0]] return defs def used(self) -> List[Operand]: if self.is_read_only(): uses = self.args() else: uses = self.args()[1:] return uses def __str__(self): s = self.ins first = True for arg in self.args(): if first: s += ' ' + str(arg) first = False else: s += ', ' + str(arg) return s def __hash__(self): return hash((self.ins, *self.args())) def printIns(self, stream): """Print the instruction on the given output.""" print(' ', str(self), file=stream) @dataclass(init=False) class Instru3A(Instruction): _args: List[Operand] def __init__(self, ins, *args: Operand): # convention is to use lower-case in RISCV self.ins = ins.lower() self._args = list(args) self._read_only = (self.ins == "call" or self.ins == "ld" or self.ins == "lw" or self.ins == "lb") if (self.ins.startswith("b") or self.ins == "j"): raise MiniCInternalError def args(self): return self._args def rename(self, renamer: Renamer): old_replaced = dict() for i, arg in enumerate(self._args): if isinstance(arg, Temporary): if i == 0 and not self.is_read_only(): old_replaced[arg] = renamer.replace(arg) new_t = renamer.fresh(arg) elif arg in old_replaced.keys(): new_t = old_replaced[arg] else: new_t = renamer.replace(arg) self._args[i] = new_t def substitute(self, subst: Dict[Operand, Operand]): for op in subst: if op not in self.args(): raise Exception( "substitute: Operand {} is not present in instruction {}" .format(op, self)) args = [subst.get(arg, arg) if isinstance(arg, Temporary) else arg for arg in self.args()] return Instru3A(self.ins, *args) def with_args(self, new_args: List[Operand]): if len(new_args) != len(self._args): raise Exception( "substitute: Expected {} operands for {}, got {}." .format(len(self._args), self, new_args)) return Instru3A(self.ins, *new_args) def __hash__(self): return hash(super) @dataclass(init=False) class AbsoluteJump(Instruction): """ An Absolute Jump is a specific kind of instruction""" ins = "j" label: Label _read_only = True def __init__(self, label: Label): self.label = label def args(self) -> List[Operand]: return [self.label] def rename(self, renamer: Renamer): pass def substitute(self, subst: Dict[Operand, Operand]): if subst != {}: raise Exception( "substitute: No possible substitution on instruction {}" .format(self)) return self def with_args(self, new_args: List[Operand]): if new_args != self.args(): raise Exception( "substitute: No possible substitution on instruction {}. Old args={}, new args={}" .format(self, self.args(), new_args)) return self def __hash__(self): return hash(super) def targets(self) -> List[Label]: """Return the labels targetted by the AbsoluteJump.""" return [self.label] @dataclass(init=False) class ConditionalJump(Instruction): """ A Conditional Jump is a specific kind of instruction""" cond: Condition label: Label op1: Operand op2: Operand _read_only = True def __init__(self, cond: Condition, op1: Operand, op2: Operand, label: Label): self.cond = cond self.label = label self.op1 = op1 self.op2 = op2 self.ins = str(self.cond) def args(self): return [self.op1, self.op2, self.label] def rename(self, renamer: Renamer): if isinstance(self.op1, Temporary): self.op1 = renamer.replace(self.op1) if isinstance(self.op2, Temporary): self.op2 = renamer.replace(self.op2) def substitute(self, subst: Dict[Operand, Operand]): for op in subst: if op not in self.args(): raise Exception( "substitute: Operand {} is not present in instruction {}" .format(op, self)) op1 = subst.get(self.op1, self.op1) if isinstance(self.op1, Temporary) \ else self.op1 op2 = subst.get(self.op2, self.op2) if isinstance(self.op2, Temporary) \ else self.op2 return ConditionalJump(self.cond, op1, op2, self.label) def with_args(self, new_args: List[Operand]): if len(new_args) != 3: raise Exception( "substitute: Expected 3 operands for {}, got {}." .format(self, new_args)) assert isinstance(new_args[2], Label) label: Label = new_args[2] return ConditionalJump(self.cond, new_args[0], new_args[1], label) def __hash__(self): return hash(super)