Source code for microprobe.target.isa

# 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.isa` package

"""

# Futures
from __future__ import absolute_import, annotations

# Built-in modules
import abc
import os
from typing import TYPE_CHECKING, List, Dict

# Third party modules

# Own modules
import microprobe.code.ins
from microprobe import MICROPROBE_RC
from microprobe.code.address import InstructionAddress
from microprobe.code.context import Context
from microprobe.code.var import VariableArray
from microprobe.exceptions import MicroprobeCodeGenerationError, \
    MicroprobeTargetDefinitionError, MicroprobeYamlFormatError
from microprobe.target.isa import comparator as comparator_mod
from microprobe.target.isa import generator as generator_mod
from microprobe.target.isa import instruction as instruction_mod
from microprobe.target.isa import instruction_field as ifield_mod
from microprobe.target.isa import instruction_format as iformat_mod
from microprobe.target.isa import operand as operand_mod
from microprobe.target.isa import register as register_mod
from microprobe.target.isa import register_type as register_type_mod
from microprobe.target.isa.dat import GenericDynamicAddressTranslation
from microprobe.utils.imp import get_object_from_module, \
    import_cls_definition, import_definition, import_operand_definition
from microprobe.utils.logger import DEBUG, get_logger
from microprobe.utils.misc import dict2OrderedDict, findfiles
from microprobe.utils.typeguard_decorator import typeguard_testsuite
from microprobe.utils.yaml import read_yaml

# Local modules

# Type hinting
if TYPE_CHECKING:
    from microprobe.target import Definition, Target
    from microprobe.target.isa.register import Register
    from microprobe.code.var import Variable
    from microprobe.code.ins import InstructionType

# Constants
SCHEMA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "schemas",
                      "isa.yaml")
DEFAULT_ISA = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                           "default", "isa.yaml")
LOG = get_logger(__name__)
__all__ = [
    "find_isa_definitions", "import_isa_definition", "ISA", "GenericISA"
]


# Functions
@typeguard_testsuite
def _read_isa_extensions(isadefs: List[Dict[str, str]], path: str):
    """

    :param isadefs:
    :param path:

    """

    if "Extends" in isadefs[-1]:
        isadefval = isadefs[-1]["Extends"]
        del isadefs[-1]["Extends"]

        if not os.path.isabs(isadefval):
            isadefval = os.path.join(path, isadefval)

        isadef = read_yaml(os.path.join(isadefval, "isa.yaml"), SCHEMA)
        isadef["Path"] = isadefval
        isadefs.append(isadef)

        _read_isa_extensions(isadefs, isadefval)


@typeguard_testsuite
def _read_yaml_definition(isadefs: List[Dict[str, str]], path: str):
    """

    :param isadefs:
    :param path:

    """

    isadef = read_yaml(os.path.join(path, "isa.yaml"), SCHEMA)
    isadef["Path"] = path

    isadefs.append(isadef)

    _read_isa_extensions(isadefs, path)

    baseisa = read_yaml(DEFAULT_ISA, SCHEMA)
    baseisa["Path"] = DEFAULT_ISA
    isadefs.append(baseisa)

    complete_isadef = dict2OrderedDict({})
    isadefs.reverse()

    for isadef in isadefs:
        for key, val in isadef.items():
            if not isinstance(val, dict):
                complete_isadef[key] = isadef[key]
            else:

                override = val.get("Override", False)
                inherit = val.get("Inherit", False)

                if key not in complete_isadef:
                    complete_isadef[key] = {}

                for key2 in val:

                    if key2 in ["YAML", "Modules"]:
                        if key2 not in complete_isadef[key]:
                            complete_isadef[key][key2] = []

                        if os.path.isabs(val[key2]):
                            if override:
                                complete_isadef[key][key2] = [val[key2]]
                            else:
                                complete_isadef[key][key2].append(val[key2])
                        else:
                            if override:
                                complete_isadef[key][key2] = [
                                    os.path.join(isadef["Path"], val[key2])
                                ]
                            else:
                                complete_isadef[key][key2].append(
                                    os.path.join(isadef["Path"], val[key2]))

                        if inherit:
                            key3 = "%s_inherits" % key2
                            if key3 not in complete_isadef[key]:
                                complete_isadef[key][key3] = []
                            complete_isadef[key][key3].append(
                                complete_isadef[key][key2][-1])

                    elif key2 == "Module":
                        if val[key2].startswith("microprobe"):
                            val[key2] = os.path.join(os.path.dirname(__file__),
                                                     "..", "..", "..",
                                                     val[key2])

                        if os.path.isabs(val[key2]):
                            complete_isadef[key][key2] = val[key2]
                        else:
                            complete_isadef[key][key2] = os.path.join(
                                isadef["Path"], val[key2])
                    else:
                        complete_isadef[key][key2] = val[key2]

    return complete_isadef


[docs] @typeguard_testsuite def import_isa_definition(path: str): """Imports a ISA definition given a path :param path: """ LOG.info("Start ISA import") LOG.debug("Importing definition from '%s'", path) if not os.path.isabs(path): path = os.path.abspath(path) isadef = _read_yaml_definition([], path) regts, force = import_definition(isadef, os.path.join(path, "isa.yaml"), "Register_type", register_type_mod, None) regts = dict2OrderedDict(regts) regs, force = import_definition(isadef, os.path.join(path, "isa.yaml"), "Register", register_mod, regts, force=force) regs = dict2OrderedDict(regs) ops, force = import_operand_definition(isadef, os.path.join(path, "isa.yaml"), "Operand", operand_mod, regs, force=force) ops = dict2OrderedDict(ops) ifields, force = import_definition(isadef, os.path.join(path, "isa.yaml"), "Instruction_field", ifield_mod, ops, force=force) ifields = dict2OrderedDict(ifields) iformats, force = import_definition(isadef, os.path.join(path, "isa.yaml"), "Instruction_format", iformat_mod, ifields, force=force) iformats = dict2OrderedDict(iformats) ins, force = import_definition(isadef, os.path.join(path, "isa.yaml"), "Instruction", instruction_mod, (iformats, ops), force=force) ins = dict2OrderedDict(ins) comp_clss = import_cls_definition(isadef, os.path.join(path, "isa.yaml"), "Comparator", comparator_mod) gen_clss = import_cls_definition(isadef, os.path.join(path, "isa.yaml"), "Generator", generator_mod) isa_cls = get_object_from_module(isadef["ISA"]["Class"], isadef["ISA"]["Module"]) try: isa = isa_cls(isadef["Name"], isadef["Description"], path, ins, regs, comp_clss, gen_clss) except TypeError as exc: LOG.critical("Unable to import ISA definition.") LOG.critical("Check if you definition is complete.") LOG.critical("Error reported: %s", exc) raise MicroprobeTargetDefinitionError(exc) LOG.info("ISA '%s' imported", isa) if not LOG.isEnabledFor(DEBUG): return isa # Check definition. Ensure that all the components defined are referenced. for unused_regt in [ regt for regt in regts.values() if regt not in [reg.type for reg in isa.registers.values()] ]: LOG.warning("Unused register type definition: %s", unused_regt) for unused_reg in [ reg for reg in regs.values() if reg not in list(isa.registers.values()) ]: LOG.warning("Unused register definition: %s", unused_reg) used_operands = [] for ins in isa.instructions.values(): for oper in ins.operands.values(): used_operands.append(oper[0].name) for operand in [operand.type for operand in ins.implicit_operands]: used_operands.append(operand.name) for field in ins.format.fields: used_operands.append(field.default_operand.name) for unused_op in [ operand for operand in ops.values() if operand.name not in used_operands ]: LOG.warning("Unused operand definition: %s", unused_op) used_fields = [] for ins in isa.instructions.values(): used_fields += [field.name for field in ins.format.fields] for unused_field in [ field for field in ifields.values() if field.name not in used_fields ]: LOG.warning("Unused field definition: %s", unused_field) used_formats = [] for ins in isa.instructions.values(): used_formats.append(ins.format.name) for unused_format in [ iformat for iformat in iformats.values() if iformat.name not in used_formats ]: LOG.warning("Unused format definition: %s", unused_format) return isa
[docs] def find_isa_definitions(paths: List[str] | None = None): if paths is None: paths = [] paths = paths + MICROPROBE_RC["architecture_paths"] \ + MICROPROBE_RC["default_paths"] results: List[Definition] = [] isafiles = findfiles(paths, "^isa.yaml$") if len(isafiles) > 0: from microprobe.target import Definition for isafile in isafiles: try: isadef = read_yaml(isafile, SCHEMA) except MicroprobeYamlFormatError as exc: LOG.info("Exception: %s", exc) LOG.info("Skipping '%s'", isafile) continue try: definition = Definition(isafile, isadef["Name"], isadef["Description"]) if (definition not in results and not definition.name.endswith("common")): results.append(definition) except TypeError as exc: # Skip bad definitions LOG.info("Exception: %s", exc) LOG.info("Skipping '%s'", isafile) continue return results
# Classes
[docs] @typeguard_testsuite class ISA(abc.ABC): """Abstract class to represent an Instruction Set Architecture (ISA). An instruction set architecture (ISA) object defines the part of the computer architecture related to programming, including instructions, registers, operands, memory operands, etc. """
[docs] @abc.abstractmethod def __init__(self): pass
@property @abc.abstractmethod def name(self) -> str: """ISA name (:class:`~.str`).""" raise NotImplementedError @property @abc.abstractmethod def description(self) -> str: """ISA description (:class:`~.str`).""" raise NotImplementedError @property @abc.abstractmethod def path(self) -> str: """Path to definition (:class:`~.str`).""" raise NotImplementedError @property @abc.abstractmethod def instructions(self) -> Dict[str, "InstructionType"]: """ISA instructions (:class:`~.dict` mapping strings to :class:`~.InstructionType`).""" raise NotImplementedError @property @abc.abstractmethod def target(self) -> Target: """Associated target object (:class:`~.Target`).""" raise NotImplementedError @property @abc.abstractmethod def registers(self) -> Dict[str, Register]: raise NotImplementedError @property @abc.abstractmethod def scratch_registers(self) -> List[Register]: raise NotImplementedError @property @abc.abstractmethod def address_registers(self) -> List[Register]: raise NotImplementedError @property @abc.abstractmethod def float_registers(self) -> List[Register]: raise NotImplementedError @property @abc.abstractmethod def control_registers(self) -> List[Register]: raise NotImplementedError @property @abc.abstractmethod def scratch_var(self) -> Variable: raise NotImplementedError @property @abc.abstractmethod def context_var(self): raise NotImplementedError @abc.abstractmethod def __str__(self) -> str: raise NotImplementedError
[docs] @abc.abstractmethod def full_report(self): raise NotImplementedError
[docs] @abc.abstractmethod def set_register(self, reg: Register, value, context: Context): """ :param reg: :param value: :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def negate_register(self, reg: Register, context: Context): """ :param reg: :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def load(self, reg: Register, address, context: Context): """ :param reg: :param address: :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def load_float(self, reg: Register, address, context: Context): """ :param reg: :param address: :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def store_float(self, reg: Register, address, context: Context): """ :param reg: :param address: :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def store_integer(self, reg: Register, address, length, context: Context): """ :param reg: :param address: :param length: :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def store_decimal(self, address, length, value, context: Context): """ :param address: :param length: :param value: :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def set_register_to_address(self, reg: Register, address, context: Context, force_absolute: bool = False, force_relative: bool = False): """ :param reg: :param address: :param context: :param force_absolute: (Default value = False) """ raise NotImplementedError
[docs] @abc.abstractmethod def get_register_for_address_arithmetic(self, context: Context): """ :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def get_register_for_float_arithmetic(self, context: Context): """ :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def set_register_bits(self, register, value, mask, shift, context: Context): """ :param register: :param value: :param mask: :param shift: :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def new_instruction(self, name: str): """ :param name: """ raise NotImplementedError
[docs] @abc.abstractmethod def set_target(self, target: Target): """ :param target: """ raise NotImplementedError
[docs] @abc.abstractmethod def add_to_register(self, register: Register, value): """ :param register: :param value: """ raise NotImplementedError
[docs] @abc.abstractmethod def branch_unconditional_relative(self, source, target: Target): """ :param source: :param target: """ raise NotImplementedError
[docs] @abc.abstractmethod def branch_to_itself(self): raise NotImplementedError
[docs] @abc.abstractmethod def compare_and_branch(self, val1, val2, cond, target, context: Context): """ :param val1: :param val2: :param cond: :param target: :param context: """ raise NotImplementedError
[docs] @abc.abstractmethod def nop(self): raise NotImplementedError
@property @abc.abstractmethod def flag_registers(self): raise NotImplementedError
[docs] @abc.abstractmethod def get_dat(self, **kwargs): raise NotImplementedError
[docs] @abc.abstractmethod def set_context(self, variable=None, tmpl_path=None): raise NotImplementedError
[docs] @abc.abstractmethod def get_context(self, variable=None, tmpl_path=None): raise NotImplementedError
[docs] @abc.abstractmethod def register_value_comparator(self, comp): raise NotImplementedError
[docs] @abc.abstractmethod def normalize_asm(self, mnemonic, operands): raise NotImplementedError
[docs] @abc.abstractmethod def randomize_register(self, register, seed=None): raise NotImplementedError
[docs] @typeguard_testsuite class GenericISA(ISA): """Class to represent a generic Instruction Set Architecture (ISA)."""
[docs] def __init__(self, name: str, descr: str, path: str, ins: Dict[str, "InstructionType"], regs: Dict[str, "Register"], comparators, generators): """ :param name: :param descr: :param ins: :param regs: :param comparators: :param generators: """ super(GenericISA, self).__init__() self._name = name self._descr = descr self._path = path self._instructions = ins self._registers = regs self._target: Target | None = None self._address_registers = [ reg for reg in regs.values() if reg.type.used_for_address_arithmetic ] self._float_registers = [ reg for reg in regs.values() if reg.type.used_for_float_arithmetic ] self._comparators = [] for comparator in comparators: self._comparators.append(comparator(self)) self._generators = [] for generator in generators: self._generators.append(generator(self)) self._scratch_registers: List[Register] = [] self._control_registers: List[Register] = [] self._flag_registers: List[Register] = [] self._scratch_var = VariableArray( ("%s_scratch_var" % self._name).upper(), "char", 256, align=8) self._context_var = None
@property def name(self): return self._name @property def description(self): return self._descr @property def path(self): """Path to definition (:class:`~.str`).""" return self._path @property def address_registers(self): return self._address_registers @property def float_registers(self): return self._float_registers @property def flag_registers(self): return self._flag_registers @property def instructions(self): return self._instructions @property def scratch_var(self): return self._scratch_var @property def registers(self): return self._registers @property def target(self): return self._target
[docs] def normalize_asm(self, mnemonic, operands): return mnemonic, operands
[docs] def set_target(self, target: Target): """ :param target: """ self._target = target
def __str__(self): return "ISA Name: %s - %s" % (self.name, self.description)
[docs] def new_instruction(self, name: str): """ :param name: """ ins_type = self.instructions[name] return microprobe.code.ins.instruction_factory(ins_type)
[docs] def full_report(self): rstr = "-" * 80 + "\n" rstr += "ISA Name: %s\n" % self.name rstr += "ISA Description: %s\n" % self.name rstr += "-" * 80 + "\n" rstr += "Register Types:\n" for regt in set([reg.type for reg in self.registers.values()]): rstr += str(regt) + "\n" rstr += "-" * 80 + "\n" rstr += "Architected registers:\n" for regname in self.registers: rstr += str(self.registers[regname]) + "\n" rstr += "-" * 80 + "\n" rstr += "Instructions:\n" for ins_name in self.instructions: rstr += str(self.instructions[ins_name].full_report()) + "\n" rstr += "\n Instructions defined: %s \n" % \ len(set([ins.mnemonic for ins in self.instructions.values()])) rstr += " Variants defined: %s \n" % len(self.instructions) rstr += "-" * 80 + "\n" return rstr
[docs] def set_register(self, reg: Register, value, context: Context): """ :param reg: :param value: :param context: """ raise MicroprobeCodeGenerationError("Unable to set register '%s' to " " value '%d'." % (reg.name, value))
@property def scratch_registers(self): return self._scratch_registers @property def control_registers(self): return self._control_registers
[docs] def get_register_for_address_arithmetic(self, context: Context): """ :param context: """ reg = [ reg for reg in self._address_registers if reg not in context.reserved_registers ] if len(reg) == 0: raise MicroprobeCodeGenerationError("No free registers available. " "Change your policy.") # REG0 tends to have special meaning and it is usually at the # beginning, move it reg = reg[1:] + [reg[0]] return reg[0]
[docs] def get_register_for_float_arithmetic(self, context: Context): """ :param context: """ reg = [ reg for reg in self._float_registers if reg not in context.reserved_registers ] assert len(reg) > 0, "All registers for floats already reserved" # REG0 tends to have special meaning and it is usually at the # beginning, move it reg = reg[1:] + [reg[0]] return reg[0]
[docs] def add_to_register(self, register: Register, value): """ :param register: :param value: """ super(GenericISA, self).add_to_register(register, value)
[docs] def branch_unconditional_relative(self, source, target: Target): """ :param source: :param target: """ return super(GenericISA, self).branch_unconditional_relative(source, target)
[docs] def branch_to_itself(self): instr = self.branch_unconditional_relative( InstructionAddress(base_address="code"), InstructionAddress(base_address="code")) instr.set_address(None) return instr
[docs] def get_dat(self, **kwargs): return GenericDynamicAddressTranslation(self, **kwargs)
[docs] def set_context(self, variable=None, tmpl_path=None, complete=False): if variable is None: variable = self.context_var if variable.size < self.context_var.size: raise MicroprobeCodeGenerationError( "Variable '%s' is too small to restore the target context") asm = open(os.path.join(tmpl_path, "setcontext.S")).readlines() for idx, line in enumerate(asm): asm[idx] = line.replace("@@SCRATCH@@", variable.name) if len(asm) == 0: return [] if complete: return microprobe.code.ins.instructions_from_asm( asm, self.target, labels=[variable.name]) else: reg = self._scratch_registers[0] instrs = self.set_register_to_address(reg, variable.address, Context()) return instrs + microprobe.code.ins.instructions_from_asm( asm, self.target, labels=[variable.name])
[docs] def get_context(self, variable=None, tmpl_path=None, complete: bool = False): if variable is None: variable = self.context_var if variable.size < self.context_var.size: raise MicroprobeCodeGenerationError( "Variable '%s' is too small to save the target context") asm = open(os.path.join(tmpl_path, "getcontext.S")).readlines() for idx, line in enumerate(asm): asm[idx] = line.replace("@@SCRATCH@@", variable.name) if len(asm) == 0: return [] if complete: return microprobe.code.ins.instructions_from_asm( asm, self.target, labels=[variable.name]) else: reg = self._scratch_registers[0] instrs = self.set_register_to_address(reg, variable.address, Context()) return instrs + \ microprobe.code.ins.instructions_from_asm( asm, self.target, labels=[variable.name] )
[docs] def register_value_comparator(self, comp): raise NotImplementedError
[docs] def randomize_register(self, register: Register, seed=None): raise NotImplementedError