442 lines
13 KiB
Python
442 lines
13 KiB
Python
"""
|
|
Cretonne meta language module.
|
|
|
|
This module provides classes and functions used to describe Cretonne
|
|
instructions.
|
|
"""
|
|
|
|
import re
|
|
import importlib
|
|
|
|
|
|
camel_re = re.compile('(^|_)([a-z])')
|
|
|
|
|
|
def camel_case(s):
|
|
"""Convert the string s to CamelCase"""
|
|
return camel_re.sub(lambda m: m.group(2).upper(), s)
|
|
|
|
|
|
# Kinds of operands.
|
|
#
|
|
# Each instruction has an opcode and a number of operands. The opcode
|
|
# determines the instruction format, and the format determines the number of
|
|
# operands and the kind of each operand.
|
|
class OperandKind(object):
|
|
"""
|
|
The kind of an operand.
|
|
|
|
An instance of the `OperandKind` class corresponds to a kind of operand.
|
|
Each operand kind has a corresponding type in the Rust representation of an
|
|
instruction.
|
|
"""
|
|
|
|
def __init__(self, name, doc):
|
|
self.name = name
|
|
self.__doc__ = doc
|
|
# The camel-cased name of an operand kind is also the Rust type used to
|
|
# represent it.
|
|
self.camel_name = camel_case(name)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def __repr__(self):
|
|
return 'OperandKind({})'.format(self.name)
|
|
|
|
|
|
#: An SSA value operand. This is a value defined by another instruction.
|
|
value = OperandKind(
|
|
'value', """
|
|
An SSA value defined by another instruction.
|
|
|
|
This kind of operand can represent any SSA value type, but the
|
|
instruction format may restrict the valid value types for a given
|
|
operand.
|
|
""")
|
|
|
|
|
|
# Instances of immediate operand types are provided in the cretonne.immediates
|
|
# module.
|
|
class ImmediateKind(OperandKind):
|
|
"""
|
|
The type of an immediate instruction operand.
|
|
"""
|
|
|
|
def __init__(self, name, doc):
|
|
self.name = name
|
|
self.__doc__ = doc
|
|
|
|
def __repr__(self):
|
|
return 'ImmediateKind({})'.format(self.name)
|
|
|
|
def operand_kind(self):
|
|
"""
|
|
An `ImmediateKind` instance can be used directly as the type of an
|
|
`Operand` when defining an instruction.
|
|
"""
|
|
return self
|
|
|
|
|
|
# ValueType instances (i8, i32, ...) are provided in the cretonne.types module.
|
|
class ValueType(object):
|
|
"""
|
|
A concrete SSA value type.
|
|
|
|
All SSA values have a type that is described by an instance of `ValueType`
|
|
or one of its subclasses.
|
|
"""
|
|
|
|
def __init__(self, name, membytes, doc):
|
|
self.name = name
|
|
self.membytes = membytes
|
|
self.__doc__ = doc
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def operand_kind(self):
|
|
"""
|
|
When a `ValueType` object is used to describe the type of an `Operand`
|
|
in an instruction definition, the kind of that operand is an SSA value.
|
|
"""
|
|
return value
|
|
|
|
|
|
class ScalarType(ValueType):
|
|
"""
|
|
A concrete scalar (not vector) type.
|
|
|
|
Also tracks a unique set of :py:class:`VectorType` instances with this type
|
|
as the lane type.
|
|
"""
|
|
|
|
def __init__(self, name, membytes, doc):
|
|
super(ScalarType, self).__init__(name, membytes, doc)
|
|
self._vectors = dict()
|
|
|
|
def __repr__(self):
|
|
return 'ScalarType({})'.format(self.name)
|
|
|
|
def by(self, lanes):
|
|
"""
|
|
Get a vector type with this type as the lane type.
|
|
|
|
For example, ``i32.by(4)`` returns the :obj:`i32x4` type.
|
|
"""
|
|
if lanes in self._vectors:
|
|
return self._vectors[lanes]
|
|
else:
|
|
v = VectorType(self, lanes)
|
|
self._vectors[lanes] = v
|
|
return v
|
|
|
|
|
|
class VectorType(ValueType):
|
|
"""
|
|
A concrete SIMD vector type.
|
|
|
|
A vector type has a lane type which is an instance of :class:`ScalarType`,
|
|
and a positive number of lanes.
|
|
"""
|
|
|
|
def __init__(self, base, lanes):
|
|
assert isinstance(base, ScalarType), 'SIMD lanes must be scalar types'
|
|
super(VectorType, self).__init__(
|
|
name='{}x{}'.format(base.name, lanes),
|
|
membytes=lanes*base.membytes,
|
|
doc="""
|
|
A SIMD vector with {} lanes containing a {} each.
|
|
""".format(lanes, base.name))
|
|
self.base = base
|
|
self.lanes = lanes
|
|
|
|
def __repr__(self):
|
|
return ('VectorType(base={}, lanes={})'
|
|
.format(self.base.name, self.lanes))
|
|
|
|
|
|
class IntType(ScalarType):
|
|
"""A concrete scalar integer type."""
|
|
|
|
def __init__(self, bits):
|
|
assert bits > 0, 'IntType must have positive number of bits'
|
|
super(IntType, self).__init__(
|
|
name='i{:d}'.format(bits),
|
|
membytes=bits/8,
|
|
doc="An integer type with {} bits.".format(bits))
|
|
self.bits = bits
|
|
|
|
def __repr__(self):
|
|
return 'IntType(bits={})'.format(self.bits)
|
|
|
|
|
|
class FloatType(ScalarType):
|
|
"""A concrete scalar floating point type."""
|
|
|
|
def __init__(self, bits, doc):
|
|
assert bits > 0, 'FloatType must have positive number of bits'
|
|
super(FloatType, self).__init__(name='f{:d}'.format(bits),
|
|
membytes=bits/8, doc=doc)
|
|
self.bits = bits
|
|
|
|
def __repr__(self):
|
|
return 'FloatType(bits={})'.format(self.bits)
|
|
|
|
|
|
class BoolType(ScalarType):
|
|
"""A concrete scalar boolean type."""
|
|
|
|
def __init__(self, bits):
|
|
assert bits > 0, 'BoolType must have positive number of bits'
|
|
super(BoolType, self).__init__(
|
|
name='b{:d}'.format(bits),
|
|
membytes=bits/8,
|
|
doc="A boolean type with {} bits.".format(bits))
|
|
self.bits = bits
|
|
|
|
def __repr__(self):
|
|
return 'BoolType(bits={})'.format(self.bits)
|
|
|
|
|
|
# Parametric polymorphism.
|
|
|
|
|
|
class TypeVar(object):
|
|
"""
|
|
A Type Variable.
|
|
|
|
Type variables can be used in place of concrete types when defining
|
|
instructions. This makes the instructions *polymorphic*.
|
|
"""
|
|
|
|
def __init__(self, name, doc):
|
|
self.name = name
|
|
self.__doc__ = doc
|
|
|
|
def operand_kind(self):
|
|
"""
|
|
When a `TypeVar` object is used to describe the type of an `Operand`
|
|
in an instruction definition, the kind of that operand is an SSA value.
|
|
"""
|
|
return value
|
|
|
|
|
|
# Defining instructions.
|
|
|
|
|
|
class InstructionGroup(object):
|
|
"""
|
|
An instruction group.
|
|
|
|
Every instruction must belong to exactly one instruction group. A given
|
|
target architecture can support instructions from multiple groups, and it
|
|
does not necessarily support all instructions in a group.
|
|
|
|
New instructions are automatically added to the currently open instruction
|
|
group.
|
|
"""
|
|
|
|
# The currently open instruction group.
|
|
_current = None
|
|
|
|
def open(self):
|
|
"""
|
|
Open this instruction group such that future new instructions are
|
|
added to this group.
|
|
"""
|
|
assert InstructionGroup._current is None, (
|
|
"Can't open {} since {} is already open"
|
|
.format(self, InstructionGroup._current))
|
|
InstructionGroup._current = self
|
|
|
|
def close(self):
|
|
"""
|
|
Close this instruction group. This function should be called before
|
|
opening another instruction group.
|
|
"""
|
|
assert InstructionGroup._current is self, (
|
|
"Can't close {}, the open instuction group is {}"
|
|
.format(self, InstructionGroup._current))
|
|
InstructionGroup._current = None
|
|
|
|
def __init__(self, name, doc):
|
|
self.name = name
|
|
self.__doc__ = doc
|
|
self.instructions = []
|
|
self.open()
|
|
|
|
@staticmethod
|
|
def append(inst):
|
|
assert InstructionGroup._current, \
|
|
"Open an instruction group before defining instructions."
|
|
InstructionGroup._current.instructions.append(inst)
|
|
|
|
|
|
class Operand(object):
|
|
"""
|
|
An instruction operand.
|
|
|
|
An instruction operand can be either an *immediate* or an *SSA value*. The
|
|
type of the operand is one of:
|
|
|
|
1. A :py:class:`ValueType` instance indicates an SSA value operand with a
|
|
concrete type.
|
|
|
|
2. A :py:class:`TypeVar` instance indicates an SSA value operand, and the
|
|
instruction is polymorphic over the possible concrete types that the
|
|
type variable can assume.
|
|
|
|
3. An :py:class:`ImmediateKind` instance indicates an immediate operand
|
|
whose value is encoded in the instruction itself rather than being
|
|
passed as an SSA value.
|
|
|
|
"""
|
|
def __init__(self, name, typ, doc=''):
|
|
self.name = name
|
|
self.typ = typ
|
|
self.__doc__ = doc
|
|
self.kind = typ.operand_kind()
|
|
|
|
def get_doc(self):
|
|
if self.__doc__:
|
|
return self.__doc__
|
|
else:
|
|
return self.typ.__doc__
|
|
|
|
|
|
class InstructionFormat(object):
|
|
"""
|
|
An instruction format.
|
|
|
|
Every instruction opcode has a corresponding instruction format which
|
|
determines the number of operands and their kinds. Instruction formats are
|
|
identified structurally, i.e., the format of an instruction is derived from
|
|
the kinds of operands used in its declaration.
|
|
|
|
Most instruction formats produce a single result, or no result at all. If
|
|
an instruction can produce more than one result, the `multiple_results`
|
|
flag must be set on its format. All results are of the `value` kind, and
|
|
the instruction format does not keep track of how many results are
|
|
produced. Some instructions, like `call`, may have a variable number of
|
|
results.
|
|
|
|
All instruction formats must be predefined in the
|
|
:py:mod:`cretonne.formats` module.
|
|
|
|
:param kinds: List of `OperandKind` objects describing the operands.
|
|
:param name: Instruction format name in CamelCase. This is used as a Rust
|
|
variant name in both the `InstructionData` and `InstructionFormat`
|
|
enums.
|
|
:param multiple_results: Set to `True` if this instruction format allows
|
|
more than one result to be produced.
|
|
"""
|
|
|
|
# Map (multiple_results, kind, kind, ...) -> InstructionFormat
|
|
_registry = dict()
|
|
|
|
def __init__(self, *kinds, **kwargs):
|
|
self.name = kwargs.get('name', None)
|
|
self.kinds = kinds
|
|
self.multiple_results = kwargs.get('multiple_results', False)
|
|
# Compute a signature for the global registry.
|
|
sig = (self.multiple_results,) + kinds
|
|
if sig in InstructionFormat._registry:
|
|
raise RuntimeError(
|
|
"Format '{}' has the same signature as existing format '{}'"
|
|
.format(self.name, InstructionFormat._registry[sig]))
|
|
InstructionFormat._registry[sig] = self
|
|
|
|
@staticmethod
|
|
def lookup(ins, outs):
|
|
"""
|
|
Find an existing instruction format that matches the given lists of
|
|
instruction inputs and outputs.
|
|
|
|
The `ins` and `outs` arguments correspond to the
|
|
:py:class:`Instruction` arguments of the same name, except they must be
|
|
tuples of :py:`Operand` objects.
|
|
"""
|
|
multiple_results = len(outs) > 1
|
|
sig = (multiple_results,) + tuple(op.kind for op in ins)
|
|
if sig not in InstructionFormat._registry:
|
|
raise RuntimeError(
|
|
"No instruction format matches ins = ({}){}".format(
|
|
", ".join(map(str, sig[1:])),
|
|
"[multiple results]" if multiple_results else ""))
|
|
return InstructionFormat._registry[sig]
|
|
|
|
@staticmethod
|
|
def extract_names(globs):
|
|
"""
|
|
Given a dict mapping name -> object as returned by `globals()`, find
|
|
all the InstructionFormat 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.iteritems():
|
|
if isinstance(obj, InstructionFormat):
|
|
assert obj.name is None
|
|
obj.name = name
|
|
|
|
|
|
class Instruction(object):
|
|
"""
|
|
An instruction description.
|
|
|
|
The operands to the instruction are specified as two tuples: ``ins`` and
|
|
``outs``. Since the Python singleton tuple syntax is a bit awkward, it is
|
|
allowed to specify a singleton as just the operand itself, i.e., `ins=x`
|
|
and `ins=(x,)` are both allowed and mean the same thing.
|
|
|
|
:param name: Instruction mnemonic, also becomes opcode name.
|
|
:param doc: Documentation string.
|
|
:param ins: Tuple of input operands. This can be a mix of SSA value
|
|
operands and immediate operands.
|
|
:param outs: Tuple of output operands. The output operands can't be
|
|
immediates.
|
|
"""
|
|
|
|
def __init__(self, name, doc, ins=(), outs=(), **kwargs):
|
|
self.name = name
|
|
self.camel_name = camel_case(name)
|
|
self.__doc__ = doc
|
|
self.ins = self._to_operand_tuple(ins)
|
|
self.outs = self._to_operand_tuple(outs)
|
|
self.format = InstructionFormat.lookup(self.ins, self.outs)
|
|
InstructionGroup.append(self)
|
|
|
|
@staticmethod
|
|
def _to_operand_tuple(x):
|
|
# Allow a single Operand instance instead of the awkward singleton
|
|
# tuple syntax.
|
|
if isinstance(x, Operand):
|
|
x = (x,)
|
|
else:
|
|
x = tuple(x)
|
|
for op in x:
|
|
assert isinstance(op, Operand)
|
|
return x
|
|
|
|
|
|
# Defining targets
|
|
|
|
|
|
class Target(object):
|
|
"""
|
|
A target instruction set architecture.
|
|
|
|
The `Target` class collects everything known about a target ISA.
|
|
|
|
:param name: Short mnemonic name for the ISA.
|
|
:param instruction_groups: List of `InstructionGroup` instances that are
|
|
relevant for this ISA.
|
|
"""
|
|
|
|
def __init__(self, name, instrution_groups):
|
|
self.name = name
|
|
self.instruction_groups = instrution_groups
|
|
|
|
# Import the fixed instruction formats now so they can be added to the
|
|
# registry.
|
|
importlib.import_module('cretonne.formats')
|