automated commit TP4 + Doc
This commit is contained in:
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user