Source code for microprobe.target

# Copyright 2011-2021 IBM Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""":mod:`microprobe.target` package

A target is defined by three components. The architecture definition
(see :mod:`~.isa` subpackage), the microarchitecture definition
(see :mod:`~.uarch` subpackage) and the environment definition
(see :mod:`~.env` subpackage). These three elements define the properties
of the target which might be queried during the code generation in order
to drive the code generation.

The main elements of this package are the following:

- :func:`~.import_definition` function provides support for importing target
  definitions.
- :class:`~.Definition` objects encapsulate the required information to be
  able to import architecture, microarchitecture or environment definitions.
- :class:`~.Target` objects are in charge of providing a generic API to query
  all kind of target properties. This is the main object that is used by other
  modules and packages of microprobe to query target information (e.g.
  instructions, registers, functional units, etc.).
"""

# Futures
from __future__ import absolute_import, annotations

# Built-in modules
import copy
import itertools
import os
from typing import TYPE_CHECKING, Tuple

# Third party modules

# Own modules
from microprobe.exceptions import MicroprobeError, \
    MicroprobeTargetDefinitionError
from microprobe.target.env import GenericEnvironment, \
    find_env_definitions, import_env_definition
from microprobe.target.isa import find_isa_definitions, import_isa_definition
from microprobe.target.uarch import find_microarchitecture_definitions, \
    import_microarchitecture_definition
from microprobe.utils.logger import get_logger
from microprobe.utils.misc import Pickable

# Local modules

# Type hinting
if TYPE_CHECKING:
    from microprobe.target.env import Environment
    from microprobe.target.isa import ISA
    from microprobe.target.uarch import Microarchitecture
    from microprobe.code.wrapper import Wrapper

# Constants
LOG = get_logger(__name__)
__all__ = [
    "import_definition",
    # "import_policies",
    "Target",
    "Definition"
]


# Functions
[docs] def import_definition(definition_tuple: str | Tuple[Definition, Definition, Definition]): """Return the target corresponding the to the given *definition_tuple*. Return the target object corresponding the to the given *definition_tuple*. The *definition_tuple* is a string of the form ``<architecture_name>-<uarch_name>-<environment_name>`` that defines the target. This function uses the search paths provided in the configuration to look for the definitions to import. :param definition_tuple: Target definition string :type definition_tuple: :class:`~.str` :return: The target object corresponding to the given *definition_tuple* :rtype: :class:`~.Target` :raise microprobe.exceptions.MicroprobeTargetDefinitionError: if something is wrong during the import """ LOG.debug("Start importing definition tuple") if isinstance(definition_tuple, str): definition_tuple = _parse_definition_tuple(definition_tuple) # Type hinting needs some help here :) assert not isinstance(definition_tuple, str) isa_def, uarch_def, env_def = definition_tuple isa = import_isa_definition(os.path.dirname(isa_def.filename)) env = import_env_definition(env_def.filename, isa, definition_name=env_def.name) uarch = import_microarchitecture_definition( os.path.dirname(uarch_def.filename)) target = Target(isa, uarch=uarch, env=env) LOG.info("Target '%s-%s-%s' imported", isa_def.name, uarch_def.name, env_def.name) LOG.debug("End importing definition tuple") return target
def _parse_definition_tuple(definition_tuple: str): """Return the target definitions corresponding to the *definition_tuple*. Return the target definitions corresponding to the *definition_tuple*. Check the *definition_tuple* format, and for each element (architecture, microarchitecture and environment) looks for the if there is a definition present in the current defined definition paths. It returns a tuple with the three corresponding definitions. :param definition_tuple: Target definition string :type definition_tuple: :class:`~.str` :return: Tuple of target definitions :rtype: :func:`tuple` of :class:`~.Definition` :raise microprobe.exceptions.MicroprobeTargetDefinitionError: if something if the *definition_tuple* format is wrong or if the definition specified is not found """ try: isa_def, architecture_def, env_def = definition_tuple.split("-") except ValueError: raise MicroprobeTargetDefinitionError( "Invalid format of '%s' target tuple" % definition_tuple) definitions = find_isa_definitions() if isa_def not in [definition.name for definition in definitions]: raise MicroprobeTargetDefinitionError("ISA '%s' not available" % isa_def) else: isa_def = [ definition for definition in definitions if definition.name == isa_def ][-1] definitions = find_microarchitecture_definitions() if architecture_def not in [definition.name for definition in definitions]: raise MicroprobeTargetDefinitionError( "Microarchitecture '%s' not available" % architecture_def) else: architecture_def = [ definition for definition in definitions if definition.name == architecture_def ][-1] definitions = find_env_definitions() if env_def not in [definition.name for definition in definitions]: raise MicroprobeTargetDefinitionError( "Environment '%s' not available. " % env_def) else: env_def = [ definition for definition in definitions if definition.name == env_def ][-1] return (isa_def, architecture_def, env_def) # def import_policies(target_name): # """Return the dictionary of policies for the *target_name*.""" # # paths = MICROPROBE_RC["default_paths"] # policies = RejectingDict() # # for policy_file in findfiles(paths, "^.*.py$"): # # LOG.debug("Looking for policies in '%s'", policy_file) # # try: # # name = get_attr_from_module("NAME", policy_file) # description = get_attr_from_module("DESCRIPTION", policy_file) # targets = get_attr_from_module("SUPPORTED_TARGETS", policy_file) # policy = get_attr_from_module("policy", policy_file) # extra = get_dict_from_module(policy_file) # # LOG.debug("Policy '%s' found!", name) # # if target_name in targets: # # LOG.debug("Policy '%s' valid!", name) # policies[name] = Policy(name, description, policy, # targets, extra, policy_file) # # except MicroprobeImportDefinitionError: # LOG.debug("Not a policy module") # continue # # except MicroprobeDuplicatedValueError: # raise MicroprobePolicyError( # "Policy '%s' in '%s' is duplicated" % (name, policy_file) # ) # # return policies # Classes
[docs] class Definition: """Class to represent a target element definition. A target element definition could be the definition of the architecture, the microarchitecture or the environment. In all three cases a definition is composed by the definition name, the filename (where in the file system the definition is located) and the description. """ # Class constant used for nice string formmating _field1 = 18 _field2 = 25 _field3 = 78 - _field1 - _field2 _fmt_str_ = "Name:'%%%ds', Description:'%%%ds', File:'%%%ds'" % ( _field1, _field2, _field3) _cmp_attributes = ["name", "description", "filename"]
[docs] def __init__(self, filename: str, name: str, description: str): """Create a Definition object. :param filename: Filename where the definition is placed :type filename: :class:`~.str` :param name: Name of the definition :type name: :class:`~.str` :param description: Description of the definition :type description: :class:`~.str` :return: Definition instance :rtype: :class:`~.Definition` """ self._file = filename self._name = name self._description = description
@property def description(self): """Description of the definition (:class:`~.str`).""" return self._description @property def filename(self): """Filename of the definition (:class:`~.str`).""" return self._file @property def name(self): """Name of the definition (:class:`~.str`).""" return self._name def __str__(self): """x.__str__() <==> str(x).""" return self._fmt_str_ % (self._name, self._description, self._file) def _check_cmp(self, other): if not isinstance(other, self.__class__): raise NotImplementedError("%s != %s" % (other.__class__, self.__class__)) def __eq__(self, other): """x.__eq__(y) <==> x==y""" self._check_cmp(other) for attr in self._cmp_attributes: if not getattr(self, attr) == getattr(other, attr): return False return True def __ne__(self, other): """x.__ne__(y) <==> x!=y""" self._check_cmp(other) for attr in self._cmp_attributes: if not getattr(self, attr) == getattr(other, attr): return True return False def __lt__(self, other): """x.__lt__(y) <==> x<y""" self._check_cmp(other) for attr in self._cmp_attributes: if getattr(self, attr) < getattr(other, attr): return True elif getattr(self, attr) > getattr(other, attr): return False return False def __gt__(self, other): """x.__gt__(y) <==> x>y""" self._check_cmp(other) for attr in self._cmp_attributes: if getattr(self, attr) > getattr(other, attr): return True elif getattr(self, attr) < getattr(other, attr): return False return False def __le__(self, other): """x.__le__(y) <==> x<=y""" self._check_cmp(other) for attr in self._cmp_attributes: if getattr(self, attr) <= getattr(other, attr): continue else: return False return True def __ge__(self, other): """x.__ge__(y) <==> x>=y""" self._check_cmp(other) for attr in self._cmp_attributes: if getattr(self, attr) >= getattr(other, attr): return True else: return False return False
[docs] class Target(Pickable): """Class to represent a code generation target. A target is defined by the architecture, the microarchitecture implementation and the generation environment. The Target object provides a common interface to all the architecture/microarchitecture/environment specific methods using a facade software design pattern. Therefore, all the references to target related properties should go through this object interface in order to minimize the coupling with the other modules (code generation, design space exploration, ...). """
[docs] def __init__(self, isa: ISA, env: Environment | None = None, uarch: Microarchitecture | None = None): """Create a Target object. :param isa: Architecture (i.e. Instruction Set Architecture) :type isa: :class:`~.ISA` :param env: Environment (default: None) :type env: :class:`~.Environment` :param uarch: Microarchitecture (default: None) :type uarch: :class:`~.Microarchitecture` :return: Target instance :rtype: :class:`~.Target` """ self._uarch: Microarchitecture | None = None self._env: Environment | None = None self._policies = None self._wrapper: Wrapper | None = None self.set_isa(isa) if uarch is not None: self.set_uarch(uarch) self.microarchitecture.add_properties_to_isa(self.isa.instructions) if env is not None: self.set_env(env) else: self.set_env( GenericEnvironment("Default", "Empty environment", self.isa))
@property def name(self): """Name of the Target (:class:`~.str`).""" name = self._isa.name if self._uarch is not None: name += "-" + self._uarch.name if self._env is not None: name += "-" + self._env.name return name @property def description(self): """Description of the Target (:class:`~.str`).""" description = [] description.append("Target ISA: %s" % self.isa.name) description.append("ISA Description: %s" % self.isa.description) if self.microarchitecture is not None: description.append("Target Micro-architecture: %s" % self.microarchitecture.name) description.append("Micro-architecture Description: %s" % self.microarchitecture.description) else: description.append("Target Micro-architecture: Not defined") description.append("Micro-architecture Description: Not defined") description.append("Target Environment: %s" % self.environment.name) description.append("Environment description: %s" % self.environment.description) return "\n".join(description) @property def environment(self): """Environment of the Target (:class:`~.Environment`).""" return self._env @property def isa(self): """Architecture of the Target (:class:`~.ISA`).""" return self._isa @property def microarchitecture(self): """Microarchitecture of the Target (:class:`~.Microarchitecture`).""" return self._uarch @property def reserved_registers(self): """Reserved registers of the Target (:class:`~.list` of :class:`~.Register`).""" return self.environment.environment_reserved_registers + \ self.isa.scratch_registers @property def wrapper(self): """Wrapper of the Target (:class:`~.Wrapper`).""" return self._wrapper
[docs] def full_report(self): """Return a long description of the Target. :rtype: :class:`~.str` """ rstr = self.isa.full_report() + '\n' if self.microarchitecture is not None: rstr += self.microarchitecture.full_report() + '\n' rstr += self.environment.full_report() + '\n' return rstr
[docs] def property_isa_map(self, prop_name): """Generate property to isa map. Return a dictionary mapping values of the property *prop_name* to the list of :class:`~.InstructionType` that have that property value. :param prop_name: Property name :type prop_name: :class:`~.str` :return: Dictionary mapping value properties to instruction types :rtype: :class:`~.dict` mapping property values to :class:`~.list` of :class:`~.InstructionType` :raise microprobe.exceptions.MicroprobeTargetDefinitionError: if the property is not found """ prop_map = {} for instr in self.isa.instructions.values(): try: value = getattr(instr, prop_name) except AttributeError: raise MicroprobeTargetDefinitionError( "Property '%s' for instruction not found" % prop_name) if not isinstance(value, list): values = [value] else: values = value for value in values: if value not in prop_map: prop_map[value] = [] prop_map[value].append(instr) for key in prop_map.keys(): prop_map[key] = set(prop_map[key]) return prop_map
[docs] def set_env(self, env: Environment): """Set the environment of the Target. :param env: Execution environment definition :type env: :class:`~.Environment` """ self._env = copy.deepcopy(env) self._env.set_target(self)
[docs] def set_isa(self, isa: ISA): """Set the ISA of the Target. :param isa: Architecture (i.e. ISA) :type isa: :class:`~.ISA` """ self._isa = copy.deepcopy(isa) self._isa.set_target(self)
[docs] def set_uarch(self, uarch: Microarchitecture): """Set the microarchitecture of the Target. :param uarch: Microarchitecture :type uarch: :class:`~.Microarchitecture` """ self._uarch = uarch self._uarch.set_target(self)
# TODO: remove this interface once the code generation back-end is fixed
[docs] def set_wrapper(self, wrapper: Wrapper): """Set the wrapper of the Target. :param wrapper: Wrapper :type wrapper: :class:`~.Wrapper` """ self._wrapper = wrapper
# TODO: Remove facade pattern to allow type hints to propagate def __getattr__(self, name): """Facade design pattern implementation. This is where we implement the facade design pattern. Whenever an attribute is not defined by the Target object itself, it is searched if it is defined by one of the three elements composing the target. :param name: Attribute name :type name: :class:`~.str` :return: Attribute value :rtype: Type of the value of the attribute requested :raise microprobe.exceptions.MicroprobeError: if attribute is defined in multiple objects :raise AttributeError: if attribute is not found in the class or any of the facade classes """ if name in ["description", "name"]: return self.__getattribute__(name) attrs = [] if self._isa is not None: try: attrs.append(self._isa.__getattribute__(name)) except AttributeError: pass if self._uarch is not None: try: attrs.append(self._uarch.__getattribute__(name)) except AttributeError: pass if self._env is not None: try: attrs.append(self._env.__getattribute__(name)) except AttributeError: pass if len(attrs) == 1: return attrs[0] elif len(attrs) > 1: if all([isinstance(attr, list) for attr in attrs]): return list(itertools.chain.from_iterable(attrs)) else: raise MicroprobeError("Attribute defined in multiple objects") else: raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))