Add a Stack() class for specifying operand constraints for values on the stack. Add encoding recipes for RISC-V spill and fill instructions. Don't implement the encoding recipe functions yet since we don't have the stack slot layout yet.
355 lines
12 KiB
Python
355 lines
12 KiB
Python
"""
|
|
Register set definitions
|
|
------------------------
|
|
|
|
Each ISA defines a separate register set that is used by the register allocator
|
|
and the final binary encoding of machine code.
|
|
|
|
The CPU registers are first divided into disjoint register banks, represented
|
|
by a `RegBank` instance. Registers in different register banks never interfere
|
|
with each other. A typical CPU will have a general purpose and a floating point
|
|
register bank.
|
|
|
|
A register bank consists of a number of *register units* which are the smallest
|
|
indivisible units of allocation and interference. A register unit doesn't
|
|
necessarily correspond to a particular number of bits in a register, it is more
|
|
like a placeholder that can be used to determine of a register is taken or not.
|
|
|
|
The register allocator works with *register classes* which can allocate one or
|
|
more register units at a time. A register class allocates more than one
|
|
register unit at a time when its registers are composed of smaller allocatable
|
|
units. For example, the ARM double precision floating point registers are
|
|
composed of two single precision registers.
|
|
"""
|
|
from __future__ import absolute_import
|
|
from . import is_power_of_two, next_power_of_two
|
|
|
|
|
|
try:
|
|
from typing import Sequence, Tuple, List, Dict, Any, TYPE_CHECKING # noqa
|
|
if TYPE_CHECKING:
|
|
from .isa import TargetISA # noqa
|
|
# A tuple uniquely identifying a register class inside a register bank.
|
|
# (count, width, start)
|
|
RCTup = Tuple[int, int, int]
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
# The number of 32-bit elements in a register unit mask
|
|
MASK_LEN = 3
|
|
|
|
# The maximum total number of register units allowed.
|
|
# This limit can be raised by also adjusting the RegUnitMask type in
|
|
# src/isa/registers.rs.
|
|
MAX_UNITS = MASK_LEN * 32
|
|
|
|
|
|
class RegBank(object):
|
|
"""
|
|
A register bank belonging to an ISA.
|
|
|
|
A register bank controls a set of *register units* disjoint from all the
|
|
other register banks in the ISA. The register units are numbered uniquely
|
|
within the target ISA, and the units in a register bank form a contiguous
|
|
sequence starting from a sufficiently aligned point that their low bits can
|
|
be used directly when encoding machine code instructions.
|
|
|
|
Register units can be given generated names like `r0`, `r1`, ..., or a
|
|
tuple of special register unit names can be provided.
|
|
|
|
:param name: Name of this register bank.
|
|
:param doc: Documentation string.
|
|
:param units: Number of register units.
|
|
:param prefix: Prefix for generated unit names.
|
|
:param names: Special names for the first units. May be shorter than
|
|
`units`, the remaining units are named using `prefix`.
|
|
"""
|
|
|
|
def __init__(self, name, isa, doc, units, prefix='r', names=()):
|
|
# type: (str, TargetISA, str, int, str, Sequence[str]) -> None
|
|
self.name = name
|
|
self.isa = isa
|
|
self.first_unit = 0
|
|
self.units = units
|
|
self.prefix = prefix
|
|
self.names = names
|
|
self.classes = list() # type: List[RegClass]
|
|
self.toprcs = list() # type: List[RegClass]
|
|
self.first_toprc_index = None # type: int
|
|
|
|
assert len(names) <= units
|
|
|
|
if isa.regbanks:
|
|
# Get the next free unit number.
|
|
last = isa.regbanks[-1]
|
|
u = last.first_unit + last.units
|
|
align = units
|
|
if not is_power_of_two(align):
|
|
align = next_power_of_two(align)
|
|
self.first_unit = (u + align - 1) & -align
|
|
|
|
self.index = len(isa.regbanks)
|
|
isa.regbanks.append(self)
|
|
|
|
def __repr__(self):
|
|
# type: () -> str
|
|
return ('RegBank({}, units={}, first_unit={})'
|
|
.format(self.name, self.units, self.first_unit))
|
|
|
|
def finish_regclasses(self):
|
|
# type: () -> None
|
|
"""
|
|
Compute subclasses and the top-level register class.
|
|
|
|
Verify that the set of register classes satisfies:
|
|
|
|
1. Closed under intersection: The intersection of any two register
|
|
classes in the set is either empty or identical to a member of the
|
|
set.
|
|
2. There are no identical classes under different names.
|
|
3. Classes are sorted topologically such that all subclasses have a
|
|
higher index that the superclass.
|
|
|
|
We could reorder classes topologically here instead of just enforcing
|
|
the order, but the ordering tends to fall out naturally anyway.
|
|
"""
|
|
cmap = dict() # type: Dict[RCTup, RegClass]
|
|
|
|
for rc in self.classes:
|
|
# All register classes must be given a name.
|
|
assert rc.name, "Anonymous register class found"
|
|
|
|
# Check for duplicates.
|
|
tup = rc.rctup()
|
|
if tup in cmap:
|
|
raise AssertionError(
|
|
'{} and {} are identical register classes'
|
|
.format(rc, cmap[tup]))
|
|
cmap[tup] = rc
|
|
|
|
# Check intersections and topological order.
|
|
for idx, rc1 in enumerate(self.classes):
|
|
rc1.toprc = rc1
|
|
for rc2 in self.classes[0:idx]:
|
|
itup = rc1.intersect(rc2)
|
|
if itup is None:
|
|
continue
|
|
if itup not in cmap:
|
|
raise AssertionError(
|
|
'intersection of {} and {} missing'
|
|
.format(rc1, rc2))
|
|
irc = cmap[itup]
|
|
# rc1 > rc2, so rc2 can't be the sub-class.
|
|
if irc is rc2:
|
|
raise AssertionError(
|
|
'Bad topological order: {}/{}'
|
|
.format(rc1, rc2))
|
|
if irc is rc1:
|
|
# The intersection of rc1 and rc2 is rc1, so it must be a
|
|
# sub-class.
|
|
rc2.subclasses.append(rc1)
|
|
rc1.toprc = rc2.toprc
|
|
|
|
if rc1.is_toprc():
|
|
self.toprcs.append(rc1)
|
|
|
|
def unit_by_name(self, name):
|
|
# type: (str) -> int
|
|
"""
|
|
Get a register unit in this bank by name.
|
|
"""
|
|
if name in self.names:
|
|
r = self.names.index(name)
|
|
elif name.startswith(self.prefix):
|
|
r = int(name[len(self.prefix):])
|
|
assert r < self.units, 'Invalid register name: ' + name
|
|
return self.first_unit + r
|
|
|
|
|
|
class RegClass(object):
|
|
"""
|
|
A register class is a subset of register units in a RegBank along with a
|
|
strategy for allocating registers.
|
|
|
|
The *width* parameter determines how many register units are allocated at a
|
|
time. Usually it that is one, but for example the ARM D registers are
|
|
allocated two units at a time. When multiple units are allocated, it is
|
|
always a contiguous set of unit numbers.
|
|
|
|
:param bank: The register bank we're allocating from.
|
|
:param count: The maximum number of allocations in this register class. By
|
|
default, the whole register bank can be allocated.
|
|
:param width: How many units to allocate at a time.
|
|
:param start: The first unit to allocate, relative to `bank.first.unit`.
|
|
"""
|
|
|
|
def __init__(self, bank, count=None, width=1, start=0):
|
|
# type: (RegBank, int, int, int) -> None
|
|
self.name = None # type: str
|
|
self.index = None # type: int
|
|
self.bank = bank
|
|
self.start = start
|
|
self.width = width
|
|
|
|
# This is computed later in `finish_regclasses()`.
|
|
self.subclasses = list() # type: List[RegClass]
|
|
self.toprc = None # type: RegClass
|
|
|
|
assert width > 0
|
|
assert start >= 0 and start < bank.units
|
|
|
|
if count is None:
|
|
count = bank.units // width
|
|
self.count = count
|
|
|
|
bank.classes.append(self)
|
|
|
|
def __str__(self):
|
|
# type: () -> str
|
|
return self.name
|
|
|
|
def is_toprc(self):
|
|
# type: () -> bool
|
|
"""
|
|
Is this a top-level register class?
|
|
|
|
A top-level register class has no sub-classes. This can only be
|
|
answered aster running `finish_regclasses()`.
|
|
"""
|
|
return self.toprc is self
|
|
|
|
def rctup(self):
|
|
# type: () -> RCTup
|
|
"""
|
|
Get a tuple that uniquely identifies the registers in this class.
|
|
|
|
The tuple can be used as a dictionary key to ensure that there are no
|
|
duplicate register classes.
|
|
"""
|
|
return (self.count, self.width, self.start)
|
|
|
|
def intersect(self, other):
|
|
# type: (RegClass) -> RCTup
|
|
"""
|
|
Get a tuple representing the intersction of two register classes.
|
|
|
|
Returns `None` if the two classes are disjoint.
|
|
"""
|
|
if self.width != other.width:
|
|
return None
|
|
s_end = self.start + self.count * self.width
|
|
o_end = other.start + other.count * other.width
|
|
if self.start >= o_end or other.start >= s_end:
|
|
return None
|
|
|
|
# We have an overlap.
|
|
start = max(self.start, other.start)
|
|
end = min(s_end, o_end)
|
|
count = (end - start) // self.width
|
|
assert count > 0
|
|
return (count, self.width, start)
|
|
|
|
def __getitem__(self, sliced):
|
|
# type: (slice) -> RegClass
|
|
"""
|
|
Create a sub-class of a register class using slice notation. The slice
|
|
indexes refer to allocations in the parent register class, not register
|
|
units.
|
|
"""
|
|
assert isinstance(sliced, slice), "RegClass slicing can't be 1 reg"
|
|
# We could add strided sub-classes if needed.
|
|
assert sliced.step is None, 'Subclass striding not supported'
|
|
|
|
w = self.width
|
|
s = self.start + sliced.start * w
|
|
c = sliced.stop - sliced.start
|
|
assert c > 1, "Can't have single-register classes"
|
|
|
|
return RegClass(self.bank, count=c, width=w, start=s)
|
|
|
|
def __getattr__(self, attr):
|
|
# type: (str) -> Register
|
|
"""
|
|
Get a specific register in the class by name.
|
|
|
|
For example: `GPR.r5`.
|
|
"""
|
|
return Register(self, self.bank.unit_by_name(attr))
|
|
|
|
def mask(self):
|
|
# type: () -> List[int]
|
|
"""
|
|
Compute a bit-mask of the register units allocated by this register
|
|
class.
|
|
|
|
Return as a list of 32-bit integers.
|
|
"""
|
|
mask = [0] * MASK_LEN
|
|
|
|
start = self.bank.first_unit + self.start
|
|
for a in range(self.count):
|
|
u = start + a * self.width
|
|
b = u % 32
|
|
# We need fancier masking code if a register can straddle mask
|
|
# words. This will only happen with widths that are not powers of
|
|
# two.
|
|
assert b + self.width <= 32, 'Register straddles words'
|
|
mask[u // 32] |= 1 << b
|
|
|
|
return mask
|
|
|
|
def subclass_mask(self):
|
|
# type: () -> int
|
|
"""
|
|
Compute a bit-mask of subclasses, including self.
|
|
"""
|
|
m = 1 << self.index
|
|
for rc in self.subclasses:
|
|
m |= 1 << rc.index
|
|
return m
|
|
|
|
@staticmethod
|
|
def extract_names(globs):
|
|
# type: (Dict[str, Any]) -> None
|
|
"""
|
|
Given a dict mapping name -> object as returned by `globals()`, find
|
|
all the RegClass objects and set their name from the dict key.
|
|
This is used to name a bunch of global variables in a module.
|
|
"""
|
|
for name, obj in globs.items():
|
|
if isinstance(obj, RegClass):
|
|
assert obj.name is None
|
|
obj.name = name
|
|
|
|
|
|
class Register(object):
|
|
"""
|
|
A specific register in a register class.
|
|
|
|
A register is identified by the top-level register class it belongs to and
|
|
its first register unit.
|
|
|
|
Specific registers are used to describe constraints on instructions where
|
|
some operands must use a fixed register.
|
|
|
|
Register instances can be created with the constructor, or accessed as
|
|
attributes on the register class: `GPR.rcx`.
|
|
"""
|
|
def __init__(self, rc, unit):
|
|
# type: (RegClass, int) -> None
|
|
self.regclass = rc
|
|
self.unit = unit
|
|
|
|
|
|
class Stack(object):
|
|
"""
|
|
An operand that must be in a stack slot.
|
|
|
|
A `Stack` object can be used to indicate an operand constraint for a value
|
|
operand that must live in a stack slot.
|
|
"""
|
|
def __init__(self, rc):
|
|
# type: (RegClass) -> None
|
|
self.regclass = rc
|