Source code for microprobe.target.isa.instruction

# 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.instruction` module

"""

# Futures
from __future__ import absolute_import, print_function, annotations

# Built-in modules
import abc
import hashlib
import os
import random
import sys
from inspect import getmembers, getmodule, isfunction
from typing import TYPE_CHECKING, List

# Third party modules

# Own modules
from microprobe.exceptions import MicroprobeArchitectureDefinitionError
from microprobe.property import PropertyHolder, import_properties
from microprobe.target.isa.operand import MemoryOperand, \
    MemoryOperandDescriptor, OperandConst, \
    OperandConstReg, OperandDescriptor
from microprobe.target.isa.instruction_field import GenericInstructionField
from microprobe.target.isa.instruction_format import GenericInstructionFormat
from microprobe.utils.logger import get_logger
from microprobe.utils.misc import OrderedDict, getnextf
from microprobe.utils.typeguard_decorator import typeguard_testsuite
from microprobe.utils.yaml import read_yaml

# Type hinting
if TYPE_CHECKING:
    from microprobe.target.isa.register import Register
    from microprobe.target.isa.operand import Operand

# Constants
SCHEMA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "schemas",
                      "instruction.yaml")
LOG = get_logger(__name__)
__all__ = [
    "import_definition", "InstructionType", "GenericInstructionType",
    "instruction_type_from_bin"
]


# Functions
[docs] @typeguard_testsuite def import_definition(cls, filenames, args): """ :param filenames: :param args: """ LOG.debug("Start importing instruction definitions") iformats, defined_operands = args defined_memory_operands = [] instructions = {} for filename in filenames: instruction_data = read_yaml(filename, SCHEMA) if instruction_data is None: continue for elem in instruction_data: name = elem["Name"] mnemonic = elem["Mnemonic"] opcode = elem["Opcode"] descr = elem.get("Description", "No description") operands = elem.get("Operands", {}) ioperands = elem.get("ImplicitOperands", {}) moperands = elem.get("MemoryOperands", {}) target_checks = elem.get("TargetChecks", {}) instruction_checks = elem.get("InstructionChecks", {}) try: iformat = iformats[elem["Format"]] except KeyError: raise MicroprobeArchitectureDefinitionError( "Unknown " "instruction " "format '%s' in " "instruction " "definition " "'%s' found " "in '%s'" % (elem["Format"], name, filename)) operands = _merge_operands(name, filename, operands, iformat, defined_operands) ioperands = _translate_ioperands(name, filename, ioperands, defined_operands) moperands = _translate_moperands(name, filename, operands, ioperands, moperands, defined_memory_operands, iformat) # target_checks = _translate_callbacks(name, filename, # target_checks) instruction_checks = _translate_checks(name, filename, cls, instruction_checks, operands) instruction = cls(name, mnemonic, opcode, descr, iformat, operands, ioperands, moperands, instruction_checks, target_checks) if name in instructions: raise MicroprobeArchitectureDefinitionError("Duplicated " "definition " "of instruction " " '%s' found " "in '%s'" % (name, filename)) LOG.debug(instruction) instructions[name] = instruction for filename in filenames: import_properties(filename, instructions) LOG.debug("End") return instructions
[docs] @typeguard_testsuite def instruction_type_from_bin(binstr, target): """ :param bin: :type bin: :param target: :type target: """ foperand = OperandConst("raw", "raw", int(binstr, 16)) fields = [ GenericInstructionField("raw", "raw", len(binstr) * 4, False, '?', foperand) ] iformat = GenericInstructionFormat("raw", "raw", fields, "raw: 0x%s" % binstr) instr_type = GenericInstructionType("raw", "raw", "0", "Unknown raw code", iformat, {'raw': [foperand, '?']}, {}, [], {}, {}) for prop in target.nop().properties.values(): if prop.name == "disable_asm": prop.set_value(True) instr_type.register_property(prop) return instr_type
@typeguard_testsuite def _translate_checks(name, filename, cls, checks, dummy_operands): """ :param name: :param filename: :param checks: :param dummy_operands: """ translated_checks = [] module = getmodule(cls) module_filename = module.__file__ defined_ichecks = GENERIC_INSTRUCTION_CHECKS.copy() for key, value in dict(getmembers(module, isfunction)).items(): defined_ichecks[key] = value for check in checks.values(): fname = check[0] fargs = check[1:] try: function = defined_ichecks[fname] translated_checks.append((fname, (function, fargs))) except KeyError: raise MicroprobeArchitectureDefinitionError( "Definition of " "instruction " "'%s' in '%s' " "requires the " "definition of '%s' " "in '%s' module with " "operands '%s'." % (name, filename, fname, module_filename, fargs)) return dict(translated_checks) @typeguard_testsuite def _translate_moperands(name, filename, operands, ioperands, moperands, defined_memory_operands, iformat): """ :param name: :param filename: :param operands: :param ioperands: :param moperands: :param defined_memory_operands: :param iformat: """ roperands = [] # defined_memory_operands = [] for memoperand, value in moperands.items(): formula, length, rate, io_flags = value tformula = [] sformula = [] tlength = [] for field in formula: if field == "D?": voperands = [ key for key in operands.keys() if key.startswith("D") ] vfield = [ tfield.name for tfield in iformat.fields if tfield.name.startswith("D") ] assert sorted(voperands) == sorted(vfield) if len(voperands) == 1: field = voperands[0] sformula.append(field) elif len(voperands) > 1: end = voperands[0][-1] voperands2 = [ voper for voper in voperands if voper.endswith(end) ] if voperands2 == voperands: for voper in voperands: sformula.append(voper) else: number = set( [elem[-1] for elem in formula if elem != "D?"]) if len(number) != 1: raise MicroprobeArchitectureDefinitionError( "Bad formula in name '%s' in memory operand" " '%s' of instruction '%s' (%s) in filename " "'%s'" % (field, memoperand, name, iformat.name, filename)) number = number.pop() sformula.append("D%s" % number) else: sformula.append(field) for field in sformula: if isinstance(field, int): field = str(field) if field.startswith('@'): try: field = [ elem for elem in operands.keys() if elem[0] == field[1] and elem[-3:] == field[-3:] ][0] except IndexError: pass # raise MicroprobeArchitectureDefinitionError( # "Bad formula in name '%s' in memory operand" # " '%s' of instruction '%s' (%s) in filename '%s'" % # (field, memoperand, name, iformat.name, filename) # ) if field in operands: tformula.append((field, operands[field][0])) elif field in ioperands: tformula.append((field, ioperands[field].type)) else: raise MicroprobeArchitectureDefinitionError( "Unknown field name '%s' in memory operand" " '%s' of instruction '%s' (%s) in filename '%s'" % (field, memoperand, name, iformat.name, filename)) for idx, field in enumerate(length): fname = "no_field_%s" % idx if isinstance(field, int): tlength.append((fname, field)) elif (field.startswith("#") or field.startswith("min") or field.startswith("max") or field.startswith("*") or field.startswith("CEIL") or field == "Unknown" or field.startswith("+") or field.startswith("-")): tlength.append((fname, field)) else: if field.endswith("_"): field = field[0:-1] pfields = [ key for key in operands.keys() if key.startswith(field) ] if len(pfields) == 1: field = pfields[0] try: tlength.append((field, operands[field][0])) except KeyError: try: tlength.append((field, ioperands[field])) except Exception: raise MicroprobeArchitectureDefinitionError( "Unknown field name '%s' in length of " "memory operand '%s' of instruction '%s' in" " filename '%s'" % (field, memoperand, name, filename)) memory_operand = MemoryOperand(OrderedDict(tformula), OrderedDict(tlength)) is_defined = [ operand for operand in defined_memory_operands if operand == memory_operand ] if len(is_defined) > 0: memory_operand = is_defined[0] else: defined_memory_operands.append(memory_operand) roperands.append( MemoryOperandDescriptor(memory_operand, io_flags, rate)) return roperands @typeguard_testsuite def _translate_ioperands(name, filename, ioperands, defined_operands): """ :param name: :param filename: :param ioperands: :param defined_operands: """ roperands = {} for regname, value in ioperands.items(): operand_name, io_def = value try: operand = defined_operands[operand_name] except KeyError: raise MicroprobeArchitectureDefinitionError( "Unknown operand name '%s' in definition " "of instruction '%s' in filename '%s'" % (operand_name, name, filename)) try: if operand.constant: reg = operand.random_value() else: reg = [ value for value in operand.values() if value.name == regname ][0] except IndexError: raise MicroprobeArchitectureDefinitionError( "Unknown constant register value '%s' in definition " "of instruction '%s' in filename '%s'" % (regname, name, filename)) try: operand = defined_operands[reg.name] except KeyError: operand = OperandConstReg("ConstantReg-%s" % reg.name, "Constant register %s" % reg.name, reg, False, False, False, False) LOG.debug("Operand added: %s", operand) defined_operands[reg.name] = operand if not operand.constant: raise MicroprobeArchitectureDefinitionError( "Implicit operand defined but it is not constant in" " definition of instruction '%s' in filename '%s'" % (name, filename)) # TODO: This can be improved, we can recycle operand # descriptors if operand and I/O are the same roperands[regname] = OperandDescriptor(operand, "I" in io_def, "O" in io_def) return roperands @typeguard_testsuite def _merge_operands(name, filename, loperands, iformat, defined_operands): """ :param name: :param filename: :param loperands: :param iformat: :param defined_operands: """ # print operands, iformat, defined_operands roperands = {} # Get default operands from ifields for field in iformat.fields: roperands[field.name] = [field.default_operand, field.default_io] # Overwrite with the specific for the instruction fields_processed = [] for operand_description in loperands.values(): operand_name, field_name, field_io = operand_description if field_name not in roperands: field_names = [ field for field in roperands.keys() if field.split("_")[0] == field_name # if field.startswith(field_name) ] if len(field_names) == 0: LOG.debug("Fields: %s", roperands.keys()) raise MicroprobeArchitectureDefinitionError( "Unknown instruction field '%s' in instruction definition" " '%s' found in '%s'" % (field_name, name, filename)) elif len(field_names) > 1: raise MicroprobeArchitectureDefinitionError( "Multiple matches for instruction field '%s' in " "instruction definition" " '%s' found in '%s'. Candidates: %s" % (field_name, name, filename, field_names)) else: field_name = field_names[0] # Fix names referring to original operands if operand_name.startswith("@ORIG@"): operand_name = operand_name.replace("@ORIG@", roperands[field_name][0].name) try: operand = defined_operands[operand_name] except KeyError: # Try for a constant constant_value = None try: constant_value = int(operand_name, 0) except ValueError: constant_value = None if constant_value is not None: operand = OperandConst("Constant-%s" % operand_name, "Constant value %s" % operand_name, constant_value) defined_operands[operand_name] = operand LOG.debug("Operand added: %s", operand) else: raise MicroprobeArchitectureDefinitionError( "Unknown instruction operand '%s' in " "instruction definition '%s' found in '%s'" % (operand_name, name, filename)) if field_name in fields_processed: raise MicroprobeArchitectureDefinitionError( "Field '%s' processed twice. Check definition" " instruction '%s' in file '%s'" % (field_name, name, filename)) roperands[field_name] = [operand, field_io] fields_processed.append(field_name) return roperands # Generic Instruction checks @typeguard_testsuite def _check_reg_value(instruction, register_name, value, condition): """ :param instruction: :param register_name: :param value: :param condition: """ key = register_name, value, condition operand = instruction.operand_by_field(register_name) valid_values = list(operand.type.values()) def checking_function_true(context): """ :param context: """ if context.register_has_value(value): registers = [ register for register in context.registers_get_value(value) if register in context.reserved_registers ] for register in operand.type.values(): if register not in registers: return True return False return True def checking_function_false(context): """ :param context: """ for register in operand.type.values(): context_value = context.get_register_value(register) if context_value is None: return True if context_value == value: return True return False def fixing_function_false(target, building_block): """ :param target: :param building_block: """ assert operand.is_input and not operand.is_output registers = [ reg for reg in valid_values if reg in building_block.context.reserved_registers and building_block.context.get_register_value(reg) != value ] if len(registers) == 0: valid_registers = [ reg for reg in valid_values if reg not in building_block.context.reserved_registers ] assert len(valid_registers) > 0 register = valid_registers[0] random_value = value while random_value == value: random_value = random.randint(0, (2**(register.type.size - 1))) instructions = target.set_register(register, random_value, building_block.context) building_block.context.add_reserved_registers([register]) building_block.context.set_register_value(register, random_value) building_block.add_init(instructions) registers = [register] new_descriptor_operand = operand.descriptor.copy() new_type_operand = new_descriptor_operand.type.copy() new_type_operand.set_valid_values(registers) new_descriptor_operand.set_type(new_type_operand) operand.set_descriptor(new_descriptor_operand) def fixing_function_true(target, building_block): """ :param target: :param building_block: """ if not building_block.context.register_has_value(value): valid_registers = [ reg for reg in valid_values if reg not in building_block.context.reserved_registers ] assert len(valid_registers) > 0 register = valid_registers[0] instructions = target.set_register(register, value, building_block.context) building_block.context.add_reserved_registers([register]) building_block.context.set_register_value(register, value) building_block.add_init(instructions) registers = [ register for register in building_block.context.registers_get_value(value) if register in building_block.context.reserved_registers ] new_descriptor_operand = operand.descriptor.copy() new_type_operand = new_descriptor_operand.type.copy() new_type_operand.set_valid_values(registers) new_descriptor_operand.set_type(new_type_operand) operand.set_descriptor(new_descriptor_operand) if condition is False: instruction.register_context_callback(key, checking_function_false, fixing_function_false) else: instruction.register_context_callback(key, checking_function_true, fixing_function_true) @typeguard_testsuite def _check_reg_bit_value(instruction, register_name, bit, value, condition): """ :param instruction: :param register_name: :param bit: :param value: :param condition: """ key = register_name, bit, value, condition if bit.isdigit(): bit_min = int(bit) bit_max = int(bit) + 1 elif ":" in bit: bit = bit.split(":") bit_min = int(bit[0]) bit_max = int(bit[1]) + 1 else: raise NotImplementedError assert bit_min < bit_max mask = int("".join(['1'] * (bit_max - bit_min) + ['0'] * bit_min), 2) shift = bit_min cannary_value = 123456789123456789 def checking_function(context): """ :param context: """ register_value = context.get_registername_value(register_name) if register_value is None: return True if register_value is cannary_value: return False bits_value = (register_value & mask) >> shift return (bits_value == value) != condition def fixing_function(target, building_block): """ :param target: :param building_block: """ register = target.registers[register_name] instructions = target.set_register_bits(register, value, mask, shift, building_block.context) if register not in building_block.context.reserved_registers: building_block.context.add_reserved_registers([register]) building_block.context.set_register_value(register, cannary_value) building_block.add_init(instructions) instruction.register_context_callback(key, checking_function, fixing_function) @typeguard_testsuite def _check_operands(instruction, operand1_name, operand2_name, check_type, condition): """ :param instruction: :param operand1_name: :param operand2_name: :param check_type: :param condition: """ operand1 = instruction.operand_by_field(operand1_name) operand2 = instruction.operand_by_field(operand2_name) orig_descriptor_operand1 = operand1.descriptor orig_descriptor_operand2 = operand2.descriptor # new_descriptor_operand1 = operand1.descriptor.copy() # new_descriptor_operand2 = operand2.descriptor.copy() # new_type_operand1 = new_descriptor_operand1.type.copy() # new_type_operand2 = new_descriptor_operand2.type.copy() if check_type == "less" and condition is False: check_type = "greater_equal" if check_type == "equal": def function_set_operand1(value): """ :param value: """ assert operand1.value == value new_descriptor_operand2 = operand2.descriptor.copy() new_type_operand2 = new_descriptor_operand2.type.copy() operand2.set_descriptor(new_descriptor_operand2) new_descriptor_operand2.set_type(new_type_operand2) LOG.debug("Ins: %s", instruction) LOG.debug("Setting operand 1 to: %s", value) previous_values = list(new_type_operand2.values()) if condition: new_type_operand2.set_valid_values([value]) assert list(new_type_operand2.values()) == [value] else: new_type_operand2.set_valid_values([ elem for elem in orig_descriptor_operand2.type.values() if elem != value ]) # LOG.debug("Orig operand 2 values: %s", # orig_descriptor_operand2.type.values()) # LOG.debug("New operand 2 values: %s", new_type_operand2.values()) LOG.debug( "Removed from orig: %s", set(orig_descriptor_operand2.type.values()) - set(new_type_operand2.values())) LOG.debug("Removed from previous: %s", set(previous_values) - set(new_type_operand2.values())) assert len( set(new_type_operand2.values()) - set(orig_descriptor_operand2.type.values())) == 0 def function_set_operand2(value): """ :param value: """ assert operand2.value == value new_descriptor_operand1 = operand1.descriptor.copy() new_type_operand1 = new_descriptor_operand1.type.copy() operand1.set_descriptor(new_descriptor_operand1) new_descriptor_operand1.set_type(new_type_operand1) LOG.debug("Ins: %s", instruction) LOG.debug("Setting operand 2 to: %s", value) previous_values = list(new_type_operand1.values()) if condition: new_type_operand1.set_valid_values([value]) else: new_type_operand1.set_valid_values([ elem for elem in orig_descriptor_operand1.type.values() if elem != value ]) # LOG.debug("Orig operand 1 values: %s", # orig_descriptor_operand1.type.values()) # LOG.debug("New operand 1 values: %s", new_type_operand1.values()) LOG.debug( "Removed from orig: %s", set(orig_descriptor_operand1.type.values()) - set(new_type_operand1.values())) LOG.debug("Removed from previous: %s", set(previous_values) - set(new_type_operand1.values())) assert len( set(new_type_operand1.values()) - set(orig_descriptor_operand1.type.values())) == 0 def function_unset_operand1(): """ """ operand2.set_descriptor(orig_descriptor_operand2) def function_unset_operand2(): """ """ operand1.set_descriptor(orig_descriptor_operand1) operand1.register_operand_callbacks(function_set_operand1, function_unset_operand1) operand2.register_operand_callbacks(function_set_operand2, function_unset_operand2) elif check_type == "less": new_base_descriptor_operand1 = operand1.descriptor.copy() new_base_type_operand1 = new_base_descriptor_operand1.type.copy() new_base_descriptor_operand2 = operand2.descriptor.copy() new_base_type_operand2 = new_base_descriptor_operand2.type.copy() # first remove the maximum value of operand1, so that it is never used # and also remove the minimum value of operand2, so that it is never # used operand1.set_descriptor(new_base_descriptor_operand1) new_base_descriptor_operand1.set_type(new_base_type_operand1) new_base_type_operand1.set_valid_values([ elem for elem in orig_descriptor_operand1.type.values() if len([ value for value in orig_descriptor_operand2.type.values() if value > elem ]) > 0 ]) orig_descriptor_operand1 = operand1.descriptor operand2.set_descriptor(new_base_descriptor_operand2) new_base_descriptor_operand2.set_type(new_base_type_operand2) new_base_type_operand2.set_valid_values([ elem for elem in orig_descriptor_operand2.type.values() if len([ value for value in orig_descriptor_operand1.type.values() if value < elem ]) > 0 ]) orig_descriptor_operand2 = operand2.descriptor # print(operand1_name) # print(orig_descriptor_operand1.type.values()) # print(operand2_name) # print(orig_descriptor_operand2.type.values()) def function_set_operand1(value): """ :param value: """ assert operand1.value == value LOG.debug("Ins: %s", instruction) LOG.debug("Ins id: %s", id(instruction)) LOG.debug("Setting operand '%s' to: %s", operand1_name, value) new_descriptor_operand2 = operand2.descriptor.copy() new_type_operand2 = new_descriptor_operand2.type.copy() operand2.set_descriptor(new_descriptor_operand2) new_descriptor_operand2.set_type(new_type_operand2) previous_values = set(new_type_operand2.values()) new_type_operand2.set_valid_values([ elem for elem in orig_descriptor_operand2.type.values() if elem > value ]) new_values = set(new_type_operand2.values()) LOG.debug("Operand '%s' values: %s", operand2_name, previous_values) LOG.debug("Operand '%s' new values: %s", operand2_name, new_values) LOG.debug("Operand differences: %s", previous_values - new_values) def function_set_operand2(value): """ :param value: """ assert operand2.value == value LOG.debug("Ins: %s", instruction) LOG.debug("Ins id: %s", id(instruction)) LOG.debug("Setting operand '%s' to: %s", operand2_name, value) new_descriptor_operand1 = operand1.descriptor.copy() new_type_operand1 = new_descriptor_operand1.type.copy() operand1.set_descriptor(new_descriptor_operand1) new_descriptor_operand1.set_type(new_type_operand1) previous_values = set(new_type_operand1.values()) new_type_operand1.set_valid_values([ elem for elem in orig_descriptor_operand1.type.values() if elem < value ]) new_values = set(new_type_operand1.values()) LOG.debug("Operand '%s' values: %s", operand1_name, previous_values) LOG.debug("Operand '%s' new values: %s", operand1_name, new_values) LOG.debug("Operand differences: %s", previous_values - new_values) def function_unset_operand1(): """ """ operand2.set_descriptor(orig_descriptor_operand2) def function_unset_operand2(): """ """ operand1.set_descriptor(orig_descriptor_operand1) operand1.register_operand_callbacks(function_set_operand1, function_unset_operand1) operand2.register_operand_callbacks(function_set_operand2, function_unset_operand2) else: raise NotImplementedError("Condition '%s' not implemented" % check_type) @typeguard_testsuite def _check_memops_overlap(instruction, overlap_type, condition): """ :param instruction: :param overlap_type: :param condition: """ assert len(instruction.memory_operands()) == 2, "Memory operands overlap" \ " check requires 2 memory"\ " operands" operand1 = instruction.memory_operands()[0] operand2 = instruction.memory_operands()[1] def function_unset_operand1(): operand2.unset_possible_addresses() operand2.unset_possible_lengths() operand2.unset_forbidden_address_range() def function_unset_operand2(): operand1.unset_possible_addresses() operand1.unset_possible_lengths() operand1.unset_forbidden_address_range() def function_set_operand1_address(address, context): """ :param address: :param context: """ assert operand1.address == address if operand1.length is not None: function_set_operand1(context) def function_set_operand1_length(length, context): """ :param length: :param context: """ assert operand1.length == length if operand1.address is not None: function_set_operand1(context) def function_set_operand2_address(address, context): """ :param address: :param context: """ assert operand2.address == address if operand2.length is not None: function_set_operand2(context) def function_set_operand2_length(length, context): """ :param length: :param context: """ assert operand2.length == length if operand2.address is not None: function_set_operand2(context) if overlap_type == "1_bytes_destructive": def function_set_operand1(context): """ :param context: """ possible_address1 = operand1.address + operand1.length - 1 possible_address2 = operand1.address - operand1.length + 1 if condition is True: operand2.set_possible_addresses( [possible_address1, possible_address2], context) operand2.set_possible_lengths([operand1.length], context) else: operand2.set_forbidden_address_range(possible_address1, possible_address2, context) def function_set_operand2(context): """ :param context: """ possible_address1 = operand2.address + operand2.length - 1 possible_address2 = operand2.address - operand2.length + 1 if condition is True: operand1.set_possible_addresses( [possible_address1, possible_address2], context) operand1.set_possible_lengths([operand2.length], context) else: operand1.set_forbidden_address_range(possible_address1, possible_address2, context) operand1.register_mem_operand_callback(function_set_operand1_address, function_set_operand1_length, function_unset_operand1, function_unset_operand1) operand2.register_mem_operand_callback(function_set_operand2_address, function_set_operand2_length, function_unset_operand2, function_unset_operand2) elif overlap_type == "8_bytes_destructive": def function_set_operand1(context): """ :param context: """ possible_address1 = operand1.address + operand1.length - 8 possible_address2 = operand1.address - operand1.length + 8 if condition is True: operand2.set_possible_addresses( [possible_address1, possible_address2], context) operand2.set_possible_lengths([operand1.length], context) else: raise NotImplementedError def function_set_operand2(context): """ :param context: """ possible_address1 = operand2.address + operand2.length - 8 possible_address2 = operand2.address - operand2.length + 8 if condition is True: operand1.set_possible_addresses( [possible_address1, possible_address2], context) operand1.set_possible_lengths([operand2.length], context) else: raise NotImplementedError operand1.register_mem_operand_callback(function_set_operand1_address, function_set_operand1_length, function_unset_operand1, function_unset_operand1) operand2.register_mem_operand_callback(function_set_operand2_address, function_set_operand2_length, function_unset_operand2, function_unset_operand2) elif overlap_type == "destructive_any" or \ overlap_type == "destructive_any_or_exact" or \ overlap_type == "destructive_any_not_exact": def function_set_operand1(context): """ :param context: """ min_address = operand1.address max_address = operand1.address + operand1.length - 1 assert max_address >= min_address if condition is False: operand2.set_forbidden_address_range(min_address, max_address, context) else: alignment = operand2.alignment() if alignment is None: alignment = 1 addresses = [ operand1.address + index for index in range(max_address - min_address + 1) if (operand1.address + index).displacement % alignment == 0 ] if len(addresses) == 0: raise NotImplementedError operand2.set_possible_addresses(addresses, context) def function_set_operand2(context): """ :param context: """ min_address = operand2.address max_address = operand2.address + operand2.length - 1 assert max_address >= min_address if condition is False: operand1.set_forbidden_address_range(min_address, max_address, context) else: alignment = operand2.alignment() if alignment is None: alignment = 1 addresses = [ operand2.address + index for index in range(max_address - min_address + 1) if (operand2.address + index).displacement % alignment == 0 ] if len(addresses) == 0: raise NotImplementedError operand1.set_possible_addresses(addresses, context) operand1.register_mem_operand_callback(function_set_operand1_address, function_set_operand1_length, function_unset_operand1, function_unset_operand1) operand2.register_mem_operand_callback(function_set_operand2_address, function_set_operand2_length, function_unset_operand2, function_unset_operand2) elif overlap_type == "exact": def function_set_operand1(context): """ :param context: """ possible_address = operand1.address if condition is True: operand2.set_possible_addresses([possible_address], context) operand2.set_possible_lengths([operand1.length], context) else: raise NotImplementedError def function_set_operand2(context): """ :param context: """ possible_address = operand2.address if condition is True: operand1.set_possible_addresses([possible_address], context) operand1.set_possible_lengths([operand2.length], context) else: raise NotImplementedError operand1.register_mem_operand_callback(function_set_operand1_address, function_set_operand1_length, function_unset_operand1, function_unset_operand1) operand2.register_mem_operand_callback(function_set_operand2_address, function_set_operand2_length, function_unset_operand2, function_unset_operand2) else: # print(overlap_type, condition) raise NotImplementedError @typeguard_testsuite def _check_alignment(instruction, operand_idx, alignment): """ :param instruction: :param operand_idx: :param alignment: """ assert len(instruction.memory_operands()) > operand_idx, \ "Memory operand alignment check requires %s memory operands" \ % operand_idx operand = instruction.memory_operands()[operand_idx] operand.set_alignment(alignment) GENERIC_INSTRUCTION_CHECKS = {} for _key, _value in dict(getmembers(sys.modules[__name__], isfunction)).items(): if _key.startswith("_check_"): GENERIC_INSTRUCTION_CHECKS[_key[1:]] = _value # Classes
[docs] @typeguard_testsuite class InstructionType(PropertyHolder, metaclass=abc.ABCMeta): """Abstract class to represent a machine instruction type."""
[docs] @abc.abstractmethod def __init__(self): pass
@property @abc.abstractmethod def name(self): raise NotImplementedError @property @abc.abstractmethod def description(self): raise NotImplementedError @property @abc.abstractmethod def mnemonic(self): raise NotImplementedError @property @abc.abstractmethod def opcode(self): raise NotImplementedError @property @abc.abstractmethod def format(self): raise NotImplementedError @property @abc.abstractmethod def operands(self): raise NotImplementedError @property @abc.abstractmethod def memory_operand_descriptors(self): raise NotImplementedError @property @abc.abstractmethod def operand_descriptors(self): raise NotImplementedError @property @abc.abstractmethod def implicit_operands(self): raise NotImplementedError @property @abc.abstractmethod def target_checks(self): raise NotImplementedError @property @abc.abstractmethod def instruction_checks(self): raise NotImplementedError
[docs] @abc.abstractmethod def sets(self, *args): """Returns a :class:`~.list` of :class:`~.Register` instances set by this :class:`~.InstructionType` when invoked with *args* :class:`~.list` of :class:`~.Operand`. :param args: Input operands. :type args: :class:`~.list` of :class:`~.Operand` instances """ raise NotImplementedError
[docs] @abc.abstractmethod def uses(self, args): """Returns a :class:`~.list` of :class:`~.Register` instances used by this :class:`~.InstructionType` when invoked with *args* :class:`~.list` of :class:`~.Operand`. :param args: Input operands. :type args: :class:`~.list` of :class:`~.Operand` instances :param args: """ raise NotImplementedError
[docs] @abc.abstractmethod def assembly(self, args, dissabled_fields=None): """Returns the assembly representation of this instruction when when invoked with *args* :class:`~.list` of :class:`~.Operand`. :param args: Input operands. """ raise NotImplementedError
[docs] @abc.abstractmethod def binary(self, args, asm_args=None): """Return the binary representation of this register when when invoked with *args* :class:`~.list` of :class:`~.Operand`. :param args: Input operands. """ raise NotImplementedError
@abc.abstractmethod def __str__(self): """Return the string representation of this instruction.""" raise NotImplementedError
[docs] @abc.abstractmethod def full_report(self, tabs=0): """Return the string representation of this instruction.""" raise NotImplementedError
[docs] @typeguard_testsuite class GenericInstructionType(InstructionType): """Instruction generic class implementation :param iname: Instruction name :type iname: :class:`~.str` :param idescr: Instruction description :type idescr: :class:`~.str` :param iformat: Instruction format :type iformat: :class:`~.InstructionFormat` """
[docs] def __init__(self, name, mnemonic, opcode, descr, iformat, operands, ioperands, moperands, instruction_checks, target_checks): """ :param name: :param mnemonic: :param opcode: :param descr: :param iformat: :param operands: :param ioperands: :param moperands: :param instruction_checks: :param target_checks: """ super(GenericInstructionType, self).__init__() self._name = name self._mnemonic = mnemonic self._opcode = opcode self._descr = descr self._format = iformat self._operands = OrderedDict(operands) self._ioperands = list(ioperands.values()) self._memoperands = moperands self._target_checks = target_checks self._instruction_checks = instruction_checks self._mask = None for key in [field.name for field in iformat.fields]: if key not in operands: raise MicroprobeArchitectureDefinitionError( "Inconsistent instruction definition. " "Each instruction field should have an operand. " "Instruction: %s", self.name) else: # Sort the operands tmp_val = self.operands[key] del self.operands[key] self.operands[key] = tmp_val if list(self.operands.keys()) != [ field.name for field in iformat.fields ]: LOG.error("Operands: %s", list(self.operands.keys())) LOG.error("Fields: %s", [field.name for field in iformat.fields]) LOG.error("Instruction format: %s", iformat.name) # print(self.operands.keys()) # print([field.name for field in iformat.fields]) raise MicroprobeArchitectureDefinitionError("Operands and fields " "are not aligned in " "'%s'" % self.name) self._operand_descriptors = OrderedDict() for op_descriptor, field in zip(list(self.operands.items()), self.format.fields): fieldname, op_descriptor = op_descriptor operand, io_def = op_descriptor if field.name != fieldname: raise MicroprobeArchitectureDefinitionError( "Operands and fields are not aligned in '%s" % self.name) if not field.default_show: continue # if operand.constant: # and operand.immediate: # continue self._operand_descriptors[field.name] = OperandDescriptor( operand, "I" in io_def, "O" in io_def) opcode_operands = [op for op in operands if op.startswith('opcode')] if len(opcode_operands) == 1: self.operands[opcode_operands[0]][0] = OperandConst( "Opcode", "Instruction opcode", int(self.opcode, 16)) else: # Assume the back-end will take care of multiple operands pass self._hash = int(hashlib.sha512(str(self).encode()).hexdigest(), 16)
def _compute_mask(self): mask_val_str = "0b" mask_str = "0b" for op_descriptor, field in zip(list(self.operands.items()), self.format.fields): dummy_fieldname, op_descriptor = op_descriptor format_str = "{0:0%db}" % field.size if op_descriptor[0].constant and op_descriptor[ 0].immediate and not field.default_show: mask_val_str = mask_val_str + \ format_str.format(list(op_descriptor[0].values())[0]) mask_str = mask_str + "".join(['1'] * field.size) elif op_descriptor[0].constant and not op_descriptor[0].immediate: mask_val_str = mask_val_str + \ format_str.format( int(list(op_descriptor[0].values())[0].representation) ) mask_str = mask_str + "".join(['1'] * field.size) else: mask_val_str = mask_val_str + format_str.format(0) mask_str = mask_str + "".join(['0'] * field.size) assert len(mask_val_str) == self.format.length * 8 + 2, "%s != %s" % ( len(mask_val_str), self.format.length * 8 + 2) mask_val = int(mask_val_str, 2) assert len(mask_str) == self.format.length * 8 + 2, "%s != %s" % ( len(mask_str), self.format.length * 8 + 2) mask = int(mask_str, 2) assert mask != 0 self._mask = (mask, mask_val) @property def name(self): return self._name @property def mnemonic(self): return self._mnemonic @property def description(self): return self._descr @property def opcode(self): return self._opcode @property def operands(self): return self._operands @property def memory_operand_descriptors(self): return self._memoperands @property def operand_descriptors(self): return self._operand_descriptors @property def implicit_operands(self): return self._ioperands @property def format(self): return self._format @property def instruction_checks(self): return self._instruction_checks @property def target_checks(self): return self._target_checks @property def bit_mask(self): if self._mask is None: self._compute_mask() return self._mask
[docs] def sets(self, *args): """Returns a :class:`~.list` of :class:`~.Register` instances set by this :class:`~.InstructionType` when invoked with *args* :class:`~.list` of :class:`~.Operand`. :param args: Input operands. :type args: :class:`~.list` of :class:`~.Operand` instances """ raise NotImplementedError
[docs] def uses(self, args): """Returns a :class:`~.list` of :class:`~.Register` instances used by this :class:`~.InstructionType` when invoked with *args* :class:`~.list` of :class:`~.Operand`. :param args: Input operands. :type args: :class:`~.list` of :class:`~.Operand` instances """ raise NotImplementedError
[docs] def assembly(self, args, dissabled_fields=None): """Returns the assembly representation of this register when when invoked with *args* :class:`~.list` of :class:`~.Operand`. :param args: Input operands. :param dissable_fields: list of fields that will not be translated into assembly :param dissabled_fields: (Default value =None) """ LOG.debug("Begin assembly: %s", self.mnemonic) if dissabled_fields is None: dissabled_fields = [] next_operand_value = getnextf(iter(args)) format_str = self.format.assembly_format LOG.debug("Assembly string: %s", format_str) assembly_str = format_str.replace("OPC", "@@@") for op_descriptor, field in zip(list(self.operands.items()), self.format.fields): fieldname, op_descriptor = op_descriptor operand, dummy = op_descriptor LOG.debug("Field: %s", fieldname) if field.name != fieldname: raise MicroprobeArchitectureDefinitionError( "Operands and fields are not aligned in '%s" % self.name) if not field.default_show: LOG.debug("Skip not shown") continue if field.name in dissabled_fields: LOG.debug("Skip dissabled") next_operand_value() continue if operand.constant: LOG.debug("Constant") if operand.name == "Zero": # Operand Zero never shows assembly_str = assembly_str.replace( " %s," % field.name, "") assembly_str = assembly_str.replace( ", %s" % field.name, "") assembly_str = assembly_str.replace("%s" % field.name, "") assembly_str = assembly_str.replace("()", "") else: assembly_str = assembly_str.replace( field.name, next_operand_value().representation) # operand.representation(operand.values()[0]) else: LOG.debug("No constant value") if assembly_str.find("@@@ " + field.name + ",") >= 0: assembly_str = assembly_str.replace( "@@@ " + field.name + ",", "@@@ " + next_operand_value().representation + ",", 1) elif assembly_str.find(", " + field.name + ",") >= 0: assembly_str = assembly_str.replace( ", " + field.name + ",", ", " + next_operand_value().representation + ",", 1) elif assembly_str.endswith(", " + field.name): assembly_str = assembly_str.replace( ", " + field.name, ", " + next_operand_value().representation, 1) elif assembly_str.find("@@@ $" + field.name + ",") >= 0: assembly_str = assembly_str.replace( "@@@ $" + field.name + ",", "@@@ $" + next_operand_value().representation + ",", 1) elif assembly_str.find(", $" + field.name + ",") >= 0: assembly_str = assembly_str.replace( ", $" + field.name + ",", ", $" + next_operand_value().representation + ",", 1) elif assembly_str.endswith(", $" + field.name): assembly_str = assembly_str.replace( ", $" + field.name, ", $" + next_operand_value().representation, 1) elif assembly_str.find("@@@ " + field.name) >= 0: assembly_str = assembly_str.replace( "@@@ " + field.name, "@@@ " + next_operand_value().representation, 1) elif assembly_str.find("(" + field.name + ")") >= 0: assembly_str = assembly_str.replace( "(" + field.name + ")", "(" + next_operand_value().representation + ")", 1) elif assembly_str.find("(" + field.name + ",") >= 0: assembly_str = assembly_str.replace( "(" + field.name + ",", "(" + next_operand_value().representation + ",", 1) elif assembly_str.find(" " + field.name + ")") >= 0: assembly_str = assembly_str.replace( " " + field.name + ")", " " + next_operand_value().representation + ")", 1) elif assembly_str.find(" " + field.name + "(") >= 0: assembly_str = assembly_str.replace( " " + field.name + "(", " " + next_operand_value().representation + "(", 1) elif assembly_str.find("," + field.name + ")") >= 0: assembly_str = assembly_str.replace( "," + field.name + ")", "," + next_operand_value().representation + ")", 1) else: LOG.debug( "%s", list( zip(list(self.operands.items()), self.format.fields))) LOG.debug("Current assembly: %s", assembly_str) raise MicroprobeArchitectureDefinitionError( "Unable to generate correct assembly for '%s' " "instruction" % self.mnemonic) LOG.debug("Current assembly: %s", assembly_str) assembly_str = assembly_str.replace("@@@", self.mnemonic) LOG.debug("End assembly: %s", assembly_str) return assembly_str
[docs] def binary(self, args, asm_args=None): """Return the binary representation of this register when when invoked with *args* :class:`~.list` of :class:`~.Operand`. :param args: Input operands. :param asm_args: (Default value = None) """ next_operand_value = getnextf(iter(args)) binary_str = "" field_length = 0 if asm_args is None: asm_args = args LOG.debug("Start codification: %s", self.assembly(asm_args)) LOG.debug("Args: %s", args) for op_descriptor, field in zip(list(self.operands.items()), self.format.fields): fieldname, op_descriptor = op_descriptor operand, dummy = op_descriptor LOG.debug("Field: %s", fieldname) LOG.debug("Descriptor: %s", op_descriptor) LOG.debug("Operand: %s", operand) if field.name != fieldname: raise MicroprobeArchitectureDefinitionError( "Operands and fields are not aligned in '%s" % self.name) format_field_str = "{0:0%db}" % field.size field_length += field.size if operand.constant and not field.default_show: LOG.debug("Operand is constant") opvalue = operand.codification(list(operand.values())[0]) LOG.debug("Operand value: %s", list(operand.values())[0]) # if field.default_show: # LOG.debug("Operand is shown") # dummy_operand = next_operand_value() else: LOG.debug("Operand is not constant or is shown") operand = next_operand_value() opvalue = operand.descriptor.type.codification(operand.value) LOG.debug("Operand value: %s", operand.value) LOG.debug("Operand value to codify: %s", opvalue) opvalue = int(opvalue) LOG.debug("Operand int value: %d", opvalue) if opvalue >= (2**field.size): LOG.debug("Field name: %s", fieldname) LOG.debug("Format field str: %s", format_field_str) LOG.debug("Field size: %d", field.size) LOG.debug("Max value codificable: %d", 2**field.size) LOG.debug("Operand value: %s", opvalue) LOG.debug("Instruction name: %s", self.name) raise MicroprobeArchitectureDefinitionError( "Operand value can not be codified within the " "instruction field. Operand: %s Value: %d" % (operand, opvalue)) if opvalue < 0: if abs(opvalue) > (2**(field.size - 1)): LOG.debug("Field name: %s", fieldname) LOG.debug("Format field str: %s", format_field_str) LOG.debug("Field size: %d", field.size) LOG.debug("Max value codificable: %d", 2**field.size) LOG.debug("Operand value: %s", opvalue) LOG.debug("Instruction name: %s", self.name) raise MicroprobeArchitectureDefinitionError( "Operand value can not be codified within the " "instruction field. Operand: %s Value: %d" % (operand, opvalue)) opvalue = opvalue + 2**field.size field_str = format_field_str.format(opvalue) binary_str = binary_str + field_str LOG.debug("Current binary: %s", binary_str) assert field_length == len(binary_str) return binary_str
[docs] def match(self, binary): """Return a bolean indicating if the binary provided matches the intruction binary mask :param binary: Binary instruction codification :type binary: :class:`~.int` :rtype: :class:`~.bool` """ return (binary & self.bit_mask[0]) == self.bit_mask[1]
def __str__(self): """Return the string representation of this instruction""" return "%-8s (%10s) OPC:0x%4s Format:%8s Description: %s" % \ (self.mnemonic, self.name, self.opcode, self.format.name, self.description) def __repr__(self): return "%s('%s')" % (self.__class__.__name__, self._name) def __hash__(self): return self._hash 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) return self.name == other.name def __ne__(self, other): """x.__ne__(y) <==> x!=y""" self._check_cmp(other) return self.name != other.name def __lt__(self, other): """x.__lt__(y) <==> x<y""" self._check_cmp(other) return self.name < other.name def __gt__(self, other): """x.__gt__(y) <==> x>y""" self._check_cmp(other) return self.name > other.name def __le__(self, other): """x.__le__(y) <==> x<=y""" self._check_cmp(other) return self.name <= other.name def __ge__(self, other): """x.__ge__(y) <==> x>=y""" self._check_cmp(other) return self.name >= other.name
[docs] def full_report(self, tabs=0): shift = ("\t" * (tabs + 1)) fmt = "%-17s : %-30s\n" rstr = str(self) rstr += "\n\n" rstr += shift + "Definition" + "\n" rstr += shift + "----------" + "\n" rstr += shift + fmt % ("Name", self.name) rstr += shift + fmt % ("Mnemonic", self.mnemonic) rstr += shift + fmt % ("Opcode", self.opcode) for operand, value in self.operand_descriptors.items(): rstr += shift + \ fmt % ("Operand %s" % operand, "%s -- \t Input: %s \t Output: %s" % (value.type, value.is_input, value.is_output)) for idx, moperand in enumerate(self.memory_operand_descriptors): rstr += shift + fmt % ("Memory Operand %d" % idx, "\n" + moperand.full_report(tabs=tabs + 1)) for value in self.implicit_operands: rstr += shift + \ fmt % ("Implicit Operand", "%s -- \t Input: %s \t Output: %s" % (value.type, value.is_input, value.is_output)) rstr += shift + \ fmt % ("Instr. Checks", ",".join(self.instruction_checks)) rstr += shift + \ fmt % ("Target Checks", ",".join(self.target_checks)) rstr += "\n" rstr += shift + "Format" + "\n" rstr += shift + "------" + "\n" rstr += self.format.full_report(tabs=tabs + 1) rstr += "\n" rstr += shift + "Property List" + "\n" rstr += shift + "-------------" + "\n" rstr += self.list_properties(tabs=tabs + 1) return rstr