# 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.code.ins` module
"""
# Futures
from __future__ import absolute_import, division, print_function, annotations
# Built-in modules
import copy
from itertools import product
from typing import TYPE_CHECKING, Callable, List
# Third party modules
# Own modules
from microprobe.code.address import Address, InstructionAddress
from microprobe.exceptions import MicroprobeArchitectureDefinitionError, \
MicroprobeCodeGenerationError, MicroprobeDuplicatedValueError, \
MicroprobeLookupError, MicroprobeValueError
from microprobe.target.isa.operand import Operand, \
OperandConstReg, OperandDescriptor, OperandImmRange
from microprobe.target.isa.register import Register
from microprobe.utils.asm import interpret_asm
from microprobe.utils.logger import get_logger
from microprobe.utils.misc import OrderedDict, \
Pickable, RejectingDict, RejectingOrderedDict
from microprobe.utils.typeguard_decorator import typeguard_testsuite
# Type hinting
if TYPE_CHECKING:
from microprobe.target.isa.instruction import InstructionType
from microprobe.code.context import Context
from microprobe.target import Target
# Constants
LOG = get_logger(__name__)
__all__ = [
"Instruction",
"InstructionMemoryOperandValue",
"InstructionOperandValue",
"create_dependency_between_ins",
"instruction_to_definition",
"instruction_from_definition",
"instruction_set_def_properties",
"instructions_from_asm",
"MicroprobeInstructionDefinition",
'instruction_factory',
]
_LABEL_COUNTER = 0
# Functions
[docs]
@typeguard_testsuite
def instruction_to_definition(instr: Instruction):
"""
Return the definition of an instruction.
Given an :class:`~.Instruction` object, return the corresponding
:class:`~.MicroprobeInstructionDefinition` object.
:param instr: Instruction object
:type instr: :class:`~.Instruction`
:rtype: :class:`~.MicroprobeInstructionDefinition`
"""
label = instr.label
instr.set_label(None)
asm = instr.assembly()
comments = instr.comments
if instr.disable_asm is True:
asmfmt = "0x%%0%dX" % (len(instr.binary()) / 4)
asm = asmfmt % int(instr.binary(), 2)
comments = [instr.assembly()] + instr.comments
return MicroprobeInstructionDefinition(
instr.architecture_type,
[operand.value for operand in instr.operands()], label, instr.address,
asm, instr.decorators, comments)
[docs]
@typeguard_testsuite
def instruction_from_definition(definition: MicroprobeInstructionDefinition,
fix_relative: bool = True):
"""
Return the instruction from a definition.
Given an :class:`~.MicroprobeInstructionDefinition` object, return the
corresponding :class:`~.Instruction` object.
:param instr: Instruction definition object
:type instr: :class:`~.MicroprobeInstructionDefinition`
:rtype: :class:`~.Instruction`
"""
ninstr = Instruction()
instruction_set_def_properties(ninstr,
definition,
fix_relative=fix_relative)
return ninstr
[docs]
@typeguard_testsuite
def instruction_set_def_properties(instr,
definition,
building_block=None,
target=None,
allowed_registers=None,
fix_relative=True,
label_displ=None):
"""
Set instruction properties from an intruction definition.
Set instruction properties according to the properties in the instruction
definition. If *building_block* is provided, its context is used.
Otherwise, an empty context is used. The *target* is the target platform
and the *allowed_registers* parameters spicify which register can be
used (written) by the instruction.
:param instr: Instruction instance
:type instr: :class:`~.Instruction`
:param definition: Instruction definition instance
:type definition: :class:`~.MicroprobeInstructionDefinition`
:param building_block: Building block of the instruction
:type building_block: :class:`~.BuildingBlock`
:param target: Target instance
:type target: :class:`~.Target`
:param allowed_registers: List of allowed registers
:type allowed_registers: :class:`~.list` of :class:`~.Register`
"""
global _LABEL_COUNTER
instruction = definition.instruction_type
operands = definition.operands
label = definition.label
address = definition.address
comments = definition.comments
decorators = definition.decorators
asm = definition.asm
if allowed_registers is None:
allowed_registers = []
if label is not None:
instr.set_label(label)
if comments is None:
comments = []
if decorators is None:
decorators = {}
instr.set_arch_type(instruction)
LOG.debug("Set instruction type: %s", instruction)
LOG.debug("Operands, not fixed: %s", operands)
if len(operands) > 0:
fixed_operands: List[Address | InstructionAddress] = []
relocation_mode = False
LOG.debug("Fixed operands: %s", fixed_operands)
for idx, operand in enumerate(operands):
LOG.debug("Fixing operands: %s", operand)
if (isinstance(operand, int)
and instr.operands()[idx].type.address_relative
and fix_relative):
LOG.debug("Hardcoded displacement")
if instr.branch:
LOG.debug("Branch label")
if label is None:
if label_displ is not None:
label = "instr_displ_label_%s" % (label_displ)
_LABEL_COUNTER = max(_LABEL_COUNTER,
label_displ + 1)
else:
label = "instr_displ_label_%s" % (_LABEL_COUNTER)
_LABEL_COUNTER += 1
instr.set_label(label)
iaddress = InstructionAddress(base_address=label,
displacement=operand)
fixed_operands.append(iaddress)
continue
if not isinstance(operand, str):
fixed_operands.append(operand)
LOG.debug("Operand not string")
continue
displacement = 0
# Check if there is displacement from a variable
if building_block is not None:
for var in building_block.registered_global_vars():
if operand.startswith(var.name) and "@" not in operand:
if operand.replace(var.name, "") != "":
displacement = int(operand.replace(var.name, ""),
base=0)
operand = var.name
break
possible_vars = [
var for var in building_block.registered_global_vars()
if var.name == operand
]
LOG.debug("Possible variables: %s", possible_vars)
if len(possible_vars) == 1:
LOG.debug("Operand variable")
taddress = Address(base_address=possible_vars[0],
displacement=displacement)
fixed_operands.append(taddress)
continue
# Check if there is displacement from a label
if operand.find('-') > -1:
displacement = int('-' + operand.split('-')[1], base=0)
operand = operand.split('-')[0]
elif operand.find('+') > -1:
displacement = int('+' + operand.split('+')[1], base=0)
operand = operand.split('+')[0]
if instr.operands()[idx].type.address_relative:
if instr.branch:
LOG.debug("Branch label")
iaddress = InstructionAddress(base_address=operand,
displacement=displacement)
fixed_operands.append(iaddress)
continue
else:
LOG.debug("Branch label")
taddress = Address(base_address=operand,
displacement=displacement)
fixed_operands.append(taddress)
continue
if (isinstance(instr.operands()[idx].type, OperandImmRange)
and "@" in operand):
LOG.debug("Relocation in immediate")
fixed_operands.append(operand)
relocation_mode = True
continue
raise MicroprobeCodeGenerationError(
"I do not know how to set operand '%s' in instruction "
"'%s'" % (operand, asm))
assert len(fixed_operands) == len(operands)
LOG.debug("Setting operands: %s", fixed_operands)
if not relocation_mode:
instr.set_operands(fixed_operands)
else:
for operand, value in zip(instr.operands(), fixed_operands):
if (isinstance(operand.type, OperandImmRange)
and "@" in value):
operand.set_value(value, check=False)
else:
operand.set_value(value)
if address is not None:
instr.set_address(address.copy())
for comment in comments:
instr.add_comment("MPT comment: %s" % comment)
for decorator_key, decorator_value in decorators.items():
if decorator_key in ['MA', 'BT', 'EA']:
if not isinstance(decorator_value, list):
decorator_value = [decorator_value]
decorator_value = ["0x%016X" % elem for elem in decorator_value]
instr.add_comment("Decorator: %s = %s" %
(decorator_key, decorator_value))
instr.add_decorator(decorator_key, decorator_value)
for register_name in allowed_registers:
assert target is not None
if register_name not in list(target.isa.registers.keys()):
raise MicroprobeCodeGenerationError(
"Unknown register '%s'. Known registers: %s" %
(register_name, list(target.isa.registers.keys())))
if target.isa.registers[register_name] in instr.sets() + instr.uses():
instr.add_allow_register(target.isa.registers[register_name])
# TODO : NO NEED TO CHECK ANYTHING JUST REPRODUCING!
# reserve implicit operands (if they are not already
# reserved) and add them as allowed
# for operand_descriptor in instr.implicit_operands:
# register = operand_descriptor.type.values()[0]
# instr.add_allow_register(register)
# if operand_descriptor.is_output and register not \
# in building_block.context.reserved_registers \
# and register not in target.control_registers:
# building_block.context.add_reserved_registers(
# [register])
# context_fix_functions = instr.check_context(
# building_block.context)
# for context_fix_function in context_fix_functions:
# try:
# context_fix_function(target, building_block)
# except MicroprobeUncheckableEnvironmentWarning\
# as warning_check:
# building_block.add_warning(str(warning_check))
[docs]
@typeguard_testsuite
def instructions_from_asm(asm, target, labels=None, fix_relative=True):
"""
:param asm:
:type asm:
:param target:
:type target:
:param labels:
:type labels:
"""
asm_defs = interpret_asm(asm, target, labels)
instrs = [
instruction_from_definition(asm_def, fix_relative=fix_relative)
for asm_def in asm_defs
]
return instrs
[docs]
@typeguard_testsuite
def instruction_factory(ins_type: InstructionType):
"""Return a instruction of the given instruction type.
:param ins_type: Instruction type of the new instruction
:type ins_type: :class:`~.InstructionType`
:return: A new instruction instance with the type *ins_type*
:rtype: :class:`~.Instruction`
"""
instruction = Instruction()
instruction.set_arch_type(ins_type)
return instruction
# Classes
[docs]
@typeguard_testsuite
class InstructionOperandValue(Pickable):
"""Class to represent an instruction operand value"""
[docs]
def __init__(self, operand_descriptor: OperandDescriptor):
"""
:param operand_descriptor:
"""
self._operand_descriptor = operand_descriptor
self._value = None
self._set_function = None
self._unset_function: Callable[[], None] | None = None
@property
def value(self):
"""Value of the instruction operand. (type depends on the operand)"""
return self._value
@property
def descriptor(self):
"""Descriptor of the operand (:class:`~.OperandDescriptor`)"""
return self._operand_descriptor
[docs]
def copy(self):
"""Return a copy of the instruction operand value.
:rtype: :class:`~.InstructionOperandValue`
"""
newobj = InstructionOperandValue(self._operand_descriptor)
if self.value is not None:
newobj.set_value(self.value)
newobj.register_operand_callbacks(self._set_function,
self._unset_function)
return newobj
[docs]
def set_descriptor(self, descriptor: OperandDescriptor):
"""
:param descriptor:
"""
self._operand_descriptor = descriptor
[docs]
def set_value(self, value, check: bool = True):
"""
:param value:
:param check: (Default value = True)
"""
if self._value is not None and self._unset_function is not None:
self._unset_function()
if check:
self.type.check(value)
self._value = value
if self._set_function is not None and value is not None:
self._set_function(value)
[docs]
def unset_value(self):
"""Unsets the operand value.
"""
self.set_value(None, check=False)
[docs]
def sets(self):
"""Return the list of registers set by the operand.
:rtype: :class:`~.list` of :class:`~.Register`
"""
if not self.is_output or self._value is None:
return []
return self._operand_descriptor.type.access(self.value)
@property
def representation(self):
"""Representation of the operand value for generating assembly."""
return self._operand_descriptor.type.representation(self.value)
[docs]
def uses(self):
"""Return the list of registers used by the operand.
:rtype: :class:`~.list` of :class:`~.Register`
"""
if not self.is_input or self._value is None:
return []
return self._operand_descriptor.type.access(self.value)
@property
def type(self):
"""Type of the operand descriptor (:class:`~.Operand`)"""
return self._operand_descriptor._type
@property
def is_input(self):
"""Is input flag (:class:`~.bool`) """
return self._operand_descriptor._is_input
@property
def is_output(self):
"""Is output flag (:class:`~.bool`) """
return self._operand_descriptor._is_output
[docs]
def set_type(self, new_type):
"""
:param new_type:
"""
self._operand_descriptor._type = new_type
def __repr__(self):
""" """
return "%s(Type: %s, Value: %s)" % (self.__class__.__name__, self.type,
self.value)
[docs]
def register_operand_callbacks(self, set_function,
unset_function: Callable[[], None] | None):
"""
:param set_function:
:param unset_function:
"""
assert self._set_function is None
assert self._unset_function is None
self._set_function = set_function
self._unset_function = unset_function
[docs]
@typeguard_testsuite
class InstructionMemoryOperandValue(Pickable):
"""Class to represent an instruction operand value"""
[docs]
def __init__(self, memory_operand_descriptor, operand_values,
length_values):
"""
:param memory_operand_descriptor:
:param operand_values:
:param length_values:
"""
self._memory_operand_descriptor = memory_operand_descriptor
self._operand_values = operand_values
self._length_values = length_values
self._address = None
self._length = None
self._possible_lengths = None
self._set_address_function = None
self._unset_address_function = None
self._set_length_function = None
self._unset_length_function = None
self._possible_lengths_set = None
self._possible_addresses = None
self._alignment = None
self._forbidden_min = None
self._forbidden_max = None
if not self.variable_length:
self._length = self._compute_length()
[docs]
def register_mem_operand_callback(self, set_address, set_length,
unset_address: Callable[[], None],
unset_length: Callable[[], None]):
"""
:param set_address:
:param set_length:
:param unset_address:
:param unset_length:
"""
assert self._set_address_function is None
assert self._unset_address_function is None
assert self._set_length_function is None
assert self._unset_length_function is None
self._set_address_function = set_address
self._unset_address_function = unset_address
self._set_length_function = set_length
self._unset_length_function = unset_length
@property
def address(self):
"""Address of the memory operand (:class:`~.Address`)."""
return self._address
@property
def descriptor(self):
"""Descriptor of the memory operand (:class:`~.OperandDescriptor`)."""
return self._memory_operand_descriptor
@property
def length(self):
"""Length of the memory operand (::class:`~.int`). """
return self._length
@property
def operands(self):
"""Memory operand values
(list of :class:`~.InstructionOperandValue`)."""
return self._operand_values
@property
def variable_length(self):
"""Variable length value(:class:`~.bool`)."""
operand_values = [
operand for operand in self._length_values
if (isinstance(operand, InstructionOperandValue) or isinstance(
operand, OperandDescriptor) or isinstance(operand, tuple))
]
if len(operand_values) > 0:
for operand in operand_values:
if isinstance(operand, tuple):
return True
elif len(list(operand.type.values())) > 1:
return True
elif not isinstance(
list(operand.type.values())[0],
tuple([str, int])):
return True
return False
def _compute_length(self):
"""Compute legnth based on current operand values."""
length = 0
for operand in self._length_values:
if isinstance(operand, int):
length = length + operand
elif operand == "Unknown":
LOG.warning("Unknown length (assume 0) for memory operand")
length = 0
else:
LOG.critical("Warning unknown length: %s", str(operand))
raise NotImplementedError
return length
def _compute_possible_lengths(self, context):
"""Compute the possible lengths that can be generated.
Compute the possible lengths that can be generated from the context
specified.
:param context: execution context
:type context: :class:`~.Context`
"""
if not self.variable_length:
self._possible_lengths = [self._length]
return
operand_values = [
operand for operand in self._length_values
if isinstance(operand, InstructionOperandValue)
]
operand_constants = [
operand for operand in self._length_values
if (not isinstance(operand, InstructionOperandValue)
and not isinstance(operand, tuple)
and not isinstance(operand, OperandDescriptor))
]
operand_register_const = [
operand for operand in self._length_values
if isinstance(operand, OperandDescriptor)
]
operand_special = [
operand for operand in self._length_values
if isinstance(operand, tuple)
]
operand_registers_diff = [
operand for operand in operand_special if operand[1] != "MASK"
]
operand_masks = [
operand for operand in operand_special if operand[1] == "MASK"
]
assert ((len(operand_registers_diff) > 0) ^ (len(operand_values) > 0) ^
(len(operand_masks) > 0) ^ (len(operand_register_const) > 0))
lengths = OrderedDict()
LOG.debug("Operand values: %s", operand_values)
LOG.debug("Operand constants: %s", operand_constants)
LOG.debug("Operand register constants: %s", operand_register_const)
LOG.debug("Operand special: %s", operand_special)
LOG.debug("Operand register diff: %s", operand_registers_diff)
LOG.debug("Operand masks: %s", operand_masks)
if len(operand_values) > 0:
operand_lengths = OrderedDict()
register_based = False
negate_idx = []
for idx, operand in enumerate(operand_values):
if operand.type.immediate:
# Check if the operand is negated
preindex = [
lidx for lidx, elem in enumerate(self._length_values)
if elem == operand
][0]
if (preindex > 0
and self._length_values[preindex - 1] == "-"):
LOG.debug("Negated Immediate operand")
operand_lengths[operand] = [
elem * (-1) for elem in operand.type.values()
]
negate_idx.append(idx)
else:
LOG.debug("Immediate operand")
operand_lengths[operand] = list(operand.type.values())
else:
assert len(
operand_values) == 1, "More than one non immediate "\
"operand " "not implemented."
register_based = True
if register_based:
LOG.debug("Register based")
for register in operand_values[0].type.values():
# if register in context.reserved_registers
# LOG.debug("Skip register: %s", register)
# continue
length = context.get_register_value(register)
if length is not None:
LOG.debug("Fixed register %s to %s", register, length)
lengths[(register, length)] = length
else:
LOG.debug("Register %s not fixed", register)
lengths[(register, "reg_range")] = "reg_range"
else:
for elem in product(*list(operand_lengths.values())):
length = 0
for item in elem:
length = length + item
if 0 in negate_idx:
lengths[elem[0] * (-1)] = length
else:
lengths[elem[0]] = length
elif len(operand_registers_diff) == 1:
LOG.debug("Operand register diff")
if isinstance(operand_registers_diff[0][2],
InstructionOperandValue):
reg2_vals = list(operand_registers_diff[0][2].type.values())
else:
raise NotImplementedError
LOG.debug("Register 2 values: %s", reg2_vals)
if isinstance(operand_registers_diff[0][1],
InstructionOperandValue):
reg1_vals = list(operand_registers_diff[0][1].type.values())
allow_reserved = False
elif operand_registers_diff[0][1] == "REGMAX":
reg1_vals = [max(reg2_vals)]
allow_reserved = True
else:
raise NotImplementedError
LOG.debug("Register 1 values: %s", reg1_vals)
for val1 in reg1_vals:
if val1 in context.reserved_registers and not allow_reserved:
LOG.debug("%s in reserved registers", val1)
continue
for val2 in reg2_vals:
if (val2 in context.reserved_registers
and not allow_reserved):
LOG.debug("%s in reserved registers", val2)
continue
LOG.debug("Checking combination: %s and %s", val1, val2)
ival1 = int(val1.representation)
ival2 = int(val2.representation)
LOG.debug("Value1: %d", ival1)
LOG.debug("Value2: %d", ival2)
# TODO: too conservative --> forcing val1 to be >= val2
if ival1 < ival2:
LOG.debug("Skipping: not allowing val1 < val2")
continue
if not allow_reserved:
skip = False
for reserved in context.reserved_registers:
if (int(reserved.representation) > ival2
and int(reserved.representation) < ival1):
skip = True
break
if skip:
LOG.debug("Skipping: traversing reserved register")
continue
diff = ival1 - ival2 + 1
LOG.debug("Difference: %s", diff)
if diff > 0:
if operand_registers_diff[0][1] == "REGMAX":
lengths[(val2, None)] = diff
else:
lengths[(val1, val2)] = diff
elif len(operand_masks) == 1:
# if the operand is a mask the lengths are the number in bytes
# is the # of 1 in the mask
for elem in operand_masks[0][0].type.values():
lengths[elem] = bin(elem).count("1")
elif len(operand_register_const) == 1:
register = list(operand_register_const[0].type.values())[0]
length = context.get_register_value(register)
if length is not None:
lengths[(register, length)] = length
else:
lengths[(register, "reg_range")] = "reg_range"
else:
raise NotImplementedError
LOG.debug("Possible lengths: %s", set(lengths.values()))
for operand_constant in operand_constants:
LOG.debug("Processing: %s", operand_constant)
lengths_to_remove = []
newlengths = OrderedDict()
for length in lengths:
if isinstance(operand_constant, int):
if isinstance(lengths[length], int):
lengths[length] += operand_constant
else:
raise NotImplementedError
elif operand_constant.startswith("*"):
lengths[length] *= int(operand_constant[1:])
elif operand_constant.startswith("+"):
lengths[length] += int(operand_constant[1:])
elif operand_constant == "-":
continue
elif operand_constant.startswith("-"):
lengths[length] -= int(operand_constant[1:])
elif operand_constant.startswith("CEIL"):
ceil_value = int(operand_constant[4:])
if lengths[length] != "reg_range":
if (lengths[length] % ceil_value) != 0:
lengths[length] = (
(lengths[length] // ceil_value) + 1) * \
ceil_value
else:
lengths[length] = lengths[length]
else:
lengths_to_remove.append(length)
# TODO: Maximum length is hard-coded to 1024 bytes
for elem in range(
0, min((2**length[0].type.size) - 1, 1024),
ceil_value):
newlengths[(length[0], elem)] = elem
elif operand_constant.startswith("min"):
assert isinstance(length, tuple)
maxvalue = int(operand_constant.replace("min", ""))
if lengths[length] != "reg_range":
# value already set
if isinstance(lengths[length], Address):
# TODO: Assuming that all addresses are always
# greater than the max value
lengths[length] = maxvalue
elif isinstance(lengths[length], int):
if isinstance(length, int):
if length > maxvalue:
lengths_to_remove.append(length)
elif isinstance(length, tuple):
if (isinstance(length[0], Register)
and isinstance(length[1], Register)):
if lengths[length] > maxvalue:
lengths_to_remove.append(length)
elif (isinstance(length[0], Register)
and isinstance(length[1],
int)):
if lengths[length] > maxvalue:
# TODO: Assuming that min value is
# the ceiling for lengths operands
lengths[length] = maxvalue
else:
LOG.critical(length)
LOG.critical(lengths[length])
raise NotImplementedError
else:
LOG.critical(length)
raise NotImplementedError
else:
LOG.critical(length)
raise NotImplementedError
else:
lengths_to_remove.append(length)
for elem in range(0, maxvalue + 1):
newlengths[(length[0], elem)] = elem
else:
LOG.critical(length, lengths)
raise NotImplementedError
LOG.debug("Pass 1: %s", set(lengths.values()))
if newlengths is not None:
for length in lengths_to_remove:
del lengths[length]
for key, value in newlengths.items():
lengths[key] = value
LOG.debug("Pass 2: %s", set(lengths.values()))
LOG.debug("Possible lengths all: %s", set(lengths.values()))
for key in list(lengths.keys()):
if lengths[key] <= 0:
del lengths[key]
LOG.debug("Possible lengths final: %s", set(lengths.values()))
assert "reg_range" not in list(lengths.values()), lengths
assert len(lengths) > 0
for elem in lengths.values():
assert isinstance(elem, int), elem
self._possible_lengths = lengths
[docs]
def possible_lengths(self, context: Context):
"""
:param context:
"""
# if self._possible_lengths_set is not None:
# return self._possible_lengths_set
self._compute_possible_lengths(context)
if not self.variable_length:
return self._possible_lengths
else:
return list(self._possible_lengths.values())
[docs]
def possible_addresses(self, dummy_context: Context):
"""
:param dummy_context:
"""
return self._possible_addresses
[docs]
def set_possible_addresses(self, addreses, dummy_context: Context):
"""Set the possible addresses for the memory operand.
:param addreses:
:param dummy_context:
"""
assert self._possible_addresses is None
self._possible_addresses = addreses
[docs]
def unset_possible_addresses(self):
"""Unset the possible addresses of the memory operand."""
self._possible_addresses = None
[docs]
def set_alignment(self, alignment):
"""Set the required alignment of the memory operand.
:param alignment: alignment
:type alignment: :class:`~.int`
"""
assert self._alignment is None
self._alignment = alignment
[docs]
def alignment(self):
"""Required alignment of the memory operand (::class:`~.int`)."""
return self._alignment
[docs]
def set_possible_lengths(self, lengths, dummy_context: Context):
"""
:param lengths:
:param dummy_context:
"""
assert self._possible_lengths_set is None
self._possible_lengths_set = lengths
[docs]
def unset_possible_lengths(self):
"""Unset the possible lengths of the memory operand."""
self._possible_lengths_set = None
[docs]
def set_forbidden_address_range(self, min_address, max_address,
dummy_context: Context):
"""
:param min_address:
:param max_address:
:param dummy_context:
"""
self._forbidden_min = min_address
self._forbidden_max = max_address
[docs]
def set_length(self, length: int, context: Context):
"""
:param length:
:param context:
"""
LOG.debug("Start set length: %s", length)
assert isinstance(length, int), length
if self._unset_length_function is not None:
self._unset_length_function()
self._compute_possible_lengths(context)
operand_values = [
key for key in self._possible_lengths
if self._possible_lengths[key] == length
]
LOG.debug("Operand value required: %s", operand_values)
# free memory?
self._possible_lengths = None
operands = []
for operand in self._length_values:
if isinstance(operand,
(InstructionOperandValue, OperandDescriptor)):
operands.append(operand)
elif isinstance(operand, tuple):
if operand[1] == "MASK":
operands.append(operand[0])
elif (isinstance(operand[1], InstructionOperandValue)
and isinstance(operand[2], InstructionOperandValue)):
operands.append(operand[1])
operands.append(operand[2])
elif (operand[1] == "REGMAX"
and isinstance(operand[2], InstructionOperandValue)):
# operands.append(max(operand[2].type.values()))
operands.append(operand[2])
else:
raise NotImplementedError
elif isinstance(operand, int):
continue
elif operand.startswith("*") and operand[1:].isdigit():
continue
elif operand.startswith("+") and operand[1:].isdigit():
continue
elif operand == "-":
continue
elif operand.startswith("-") and operand[1:].isdigit():
continue
elif operand.startswith("min") and operand[3:].isdigit():
continue
elif operand.startswith("CEIL") and operand[4:].isdigit():
continue
else:
raise NotImplementedError
if len(operand_values) >= 1:
# Take the first one values that sets the length (if register
# dependent, get first the one with value already there)
operand_values = operand_values[0]
elif len(operand_values) == 0:
raise MicroprobeCodeGenerationError("Unable to generate the "
"requested length")
LOG.debug("Operand to use: %s", operands)
# TODO: Why this check?
if isinstance(operand_values, tuple):
if (isinstance(operand_values[0], Register)
and isinstance(operand_values[1], Register)):
operand_values = [operand_values[0], operand_values[1]]
else:
operand_values = [operand_values]
else:
operand_values = [operand_values]
assert len(operands) == len(operand_values)
for operand, operand_value in zip(operands, operand_values):
if isinstance(operand, OperandDescriptor):
# is a register constant
register = operand_value[0]
value = operand_value[1]
if context.get_register_value(register) != value:
raise MicroprobeCodeGenerationError(
"Unable to generate the requested length. Need the "
"register '%s' to be set to '%s' value" %
(register, value))
# Register used for length (conservative approach, reserve it)
if register not in context.reserved_registers:
context.add_reserved_registers([register])
elif isinstance(operand_value, tuple):
register = operand_value[0]
value = operand_value[1]
operand.set_value(register)
if (value is not None
and context.get_register_value(register) != value):
LOG.debug("Context: %s", context.dump())
LOG.debug("Register used: %s", register)
raise MicroprobeCodeGenerationError(
"Unable to generate the requested length. Need a "
"register initialized to value: %s" % value)
# Register used for length (conservative approach, reserve it)
if register not in context.reserved_registers:
context.add_reserved_registers([register])
else:
operand.set_value(operand_value)
self._length = length
self._check_forbidden_range()
if self._set_length_function is not None:
self._set_length_function(length, context)
LOG.debug("End set length: %s", length)
[docs]
def update_address(self, address: Address):
"""
:param address:
:param context:
"""
self._address = address
[docs]
def set_address(self, address: Address, context: Context):
"""
:param address:
:param context:
"""
LOG.debug("Start setting address to: %s", address)
if self._possible_addresses is not None:
LOG.debug("Check address in possible addresses")
assert address in self._possible_addresses
if self._unset_address_function is not None:
self._unset_address_function()
base = address.base_address
address_base = Address(base_address=base, displacement=0)
index = address.displacement
base_operand = None
index_operand_reg = None
index_operand_imm = None
relative_operand = None
# for operand in self.descriptor.type.address_operands.values():
for idx, operand in enumerate(self.operands):
LOG.debug("Operand %d: '%s'", idx, operand)
if operand.descriptor.type.address_base and base_operand is None:
LOG.debug("Base operand %d: %s", idx, operand)
base_operand = operand
if (operand.descriptor.type.address_index
and index_operand_reg is None):
LOG.debug("Index operand %d: %s", idx, operand)
index_operand_reg = operand
if (operand.descriptor.type.address_immediate
and index_operand_imm is None):
LOG.debug("Immediate operand %d: %s", idx, operand)
index_operand_imm = operand
if (operand.descriptor.type.address_relative
and relative_operand is None):
LOG.debug("Relative operand %d: %s", idx, operand)
relative_operand = operand
if relative_operand is not None:
assert base_operand is None
assert index_operand_imm is None
assert index_operand_reg is None
relative_operand.set_value(address)
self._address = address
LOG.debug("Relative operand. Set address directly.")
return
if self.descriptor.is_agen and base_operand is None:
raise MicroprobeCodeGenerationError(
"I do not know how to generate this address: %s" % address)
if base_operand is None:
raise MicroprobeCodeGenerationError(
"I do not know how to generate "
"this address: %s (no base operand)" % address)
if not base_operand.descriptor.is_input:
raise MicroprobeCodeGenerationError(
"I do not know how to generate "
"this address: %s (no base operand input)" % address)
fast_path = False
if context.register_has_value(address):
if context.registers_get_value(address)[0] in \
list(base_operand.type.values()):
fast_path = True
if fast_path:
LOG.debug("Address already in context")
# Check if the address is already in a register
# FAST PATH
register_base = context.registers_get_value(address)[0]
base_operand.set_value(register_base)
if index_operand_imm is not None:
index_operand_imm.set_value(0)
if index_operand_reg is not None:
register_zero_list = []
if context.register_has_value(0):
register_zero_list = [
reg for reg in context.registers_get_value(0)
if reg in list(index_operand_reg.type.values())
]
if len(register_zero_list) == 0:
raise MicroprobeCodeGenerationError(
"I do not know how to generate this address: %s. "
"One of the following registers should be set to zero:"
" %s" %
(address, list(index_operand_reg.type.values())))
index_operand_reg.set_value(register_zero_list[0])
generated_address = address.copy()
else:
LOG.debug("Address not in context")
# Get all the addresses in the context that have
# a base address as 'address_base', if none,
# raise an exception
possible_base_regs = []
for reg, value in context.register_values.items():
if not isinstance(value, Address):
continue
if reg not in list(base_operand.type.values()):
continue
if Address(base_address=value.base_address) == address_base:
possible_base_regs.append((reg, value))
LOG.debug("Possible base reg: '%s', Address: '%s'",
reg.name, value)
# ensure that we have at least one
if len(possible_base_regs) == 0:
LOG.debug("Unable to generate address")
raise MicroprobeCodeGenerationError(
"I do not know how to generate that address. Base address "
"not set. Address: %s Context:\n%s" %
(address, context.dump()))
# get the closest address in the context
closest_index = index - possible_base_regs[0][1].displacement
closest_reg = possible_base_regs[0][0]
closest_address = possible_base_regs[0][1]
for reg, paddress in possible_base_regs[1:]:
if index - paddress.displacement > 0 and (
abs(index - paddress.displacement)
< abs(closest_index)):
if ((index_operand_imm is None)
and not context.register_has_value(
index - paddress.displacement)):
continue
closest_index = index - paddress.displacement
closest_reg = reg
closest_address = paddress
register_base = closest_reg
base_operand.set_value(register_base)
generated_address = Address(
base_address=closest_address.base_address,
displacement=closest_address.displacement)
index = closest_index
LOG.debug("Register base: '%s'", register_base.name)
LOG.debug("Closest index: '%s (0x%x)'", index, index)
# TODO: some instructions only have base operands
# if index_operand_reg is not None or index_operand_imm is not None:
# assert address == generated_address
set_index = False
if (index_operand_imm is not None and not set_index
and generated_address != address):
LOG.debug("The instruction has immediate operand, and the "
"generated address still needs some extra displacement")
assert (index_operand_imm.descriptor.is_input
and not index_operand_imm.descriptor.is_output)
try:
LOG.debug("Setting immediate displacement")
index_operand_imm.set_value(index)
set_index = True
generated_address += index
except MicroprobeValueError:
LOG.debug("Unable to generate the address with the current "
"context")
raise MicroprobeCodeGenerationError(
"I do not know how to generate that address. Index "
"register not set.")
if (index_operand_reg is not None and not set_index
and generated_address != address):
LOG.debug("The instruction has index register operand, and the "
"generated address still needs some extra displacement")
assert (index_operand_reg.descriptor.is_input
and not index_operand_reg.descriptor.is_output)
if not context.register_has_value(index):
LOG.debug("Unable to generate the address with the current "
"context")
raise MicroprobeCodeGenerationError(
"I do not know how to generate that address. Index "
"register not set. Change the generation policy.")
registers_index = context.registers_get_value(index)
registers_index = [
reg for reg in registers_index
if reg in list(index_operand_reg.type.values())
]
if len(registers_index) == 0:
LOG.debug("Unable to generate the address with the current "
"context")
raise MicroprobeCodeGenerationError(
"I do not know how to generate that address. Index "
"register not set. Change the generation policy.")
register_index = registers_index[0]
index_operand_reg.set_value(register_index)
set_index = True
generated_address += index
LOG.debug("Using register: %s", register_index.name)
if (index_operand_imm is not None and set_index
and generated_address == address
and index_operand_imm.value is None):
LOG.debug("Setting index immediate operand to zero")
index_operand_imm.set_value(0)
if (index_operand_reg is not None and set_index
and generated_address == address
and index_operand_reg.value is None):
LOG.debug("Setting index register operand to zero")
register_zero = context.registers_get_value(0)[0]
index_operand_reg.set_value(register_zero)
if address != generated_address:
LOG.debug("Unable to generate the address. Raising exception.")
raise MicroprobeCodeGenerationError(
"I do not know how to generate that address. %s != %s" %
(address, generated_address))
for operand in self.operands:
if operand in [base_operand, index_operand_imm, index_operand_reg]:
continue
if operand.descriptor.type.immediate:
operand.set_value(0)
else:
register_zero = context.registers_get_value(0)[0]
operand.set_value(register_zero)
self._address = address
self._check_forbidden_range()
if self._set_address_function is not None:
self._set_address_function(address, context)
if base_operand.descriptor.is_output:
# TODO: sometimes the base register is also an output (the
# generated address is placed there. Implement that case (update
# context accordingly).
LOG.warning("Base address register is an output. The memory model"
" pass should take that into account")
LOG.debug("End setting address")
[docs]
def update_address_from_operands(self, context=None):
"""
:param context:
:type context:
"""
assert self._address is None
for operand in self.operands:
if operand.value is None:
# Unable to update anything if some operad does not have value
return
# Only fixing relative operands.
if (len(self.operands) == 1 and self.operands[0].type.address_relative
and isinstance(self.operands[0].value, InstructionAddress)):
self._address = self.operands[0].value
if context is not None:
# TODO: fix other types as well
pass
def _check_forbidden_range(self):
""" """
if self._address is None:
return
if self._length is None:
return
if self._forbidden_max is None and self._forbidden_min is None:
return
elif (self._forbidden_max is not None
and self._forbidden_min is not None):
assert self._address > self._forbidden_max or \
(self._address + self._length) < self._forbidden_min
[docs]
def unset_forbidden_address_range(self):
"""Unset the forbiddend address range."""
self._forbidden_min = None
self._forbidden_max = None
[docs]
def sets(self):
"""Return the list of registers set by the memory operand.
:rtype: :class:`~.list` of :class:`~.Register`
"""
if not self.is_store:
return []
raise NotImplementedError
# return self._address
[docs]
def uses(self):
"""Return the list of registers used by the memory operand.
:rtype: :class:`~.list` of :class:`~.Register`
"""
if not self.is_load:
return []
raise NotImplementedError
# return self._address
def __getattr__(self, name):
"""
:param name:
"""
try:
return self._memory_operand_descriptor.__getattribute__(name)
except AttributeError:
raise AttributeError("'%s' object has no attribute '%s'" %
(self.__class__.__name__, name))
def __repr__(self):
""" """
return "%s(Type: %s, Address: %s)" % (self.__class__.__name__,
str(self.type), self.address)
[docs]
@typeguard_testsuite
class Instruction(Pickable):
"""Class to represent an instruction"""
[docs]
def __init__(self):
""" """
self._address = None
self._allowed_regs = []
self._arch_type = None
self._comments = []
self._context_callback_check = RejectingDict()
self._context_callback_fix = RejectingDict()
self._decorators = RejectingDict()
self._dependency_distance = 0
self._generic_type = None
self._label = None
self._mem_operands = []
self._operands = RejectingOrderedDict()
[docs]
def set_arch_type(self, instrtype):
"""
:param instrtype:
"""
self._arch_type = instrtype
self._operands = RejectingOrderedDict()
self._mem_operands = []
self._allowed_regs = []
self._address = None
self._context_callback_check = RejectingDict()
self._context_callback_fix = RejectingDict()
# TODO: now all architecture share operand values but this should
# be decoupled in the future to support different ISA operand setters
for field, operand_descr in instrtype.operand_descriptors.items():
self._operands[field] = InstructionOperandValue(operand_descr)
for mem_operand_descr in instrtype.memory_operand_descriptors:
operand_values = []
for field, operand_type in \
mem_operand_descr.type.address_operands.items():
if field in self._operands:
operand_value = self._operands[field]
assert operand_type == operand_value.type
operand_values.append(operand_value)
else:
operand_value = None
for ioperand in instrtype.implicit_operands:
if len(list(ioperand.type.values())) > 1:
continue
if list(ioperand.type.values())[0].name == field:
operand_value = InstructionOperandValue(ioperand)
break
if operand_value is None:
raise NotImplementedError
assert isinstance(operand_value.type, OperandConstReg)
# TODO: memory operand is implicit
# force it to be address base (is this generic?)
# needed for POWERPC branches to CTR or LR
operand_value.descriptor.set_type(
OperandConstReg(operand_value.type.name,
operand_value.type.description,
list(operand_value.type.values())[0],
True, False, False, False))
operand_value.descriptor.set_type(operand_value.type)
operand_value.set_value(operand_value.type.values()[0])
operand_values.append(operand_value)
length_values = []
for field, operand_type in \
mem_operand_descr.type.length_operands.items():
if isinstance(operand_type, Operand) and field.startswith('M'):
length_value = self._operands[field]
assert operand_type == length_value.type
length_values.append((length_value, "MASK"))
elif isinstance(operand_type, Operand):
length_value = self._operands[field]
assert operand_type == length_value.type
length_values.append(length_value)
elif str(operand_type).startswith("#"):
rfirst = str(operand_type)[1:].split('-')[0]
rsecond = str(operand_type)[1:].split('-')[1]
if rfirst in self._operands:
length_value_a = self._operands[rfirst]
elif rfirst == "REGMAX":
length_value_a = rfirst
else:
raise NotImplementedError
if rsecond in self._operands:
length_value_b = self._operands[rsecond]
else:
raise NotImplementedError
# assert operand_type == length_value.type
length_values.append(
(operand_type, length_value_a, length_value_b))
else:
length_values.append(operand_type)
self._mem_operands.append(
InstructionMemoryOperandValue(mem_operand_descr,
operand_values, length_values))
for value in instrtype.instruction_checks.values():
function, args = value
function(self, *args)
[docs]
def register_context_callback(self, key, checking_function,
fixing_function):
"""
:param key:
:param checking_function:
:param fixing_function:
"""
self._context_callback_check[key] = checking_function
self._context_callback_fix[key] = fixing_function
@property
def context_callbacks(self):
"""Returns the list of context callbacks registered."""
return [(key, self._context_callback_check[key],
self._context_callback_fix[key])
for key in self._context_callback_check.keys()]
[docs]
def check_context(self, context: Context):
"""
:param context:
"""
fixing_callbacks = []
for key, check_context in self._context_callback_check.items():
requires_fixing = check_context(context)
if requires_fixing:
fixing_callbacks.append(self._context_callback_fix[key])
return fixing_callbacks
@property
def architecture_type(self):
"""Instruction architecture type (:class:`~.InstructionType`)."""
return self._arch_type
@property
def decorators(self):
"""Instruction decorators (:class:`~.dict`). """
return self._decorators
[docs]
def memory_operands(self):
"""Instruction memory operands.
:rtype: list of `MemoryOperands`
"""
assert self._arch_type is not None
return self._mem_operands
[docs]
def operands(self):
"""Instruction operands.
:rtype: list of `Operands`
"""
assert self._arch_type is not None
return list(self._operands.values())
[docs]
def copy(self):
"""Return a copy of the instruction.
:rtype: :class:`~.Instruction`
"""
new_instruction = Instruction()
new_instruction.set_arch_type(self.architecture_type)
for callback in self.context_callbacks:
new_instruction.register_context_callback(*callback)
new_instruction.set_operands([op.value for op in self.operands()],
check=False)
for reg in self.allowed_regs:
new_instruction.add_allow_register(reg)
for comment in self.comments:
new_instruction.add_comment(comment)
for dec_key, dec_val in self.decorators.items():
new_instruction.add_decorator(dec_key, dec_val)
new_instruction.set_label(self.label)
if self.address is not None:
new_instruction.set_address(self.address.copy())
return new_instruction
[docs]
def operand_by_field(self, fieldname):
"""
:param fieldname:
"""
assert self._arch_type is not None
try:
fieldnames = [
key for key in self._operands.keys()
if key.startswith(fieldname)
]
fieldname = fieldnames[0]
if len(fieldnames) > 1:
raise MicroprobeArchitectureDefinitionError(
"Looking for field name with multiple options. "
"Check the definition of instruction type "
"'%s'" % self._arch_type)
return self._operands[fieldname]
except (KeyError, IndexError):
raise MicroprobeLookupError(
"'%s' not in %s" % (fieldname, list(self._operands.keys())))
[docs]
def operand_fields(self):
"""Instruction field names of the operands.
:rtype: list of :class:`~.str`
"""
assert self._arch_type is not None
return list(self._operands.keys())
def __str__(self):
""" """
if self._arch_type is None:
return "Generic '%s' instr. Dep. distance: %d" % \
(self._generic_type, self._dependency_distance)
return str(self._arch_type)
[docs]
def set_operands(self, values, context=None, check=True):
"""
:param values:
"""
if len(values) != len(self.operands()):
raise MicroprobeCodeGenerationError(
"Invalid number of operands specified for instruction "
"'%s'. # Accepted operands: %s\n # Provided operands: %s"
"\n Operands: %s" %
(self, len(self.operands()), len(values), self.operands()))
try:
for operand, value in zip(self.operands(), values):
if value is not None:
operand.set_value(value, check=check)
except MicroprobeValueError as exc:
LOG.debug("Valid operands:")
for operand in self.operands():
LOG.debug(operand)
raise exc
for memoperand in self.memory_operands():
memoperand.update_address_from_operands(context=context)
[docs]
def sets(self):
"""Return the list of registers set by the instruction.
:rtype: :class:`~.list` of :class:`~.Register`
"""
sets = []
for operand in self.operands():
sets += operand.sets()
for ioperand in self._arch_type.implicit_operands:
if ioperand.is_output:
sets += list(ioperand.type.values())
return sets
[docs]
def uses(self):
"""Return the list of registers used by the instruction.
:rtype: :class:`~.list` of :class:`~.Register`
"""
uses = []
for operand in self.operands():
uses += operand.uses()
for ioperand in self._arch_type.implicit_operands:
if ioperand.is_input:
uses += list(ioperand.type.values())
return uses
[docs]
def add_allow_register(self, reg):
"""
:param reg:
"""
assert reg not in self._allowed_regs, "Already allowed"
self._allowed_regs.append(reg)
[docs]
def add_decorator(self, name, value):
"""
:param key: decorator name
:type key:
:param value: decorator value
:type value:
"""
try:
self._decorators[name] = {}
self._decorators[name]['value'] = value
except MicroprobeDuplicatedValueError:
raise MicroprobeCodeGenerationError(
"Decorator '%s' already defined for this instruction." % name)
[docs]
def rm_decorator(self, name):
"""
"""
try:
self._decorators.pop(name)
except KeyError:
raise MicroprobeCodeGenerationError(
"Decorator '%s' not defined for this instruction." % name)
@property
def comments(self):
"""List of comments of the instruction (:class:`~.list`). """
return self._comments
[docs]
def allows(self, reg):
"""
:param reg:
"""
return reg in self._allowed_regs
@property
def allowed_regs(self):
"""List of allowed registers of the instructon. """
return self._allowed_regs
[docs]
def set_label(self, label: str | None):
"""
:param label:
"""
if label == "":
raise MicroprobeCodeGenerationError("Do not set the instruction"
" label to an empty string."
"Set it to None if you want"
" to remove it.")
self._label = label
@property
def label(self):
"""Instruction label (:class:`~.str`)."""
return self._label
[docs]
def assembly(self):
"""Assembly representation of the instruction.
:rtype: :class:`~.str`
"""
for operand in self.operands():
if operand.value is None:
raise MicroprobeCodeGenerationError(
"Unable to generate the assembly of an instruction without"
" all the operand values specified")
if self._label is None:
return self._arch_type.assembly(list(self._operands.values()))
return self._label + ":" + \
self._arch_type.assembly(list(self._operands.values()))
[docs]
def binary(self):
"""Binary representation of the instruction.
:rtype: :class:`~.str`
"""
for operand in self.operands():
if operand.value is None:
raise MicroprobeCodeGenerationError(
"Unable to generate the binary of an instruction without"
" all the operand values specified")
return self._arch_type.binary(list(self._operands.values()))
@property
def address(self):
"""Instruction address (:class:`~.InstructionAddress`)"""
return self._address
[docs]
def set_address(self, address: Address):
"""
:param address:
"""
self._address = address
[docs]
def unset_address(self):
"""Unset instruction address."""
self._address = None
def __repr__(self):
""" """
if self._arch_type is not None:
return "%s(%s)" % (self._arch_type.name, self._operands)
else:
return "%s(%s)" % (self.__class__.__name__, "empty")
def __getattr__(self, name):
"""If attribute not found, check if the architecture type implements it
:param name:
"""
try:
return self._arch_type.__getattribute__(name)
except AttributeError:
try:
return self._arch_type.__getattr__(name)
except AttributeError:
raise AttributeError("'%s' object has no attribute '%s'" %
(self.__class__.__name__, name))
def __getstate__(self):
""" """
# We can not pickle/unpickle some functions.
# Therefore, we miss that call-back control to maintain
# the instruction variant. This is not an issue, since we
# do not expect to modify the operands.
# TODO: change operand definition to ensure contant values
# everywhere.
state = self.__dict__
state["_context_callback_check"] = RejectingDict()
state["_context_callback_fix"] = RejectingDict()
for operand in state['_operands'].values():
operand._set_function = None
operand._unset_function = None
for memoperand in state['_mem_operands']:
memoperand._set_address_function = None
memoperand._unset_address_function = None
memoperand._set_length_function = None
memoperand._unset_length_function = None
return state
[docs]
@typeguard_testsuite
def create_dependency_between_ins(output_ins: Instruction,
input_ins: Instruction, context: Context):
"""
:param output_ins:
:param input_ins:
:param context:
"""
reserved_registers = set(context.reserved_registers)
output_operands = [
operand for operand in output_ins.operands() if operand.is_output
and not operand.type.immediate and not operand.type.address_relative
and not operand.type.address_base and not operand.type.address_index
]
input_operands = [
operand for operand in input_ins.operands() if operand.is_input
and not operand.type.immediate and not operand.type.address_relative
and not operand.type.address_base and not operand.type.address_index
]
# prioritize the operands that are only inputs or only outputs
output_operands = [operand for operand in output_operands
if not operand.is_input] + \
[operand for operand in output_operands
if operand.is_input]
input_operands = [operand for operand in input_operands
if not operand.is_output] + \
[operand for operand in input_operands
if operand.is_output]
dependency_set = False
for output_operand in output_operands:
for input_operand in input_operands:
if output_operand.value is not None:
valid_values1 = set([output_operand.value])
else:
valid_values1 = set(output_operand.type.values())
if input_operand.value is not None:
valid_values2 = set([output_operand.value])
else:
valid_values2 = set(input_operand.type.values())
valid_values1 = valid_values1.difference(reserved_registers)
valid_values2 = valid_values2.difference(reserved_registers)
valid_values = valid_values1.intersection(valid_values2)
if len(valid_values) == 0:
continue
valid_value = list(valid_values)[0]
if output_operand.value is None:
output_operand.set_value(valid_value)
if input_operand.value is None:
input_operand.set_value(valid_value)
assert len(valid_values.intersection(reserved_registers)) == 0
dependency_set = True
break
if dependency_set:
break
return dependency_set
[docs]
@typeguard_testsuite
class MicroprobeInstructionDefinition(object):
[docs]
def __init__(self, instruction_type: InstructionType, operands, label,
address: Address | None, asm, decorators, comments):
self._type = instruction_type
self._operands = operands
self._label = label
self._address = address
self._asm = asm
self._decorators = decorators
self._comments = comments
[docs]
def get_instruction_type(self):
return self._type
[docs]
def get_operands(self):
return self._operands
[docs]
def get_label(self):
return self._label
[docs]
def get_address(self):
return self._address
[docs]
def get_asm(self):
return self._asm
[docs]
def get_decorators(self):
return self._decorators
[docs]
def set_instruction_type(self, value):
self._type = value
[docs]
def set_operands(self, value):
self._operands = value
[docs]
def set_label(self, value):
self._label = value
[docs]
def set_address(self, value, convert=True):
if isinstance(value, int) and convert:
value = InstructionAddress(base_address="code", displacement=value)
self._address = value
[docs]
def set_asm(self, value):
self._asm = value
[docs]
def set_decorators(self, value):
self._decorators = value
[docs]
def copy(self):
operands = None
if self._operands is not None:
operands = self._operands[:]
address = None
if self._address is not None:
address = self._address.copy()
decorators = None
if self._decorators is not None:
decorators = copy.deepcopy(self._decorators)
comments = None
if self._comments is not None:
comments = self._comments[:]
return MicroprobeInstructionDefinition(self._type, operands,
self._label, address, self._asm,
decorators, comments)
instruction_type = property(get_instruction_type, set_instruction_type,
None, "type's docstring")
operands = property(get_operands, set_operands, None,
"operands's docstring")
label = property(get_label, set_label, None, "label's docstring")
address = property(get_address, set_address, None, "address's docstring")
asm = property(get_asm, set_asm, None, "asm's docstring")
decorators = property(get_decorators, set_decorators, None,
"decorators's docstring")
comments = property(get_comments, set_comments, None,
"comments's docstring")
def __str__(self):
return "%s(%s,%s,%s,%s,%s,%s,%s)" % (
self.__class__.__name__, self.instruction_type, self.operands,
self.label, self.address, self.asm, self.decorators, self.comments)