automated commit TP4 + Doc

This commit is contained in:
Rémi Di Guardia
2022-10-04 20:43:34 +02:00
parent 60757b5e9f
commit b31591657e
110 changed files with 24201 additions and 23 deletions
+77
View File
@@ -0,0 +1,77 @@
"""
This file defines the base class :py:class:`Allocator`
and the naïve implementation :py:class:`NaiveAllocator`.
"""
from Lib.Operands import Temporary, Operand, DataLocation, GP_REGS
from Lib.Statement import Instruction
from Lib.Errors import AllocationError
from Lib.FunctionData import FunctionData
from typing import Dict, List
class Allocator():
"""General base class for Naive, AllInMem and Smart Allocators.
Replace all temporaries in the code with actual data locations.
Allocation is done in two steps:
- First, :py:meth:`prepare` is responsible for calling
:py:meth:`Lib.Operands.TemporaryPool.set_temp_allocation`
with a mapping from temporaries to where they should actually be stored
(in registers or in memory).
- Then, :py:meth:`replace` is called for each instruction in order to
replace the temporary operands with the previously assigned locations
(and possibly add some instructions before or after).
Concretely, it returns a list of instructions that should replace the original
instruction. The actual iteration over all the instructions is handled transparently
by :py:meth:`Lib.LinearCode.LinearCode.iter_statements`.
"""
_fdata: FunctionData
def __init__(self, fdata: FunctionData):
self._fdata = fdata
def prepare(self) -> None: # pragma: no cover
pass
def replace(self, instr: Instruction) -> List[Instruction]:
"""Transform an instruction with temporaries into a list of instructions."""
return [instr]
def rewriteCode(self, listcode) -> None:
"""Modify the code to replace temporaries with
registers or memory locations.
"""
listcode.iter_statements(self.replace)
class NaiveAllocator(Allocator):
"""Naive Allocator: try to assign a register to each temporary,
fails if there are more temporaries than registers.
"""
def replace(self, old_instr: Instruction) -> List[Instruction]:
"""Replace Temporary operands with the corresponding allocated Register."""
subst: Dict[Operand, Operand] = {}
for arg in old_instr.args():
if isinstance(arg, Temporary):
subst[arg] = arg.get_alloced_loc()
new_instr = old_instr.substitute(subst)
return [new_instr]
def prepare(self) -> None:
"""Allocate all temporaries to registers.
Fail if there are too many temporaries."""
regs = list(GP_REGS) # Get a writable copy
temp_allocation: Dict[Temporary, DataLocation] = dict()
for tmp in self._fdata._pool.get_all_temps():
try:
reg = regs.pop()
except IndexError:
raise AllocationError(
"Too many temporaries ({}) for the naive allocation, sorry."
.format(len(self._fdata._pool.get_all_temps())))
temp_allocation[tmp] = reg
self._fdata._pool.set_temp_allocation(temp_allocation)
+179
View File
@@ -0,0 +1,179 @@
"""
This file defines the base class :py:class:`FunctionData`,
containing metadata on a RiscV function, as well as utility
functions common to the different intermediate representations.
"""
from typing import (List, Callable, TypeVar)
from Lib.Errors import AllocationError
from Lib.Operands import (
Offset, Temporary, TemporaryPool,
S, T, FP)
from Lib.Statement import (Statement, Instruction, Label, Comment)
class FunctionData:
"""
Stores some metadata on a RiscV function:
name of the function, label names, temporary variables
(using :py:class:`Lib.Operands.TemporaryPool`),
and div_by_zero label.
This class is usually used indirectly through the
different intermediate representations we work with,
such as :py:attr:`Lib.LinearCode.LinearCode.fdata`.
"""
_nblabel: int
_dec: int
_pool: TemporaryPool
_name: str
_label_div_by_zero: Label
def __init__(self, name: str):
self._nblabel = -1
self._dec = 0
self._pool = TemporaryPool()
self._name = name
self._label_div_by_zero = self.fresh_label("div_by_zero")
def get_name(self) -> str:
"""Return the name of the function."""
return self._name
def fresh_tmp(self) -> Temporary:
"""
Return a new fresh Temporary,
which is added to the pool.
"""
return self._pool.fresh_tmp()
def fresh_offset(self) -> Offset:
"""
Return a new offset in the memory stack.
Offsets are decreasing relative to FP.
"""
self._dec = self._dec + 1
# For ld or sd, an offset on 12 signed bits is expected
# Raise an error if the offset is too big
if -8 * self._dec < - 2 ** 11:
raise AllocationError(
"Offset given by the allocation too big to be manipulated ({}), sorry."
.format(self._dec))
return Offset(FP, -8 * self._dec)
def get_offset(self) -> int:
"""
Return the current offset in the memory stack.
"""
return self._dec
def _fresh_label_name(self, name) -> str:
"""
Return a new unique label name based on the given string.
"""
self._nblabel = self._nblabel + 1
return name + "_" + str(self._nblabel) + "_" + self._name
def fresh_label(self, name) -> Label:
"""
Return a new label, with a unique name based on the given string.
"""
return Label(self._fresh_label_name(name))
def get_label_div_by_zero(self) -> Label:
return self._label_div_by_zero
_T = TypeVar("_T", bound=Statement)
def _iter_statements(
listIns: List[_T], f: Callable[[_T], List[_T]]) -> List[_T | Comment]:
"""Iterate over instructions.
For each real instruction i (not label or comment), replace it
with the list of instructions given by f(i).
"""
newListIns: List[_T | Comment] = []
for old_i in listIns:
# Do nothing for label or comment
if not isinstance(old_i, Instruction):
newListIns.append(old_i)
continue
new_i_list = f(old_i)
# Otherwise, replace the instruction by the list
# returned by f, with comments giving the replacement
newListIns.append(Comment("Replaced " + str(old_i)))
newListIns.extend(new_i_list)
return newListIns
def _print_code(listIns: List, fdata: FunctionData, output,
init_label=None, fin_label=None, fin_div0=False, comment=None) -> None:
"""
Please use print_code from LinearCode or CFG, not directly this one.
Print the instructions from listIns, forming fdata, on output.
If init_label is given, add an initial jump to it before the generated code.
If fin_label is given, add it after the generated code.
If fin_div0 is given equal to true, add the code for returning an
error when dividing by 0, at the very end.
"""
# compute size for the local stack - do not forget to align by 16
fo = fdata.get_offset() # allocate enough memory for stack
cardoffset = 8 * (fo + (0 if fo % 2 == 0 else 1)) + 16
output.write(
"##Automatically generated RISCV code, MIF08 & CAP\n")
if comment is not None:
output.write("##{} version\n".format(comment))
output.write("\n\n##prelude\n")
# We put an li t0, cardoffset in case it is greater than 2**11
# We use t0 because it is caller-saved
output.write("""
.text
.globl {0}
{0}:
li t0, {1}
sub sp, sp, t0
sd ra, 0(sp)
sd fp, 8(sp)
add fp, sp, t0
""".format(fdata.get_name(), cardoffset))
# Stack in RiscV is managed with SP
if init_label is not None:
# Add a jump to init_label before the generated code.
output.write("""
j {0}
""".format(init_label))
output.write("\n\n##Generated Code\n")
# Generated code
for i in listIns:
i.printIns(output)
output.write("\n\n##postlude\n")
if fin_label is not None:
# Add fin_label after the generated code.
output.write("""
{0}:
""".format(fin_label))
# We put an li t0, cardoffset in case it is greater than 2**11
# We use t0 because it is caller-saved
output.write("""
ld ra, 0(sp)
ld fp, 8(sp)
li t0, {0}
add sp, sp, t0
ret
""".format(cardoffset))
if fin_div0:
# Add code for division by 0 at the end.
output.write("""
{0}:
la a0, {0}_msg
call println_string
li a0, 1
call exit
""".format(fdata._label_div_by_zero))
# Add the data for the message of the division by 0
output.write("""
{0}_msg: .string "Division by 0"
""".format(fdata._label_div_by_zero))
+101
View File
@@ -0,0 +1,101 @@
"""
CAP, CodeGeneration, LinearCode API
Classes for a RiscV linear code.
"""
from typing import List
from Lib.Operands import (A0, Function, DataLocation)
from Lib.Statement import (
Instru3A, AbsoluteJump, ConditionalJump, Comment, Label
)
from Lib.RiscV import (mv, call)
from Lib.FunctionData import (FunctionData, _iter_statements, _print_code)
CodeStatement = Comment | Label | Instru3A | AbsoluteJump | ConditionalJump
class LinearCode:
"""
Representation of a RiscV program as a list of instructions.
:py:meth:`add_instruction` is repeatedly called in the codegen visitor
to build a complete list of RiscV instructions for the source program.
The :py:attr:`fdata` member variable contains some meta-information
on the program, for instance to allocate a new temporary.
See :py:class:`Lib.FunctionData.FunctionData`.
For debugging purposes, :py:meth:`print_code` allows to print
the RiscV program to a file.
"""
"""
The :py:attr:`fdata` member variable contains some meta-information
on the program, for instance to allocate a new temporary.
See :py:class:`Lib.FunctionData.FunctionData`.
"""
fdata: FunctionData
_listIns: List[CodeStatement]
def __init__(self, name: str):
self._listIns = []
self.fdata = FunctionData(name)
def add_instruction(self, i: CodeStatement) -> None:
"""
Utility function to add an instruction in the program.
See also :py:mod:`Lib.RiscV` to generate relevant instructions.
"""
self._listIns.append(i)
def iter_statements(self, f) -> None:
"""Iterate over instructions.
For each real instruction (not label or comment), call f,
which must return either None or a list of instruction. If it
returns None, nothing happens. If it returns a list, then the
instruction is replaced by this list.
"""
self._listIns = _iter_statements(self._listIns, f)
def get_instructions(self) -> List[CodeStatement]:
"""Return the list of instructions of the program."""
return self._listIns
# each instruction has its own "add in list" version
def add_label(self, s: Label) -> None:
"""Add a label in the program."""
return self.add_instruction(s)
def add_comment(self, s: str) -> None:
"""Add a comment in the program."""
self.add_instruction(Comment(s))
def add_instruction_PRINTLN_INT(self, reg: DataLocation) -> None:
"""Print integer value, with newline. (see Expand)"""
# a print instruction generates the temp it prints.
self.add_instruction(mv(A0, reg))
self.add_instruction(call(Function('println_int')))
def __str__(self):
return '\n'.join(map(str, self._listIns))
def print_code(self, output, comment=None) -> None:
"""Outputs the RiscV program as text to a file at the given path."""
_print_code(self._listIns, self.fdata, output, init_label=None,
fin_label=None, fin_div0=True, comment=comment)
def print_dot(self, filename: str, DF=None, view=False) -> None: # pragma: no cover
"""Outputs the RiscV program as graph to a file at the given path."""
# import graphviz here so that students who don't have it can still work on lab4
from graphviz import Digraph
graph = Digraph()
# nodes
content = ""
for i in self._listIns:
content += str(i) + "\\l"
graph.node("Code", label=content, shape='rectangle')
# no edges
graph.render(filename, view=view)
+265
View File
@@ -0,0 +1,265 @@
"""
This file defines the base class :py:class:`Operand`
and its subclasses for different operands: :py:class:`Condition`,
:py:class:`DataLocation` and :py:class:`Function`.
The class :py:class:`DataLocation` itself has subclasses:
:py:class:`Register`, :py:class:`Offset` for address in memory,
:py:class:`Immediate` for constants and :py:class:`Temporary`
for location not yet allocated.
This file also define shortcuts for registers in RISCV.
"""
from typing import Dict, List
from MiniCParser import MiniCParser
from Lib.Errors import MiniCInternalError
class Operand():
pass
# signed version for riscv
all_ops = ['blt', 'bgt', 'beq', 'bne', 'ble', 'bge', 'beqz', 'bnez']
opdict = {MiniCParser.LT: 'blt', MiniCParser.GT: 'bgt',
MiniCParser.LTEQ: 'ble', MiniCParser.GTEQ: 'bge',
MiniCParser.NEQ: 'bne', MiniCParser.EQ: 'beq'}
opnot_dict = {'bgt': 'ble',
'bge': 'blt',
'blt': 'bge',
'ble': 'bgt',
'beq': 'bne',
'bne': 'beq',
'beqz': 'bnez',
'bnez': 'beqz'}
class Condition(Operand):
"""Condition, i.e. comparison operand for a CondJump.
Example usage :
- Condition('beq') = branch if equal.
- Condition(MiniCParser.LT) = branch if lower than.
- ...
The constructor's argument shall be a string in the list all_ops, or a
comparison operator in MiniCParser.LT, MiniCParser.GT, ... (one of the keys
in opdict).
A 'negate' method allows getting the negation of this condition.
"""
_op: str
def __init__(self, optype):
if optype in opdict:
self._op = opdict[optype]
elif str(optype) in all_ops:
self._op = str(optype)
else:
raise MiniCInternalError("Unsupported comparison operator %s", optype)
def negate(self) -> 'Condition':
"""Return the opposite condition."""
return Condition(opnot_dict[self._op])
def __str__(self):
return self._op
class Function(Operand):
"""Operand for build-in function call."""
_name: str
def __init__(self, name: str):
self._name = name
def __str__(self):
return self._name
class DataLocation(Operand):
""" A Data Location is either a register, a temporary
or a place in memory (offset).
"""
pass
# map for register shortcuts
reg_map = dict([(0, 'zero'), (1, 'ra'), (2, 'sp')] + # no (3, 'gp') nor (4, 'tp')
[(i+5, 't'+str(i)) for i in range(3)] +
[(8, 'fp'), (9, 's1')] +
[(i+10, 'a'+str(i)) for i in range(8)] +
[(i+18, 's'+str(i+2)) for i in range(10)] +
[(i+28, 't'+str(i+3)) for i in range(4)])
class Register(DataLocation):
""" A (physical) register."""
_number: int
def __init__(self, number: int):
self._number = number
def __repr__(self):
if self._number not in reg_map:
raise Exception("Register number %d should not be used", self._number)
else:
return ("{}".format(reg_map[self._number]))
def __eq__(self, other):
return isinstance(other, Register) and self._number == other._number
def __hash__(self):
return self._number
# Shortcuts for registers in RISCV
# Only integer registers
ZERO = Register(0)
RA = Register(1)
SP = Register(2)
GP = Register(3) # Register not used for this course
TP = Register(4) # Register not used for this course
A = tuple(Register(i + 10) for i in range(8))
S = tuple(Register(i + 8) for i in range(2)) + tuple(Register(i + 18) for i in range(10))
T = tuple(Register(i + 5) for i in range(3)) + tuple(Register(i + 28) for i in range(4))
A0 = A[0] # function args/return Values: A0, A1
A1 = A[1]
FP = S[0] # Frame Pointer = Saved register 0
# General purpose registers, usable for the allocator
GP_REGS = S[4:] + T # s0, s1, s2 and s3 are special
class Offset(DataLocation):
""" Offset = address in memory computed with base + offset."""
_basereg: Register
_offset: int
def __init__(self, basereg: Register, offset: int):
self._basereg = basereg
self._offset = offset
def __repr__(self):
return ("{}({})".format(self._offset, self._basereg))
def get_offset(self) -> int:
"""Return the value of the offset."""
return self._offset
class Immediate(DataLocation):
"""Immediate operand (integer)."""
_val: int
def __init__(self, val):
self._val = val
def __str__(self):
return str(self._val)
class Temporary(DataLocation):
"""Temporary, a location that has not been allocated yet.
It will later be mapped to a physical register (Register) or to a memory location (Offset).
"""
_number: int
_pool: 'TemporaryPool'
def __init__(self, number: int, pool: 'TemporaryPool'):
self._number = number
self._pool = pool
def __repr__(self):
return ("temp_{}".format(str(self._number)))
def get_alloced_loc(self) -> DataLocation:
"""Return the DataLocation allocated to this Temporary."""
return self._pool.get_alloced_loc(self)
class TemporaryPool:
"""Manage a pool of temporaries."""
_all_temps: List[Temporary]
_current_num: int
_allocation: Dict[Temporary, DataLocation]
def __init__(self):
self._all_temps = []
self._current_num = 0
self._allocation = dict()
def get_all_temps(self) -> List[Temporary]:
"""Return all the temporaries of the pool."""
return self._all_temps
def get_alloced_loc(self, t: Temporary) -> DataLocation:
"""Get the actual DataLocation allocated for the temporary t."""
return self._allocation[t]
def add_tmp(self, t: Temporary):
"""Add a temporary to the pool."""
self._all_temps.append(t)
self._allocation[t] = t # While no allocation, return the temporary itself
def set_temp_allocation(self, allocation: Dict[Temporary, DataLocation]) -> None:
"""Give a mapping from temporaries to actual registers.
The argument allocation must be a dict from Temporary to
DataLocation other than Temporary (typically Register or Offset).
Typing enforces that keys are Temporary and values are Datalocation.
We check the values are indeed not Temporary.
"""
for v in allocation.values():
assert not isinstance(v, Temporary), (
"Incorrect allocation scheme: value " +
str(v) + " is a Temporary.")
self._allocation = allocation
def fresh_tmp(self) -> Temporary:
"""Give a new fresh Temporary and add it to the pool."""
t = Temporary(self._current_num, self)
self._current_num += 1
self.add_tmp(t)
return t
class Renamer:
"""Manage a renaming of temporaries."""
_pool: TemporaryPool
_env: Dict[Temporary, Temporary]
def __init__(self, pool: TemporaryPool):
self._pool = pool
self._env = dict()
def fresh(self, t: Temporary) -> Temporary:
"""Give a fresh rename for a Temporary."""
new_t = self._pool.fresh_tmp()
self._env[t] = new_t
return new_t
def replace(self, t: Temporary) -> Temporary:
"""Give the rename for a Temporary (which is itself if it is not renamed)."""
return self._env.get(t, t)
def defined(self, t: Temporary) -> bool:
"""True if the Temporary is renamed."""
return t in self._env
def copy(self):
"""Give a copy of the Renamer."""
r = Renamer(self._pool)
r._env = self._env.copy()
return r
+89
View File
@@ -0,0 +1,89 @@
"""
MIF08, CAP, CodeGeneration, RiscV API
Functions to define instructions.
"""
from Lib.Errors import MiniCInternalError
from Lib.Operands import (
Condition, Immediate, Operand, Function, ZERO)
from Lib.Statement import (Instru3A, AbsoluteJump, ConditionalJump, Label)
def call(function: Function) -> Instru3A:
"""Function call."""
return Instru3A('call', function)
def jump(label: Label) -> AbsoluteJump:
"""Unconditional jump to label."""
return AbsoluteJump(label)
def conditional_jump(label: Label, op1: Operand, cond: Condition, op2: Operand):
"""Add a conditional jump to the code.
This is a wrapper around bge, bgt, beq, ... c is a Condition, like
Condition('bgt'), Condition(MiniCParser.EQ), ...
"""
op2 = op2 if op2 != Immediate(0) else ZERO
return ConditionalJump(cond=cond, op1=op1, op2=op2, label=label)
def add(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
if isinstance(sr2orimm7, Immediate):
return Instru3A("addi", dr, sr1, sr2orimm7)
else:
return Instru3A("add", dr, sr1, sr2orimm7)
def mul(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
if isinstance(sr2orimm7, Immediate):
raise MiniCInternalError("Cant multiply by an immediate")
else:
return Instru3A("mul", dr, sr1, sr2orimm7)
def div(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
if isinstance(sr2orimm7, Immediate):
raise MiniCInternalError("Cant divide by an immediate")
else:
return Instru3A("div", dr, sr1, sr2orimm7)
def rem(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
if isinstance(sr2orimm7, Immediate):
raise MiniCInternalError("Cant divide by an immediate")
return Instru3A("rem", dr, sr1, sr2orimm7)
def sub(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
if isinstance(sr2orimm7, Immediate):
raise MiniCInternalError("Cant substract by an immediate")
return Instru3A("sub", dr, sr1, sr2orimm7)
def land(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
return Instru3A("and", dr, sr1, sr2orimm7)
def lor(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
return Instru3A("or", dr, sr1, sr2orimm7)
def xor(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A: # pragma: no cover
return Instru3A("xor", dr, sr1, sr2orimm7)
def li(dr: Operand, imm7: Immediate) -> Instru3A:
return Instru3A("li", dr, imm7)
def mv(dr: Operand, sr: Operand) -> Instru3A:
return Instru3A("mv", dr, sr)
def ld(dr: Operand, mem: Operand) -> Instru3A:
return Instru3A("ld", dr, mem)
def sd(sr: Operand, mem: Operand) -> Instru3A:
return Instru3A("sd", sr, mem)
+242
View File
@@ -0,0 +1,242 @@
"""
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):
"""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]:
return []
def used(self) -> List[Operand]:
return []
def substitute(self: TStatement, subst: Dict[Operand, Operand]) -> TStatement:
raise Exception(
"substitute: Operands {} are not present in instruction {}"
.format(subst, self))
def printIns(self, stream):
"""
Print the statement on the 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]:
raise NotImplementedError
def defined(self):
if self.is_read_only():
defs = []
else:
defs = [self.args()[0]]
return defs
def used(self):
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 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 __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):
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 __hash__(self):
return hash(super)
def targets(self) -> List[Label]:
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 __hash__(self):
return hash(super)
View File