diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 49300c2ed0..421913b0e4 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -6,8 +6,8 @@ Rust representation of cretonne IL, so all instruction formats must be defined in this module. """ from __future__ import absolute_import +from cdsl.formats import InstructionFormat from cdsl.operands import VALUE, VARIABLE_ARGS -from cretonne import InstructionFormat from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc from .entities import ebb, sig_ref, func_ref, jump_table diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py new file mode 100644 index 0000000000..32f4463ae0 --- /dev/null +++ b/lib/cretonne/meta/cdsl/formats.py @@ -0,0 +1,189 @@ +"""Classes for describing instruction formats.""" +from __future__ import absolute_import +from .operands import OperandKind, VALUE, VARIABLE_ARGS +from .operands import Operand # noqa + +# The typing module is only required by mypy, and we don't use these imports +# outside type comments. +try: + from typing import Tuple, Union, Any, Sequence, Iterable # noqa +except ImportError: + pass + + +class InstructionFormat(object): + """ + 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. + :param boxed_storage: Set to `True` is this instruction format requires a + `data: Box<...>` pointer to additional storage in its `InstructionData` + variant. + :param typevar_operand: Index of the input operand that is used to infer + the controlling type variable. By default, this is the first `value` + operand. + """ + + # Map (multiple_results, kind, kind, ...) -> InstructionFormat + _registry = dict() # type: Dict[Tuple[bool, Tuple[OperandKind, ...]], InstructionFormat] # noqa + + # All existing formats. + all_formats = list() # type: List[InstructionFormat] + + def __init__(self, *kinds, **kwargs): + # type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa + self.name = kwargs.get('name', None) # type: str + self.multiple_results = kwargs.get('multiple_results', False) + self.boxed_storage = kwargs.get('boxed_storage', False) + self.members = list() # type: List[str] + self.kinds = tuple(self._process_member_names(kinds)) + + # Which of self.kinds are `value`? + self.value_operands = tuple( + i for i, k in enumerate(self.kinds) if k is VALUE) + + # The typevar_operand argument must point to a 'value' operand. + self.typevar_operand = kwargs.get('typevar_operand', None) # type: int + if self.typevar_operand is not None: + assert self.kinds[self.typevar_operand] is VALUE, \ + "typevar_operand must indicate a 'value' operand" + elif len(self.value_operands) > 0: + # Default to the first 'value' operand, if there is one. + self.typevar_operand = self.value_operands[0] + + # Compute a signature for the global registry. + sig = (self.multiple_results, self.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 + InstructionFormat.all_formats.append(self) + + def _process_member_names(self, kinds): + # type: (Sequence[Union[OperandKind, Tuple[str, OperandKind]]]) -> Iterable[OperandKind] # noqa + """ + Extract names of all the immediate operands in the kinds tuple. + + Each entry is either an `OperandKind` instance, or a `(member, kind)` + pair. The member names correspond to members in the Rust + `InstructionData` data structure. + + Yields the operand kinds. + """ + for arg in kinds: + if isinstance(arg, OperandKind): + member = arg.default_member + k = arg + else: + member, k = arg + self.members.append(member) + yield k + + def __str__(self): + # type: () -> str + args = ', '.join('{}: {}'.format(m, k) if m else str(k) + for m, k in zip(self.members, self.kinds)) + return '{}({})'.format(self.name, args) + + def __getattr__(self, attr): + # type: (str) -> FormatField + """ + Make instruction format members available as attributes. + + Each non-value format member becomes a corresponding `FormatField` + attribute. + """ + try: + i = self.members.index(attr) + except ValueError: + raise AttributeError( + '{} is neither a {} member or a ' + .format(attr, self.name) + + 'normal InstructionFormat attribute') + field = FormatField(self, i, attr) + setattr(self, attr, field) + return field + + @staticmethod + def lookup(ins, outs): + # type: (Sequence[Operand], Sequence[Operand]) -> InstructionFormat + """ + 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. + """ + if len(outs) == 1: + multiple_results = outs[0].kind == VARIABLE_ARGS + else: + 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.items(): + if isinstance(obj, InstructionFormat): + assert obj.name is None + obj.name = name + + +class FormatField(object): + """ + A field in an instruction format. + + This corresponds to a single member of a variant of the `InstructionData` + data type. + + :param format: Parent `InstructionFormat`. + :param operand: Operand number in parent. + :param name: Member name in `InstructionData` variant. + """ + + def __init__(self, format, operand, name): + # type: (InstructionFormat, int, str) -> None + self.format = format + self.operand = operand + self.name = name + + def __str__(self): + # type: () -> str + return '{}.{}'.format(self.format.name, self.name) + + def rust_name(self): + # type: () -> str + if self.format.boxed_storage: + return 'data.' + self.name + else: + return self.name diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index ee66dbe00b..0ab55491c9 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -10,7 +10,8 @@ from cdsl import camel_case from cdsl.predicates import And from cdsl.types import ValueType from cdsl.typevar import TypeVar -from cdsl.operands import VALUE, VARIABLE_ARGS, OperandKind, Operand +from cdsl.operands import Operand +from cdsl.formats import InstructionFormat # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -19,7 +20,6 @@ try: from cdsl.predicates import Predicate, FieldPredicate # noqa MaybeBoundInst = Union['Instruction', 'BoundInstruction'] AnyPredicate = Union['Predicate', 'FieldPredicate'] - OperandSpec = Union['OperandKind', ValueType, TypeVar] except ImportError: TYPE_CHECKING = False @@ -80,182 +80,6 @@ class InstructionGroup(object): InstructionGroup._current.instructions.append(inst) -class InstructionFormat(object): - """ - 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. - :param boxed_storage: Set to `True` is this instruction format requires a - `data: Box<...>` pointer to additional storage in its `InstructionData` - variant. - :param typevar_operand: Index of the input operand that is used to infer - the controlling type variable. By default, this is the first `value` - operand. - """ - - # Map (multiple_results, kind, kind, ...) -> InstructionFormat - _registry = dict() # type: Dict[Tuple[bool, Tuple[OperandKind, ...]], InstructionFormat] # noqa - - # All existing formats. - all_formats = list() # type: List[InstructionFormat] - - def __init__(self, *kinds, **kwargs): - # type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa - self.name = kwargs.get('name', None) # type: str - self.multiple_results = kwargs.get('multiple_results', False) - self.boxed_storage = kwargs.get('boxed_storage', False) - self.members = list() # type: List[str] - self.kinds = tuple(self._process_member_names(kinds)) - - # Which of self.kinds are `value`? - self.value_operands = tuple( - i for i, k in enumerate(self.kinds) if k is VALUE) - - # The typevar_operand argument must point to a 'value' operand. - self.typevar_operand = kwargs.get('typevar_operand', None) # type: int - if self.typevar_operand is not None: - assert self.kinds[self.typevar_operand] is VALUE, \ - "typevar_operand must indicate a 'value' operand" - elif len(self.value_operands) > 0: - # Default to the first 'value' operand, if there is one. - self.typevar_operand = self.value_operands[0] - - # Compute a signature for the global registry. - sig = (self.multiple_results, self.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 - InstructionFormat.all_formats.append(self) - - def _process_member_names(self, kinds): - # type: (Sequence[Union[OperandKind, Tuple[str, OperandKind]]]) -> Iterable[OperandKind] # noqa - """ - Extract names of all the immediate operands in the kinds tuple. - - Each entry is either an `OperandKind` instance, or a `(member, kind)` - pair. The member names correspond to members in the Rust - `InstructionData` data structure. - - Yields the operand kinds. - """ - for arg in kinds: - if isinstance(arg, OperandKind): - member = arg.default_member - k = arg - else: - member, k = arg - self.members.append(member) - yield k - - def __str__(self): - args = ', '.join('{}: {}'.format(m, k) if m else str(k) - for m, k in zip(self.members, self.kinds)) - return '{}({})'.format(self.name, args) - - def __getattr__(self, attr): - # type: (str) -> FormatField - """ - Make instruction format members available as attributes. - - Each non-value format member becomes a corresponding `FormatField` - attribute. - """ - try: - i = self.members.index(attr) - except ValueError: - raise AttributeError( - '{} is neither a {} member or a ' - .format(attr, self.name) + - 'normal InstructionFormat attribute') - field = FormatField(self, i, attr) - setattr(self, attr, field) - return field - - @staticmethod - def lookup(ins, outs): - # type: (Sequence[Operand], Sequence[Operand]) -> InstructionFormat - """ - 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. - """ - if len(outs) == 1: - multiple_results = outs[0].kind == VARIABLE_ARGS - else: - 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.items(): - if isinstance(obj, InstructionFormat): - assert obj.name is None - obj.name = name - - -class FormatField(object): - """ - A field in an instruction format. - - This corresponds to a single member of a variant of the `InstructionData` - data type. - - :param format: Parent `InstructionFormat`. - :param operand: Operand number in parent. - :param name: Member name in `InstructionData` variant. - """ - - def __init__(self, format, operand, name): - # type: (InstructionFormat, int, str) -> None - self.format = format - self.operand = operand - self.name = name - - def __str__(self): - return '{}.{}'.format(self.format.name, self.name) - - def rust_name(self): - # type: () -> str - if self.format.boxed_storage: - return 'data.' + self.name - else: - return self.name - - class Instruction(object): """ The operands to the instruction are specified as two tuples: ``ins`` and diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index c648633ed9..ce747cc203 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -12,6 +12,7 @@ from base.types import i8, f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, immvector from base.immediates import intcc, floatcc from base import entities +import base.formats # noqa instructions = InstructionGroup("base", "Shared base instruction set")