CAP/MiniC/Lib/Operands.py
2024-10-06 19:58:11 +02:00

280 lines
7.6 KiB
Python

"""
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(f"Unsupported comparison operator {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 MiniCInternalError(f"Register number {self._number} should not be used")
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
ZERO = Register(0)
#:
RA = Register(1)
#:
SP = Register(2)
#: Register not used for this course
GP = Register(3)
#: Register not used for this course
TP = Register(4)
#:
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]
#: Frame Pointer = Saved register 0
FP = S[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