# 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.operand` module
"""
# Futures
from __future__ import absolute_import, print_function
# Built-in modules
import abc
import math
import os
import random
# Own modules
from microprobe import MICROPROBE_RC
from microprobe.code.address import Address
from microprobe.code.var import Variable
from microprobe.exceptions import (
MicroprobeArchitectureDefinitionError,
MicroprobeCodeGenerationError,
MicroprobeValueError,
)
from microprobe.target.isa.register import Register
from microprobe.utils.logger import get_logger
from microprobe.utils.misc import OrderedDict, natural_sort
from microprobe.utils.typeguard_decorator import typeguard_testsuite
from microprobe.utils.yaml import read_yaml
# Constants
SCHEMA = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "schemas", "operand.yaml"
)
LOG = get_logger(__name__)
__all__ = [
"import_definition",
"OperandDescriptor",
"MemoryOperandDescriptor",
"MemoryOperand",
"Operand",
"OperandReg",
"OperandImmRange",
"OperandValueSet",
"OperandConst",
"OperandConstReg",
"InstructionAddressRelativeOperand",
]
# Functions
[docs]@typeguard_testsuite
def import_definition(filenames, inherits, registers):
"""
:param filenames:
:param registers:
"""
LOG.debug("Start")
operands = {}
operands_duplicated = {}
register_types = tuple([reg.type.name for reg in registers.values()])
for filename in filenames:
ope_data = read_yaml(filename, SCHEMA)
if ope_data is None:
continue
for elem in ope_data:
name = elem["Name"]
descr = elem.get("Description", "No description")
override = elem.get("Override", False)
key = []
try:
if "Registers" in elem:
regnames = elem["Registers"]
if isinstance(regnames, list):
if (
len(regnames) == 1
and regnames[0] in register_types
):
regs = [
reg
for reg in registers.values()
if reg.type.name == regnames[0]
]
else:
regs = [
registers[regname]
for regname in natural_sort(regnames)
]
key.append(tuple(regnames))
else:
regs = OrderedDict()
for regname in natural_sort(regnames):
regs[registers[regname]] = []
for regname2 in regnames[regname]:
regs[registers[regname]].append(
registers[regname2]
)
key.append(
tuple([(k, tuple(v)) for k, v in regnames.items()])
)
address_base = elem.get("AddressBase", False)
address_index = elem.get("AddressIndex", False)
floating_point = elem.get("FloatingPoint", None)
vector = elem.get("Vector", None)
key.append(address_base)
key.append(address_index)
key.append(floating_point)
key.append(vector)
# Filter out Register without
# representation (N/A)
#
# These are pseudo registers used in
# simulation/emulation environment.
# They are not architected registers.
if isinstance(regs, list):
regs = [
reg for reg in regs if reg.representation != "N/A"
]
elif isinstance(regs, dict):
for elem in regs:
regs[elem] = [
reg2
for reg2 in regs[elem]
if reg2.representation != "N/A"
]
operand = OperandReg(
name,
descr,
regs,
address_base,
address_index,
floating_point,
vector,
)
elif "Min" in elem and "Max" in elem:
minval = elem["Min"]
maxval = elem["Max"]
step = elem.get("Step", 1)
novalues = elem.get("Except", [])
address_index = elem.get("AddressIndex", False)
shift = elem.get("Shift", 0)
add = elem.get("Add", 0)
key.append(minval)
key.append(maxval)
key.append(step)
key.append(tuple(novalues))
key.append(address_index)
key.append(shift)
key.append(add)
operand = OperandImmRange(
name,
descr,
minval,
maxval,
step,
address_index,
shift,
novalues,
add,
)
elif "Values" in elem:
values = tuple(elem["Values"])
rep = elem.get("Representation", None)
key.append(tuple(values))
operand = OperandValueSet(name, descr, values, rep)
elif "Value" in elem:
value = elem["Value"]
key.append(value)
operand = OperandConst(name, descr, value)
elif "Register" in elem:
reg = registers[elem["Register"]]
address_base = elem.get("AddressBase", False)
address_index = elem.get("AddressIndex", False)
floating_point = elem.get("FloatingPoint", False)
vector = elem.get("Vector", False)
key.append(elem["Register"])
key.append(address_base)
key.append(address_index)
key.append(floating_point)
key.append(vector)
operand = OperandConstReg(
name,
descr,
reg,
address_base,
address_index,
floating_point,
vector,
)
elif "Relative" in elem:
mindispl = elem["MinDisplacement"]
maxdispl = elem["MaxDisplacement"]
relative = elem["Relative"]
shift = elem.get("Shift", 0)
step = elem.get("Step", 1)
except_ranges = elem.get("ExceptRange", [])
key.append(mindispl)
key.append(maxdispl)
key.append(shift)
key.append(step)
key.append(tuple([tuple(elem) for elem in except_ranges]))
operand = InstructionAddressRelativeOperand(
name,
descr,
maxdispl,
mindispl,
shift,
except_ranges,
relative,
step,
)
else:
raise MicroprobeArchitectureDefinitionError(
"Operand definition '%s' in '%s' not supported"
% (name, filename)
)
tkey = tuple(key)
if tkey in operands_duplicated:
LOG.warning(
"Similar definition of operands: '%s' and"
" '%s'. Check if definition needed.",
name,
operands_duplicated[tkey],
)
else:
operands_duplicated[tkey] = name
except KeyError as exception:
raise MicroprobeArchitectureDefinitionError(
"Definition"
" of operand '%s' "
"uses an unknown "
"register in '%s'"
"\nMissing defini"
"tion of: %s" % (name, filename, exception)
)
if name in operands and not override and filename not in inherits:
raise MicroprobeArchitectureDefinitionError(
"Duplicated definition of operand '%s' found in '%s'"
% (name, filename)
)
if name in operands:
LOG.debug("Redefined operand: %s", operand)
LOG.debug(operand)
operands[name] = operand
LOG.debug("End")
return operands
@typeguard_testsuite
def _format_integer(operand, value):
if MICROPROBE_RC["hex_all"]:
if hex(value).endswith("L"):
return hex(value)[:-1]
return hex(value)
elif MICROPROBE_RC["hex_none"]:
return str(value)
elif MICROPROBE_RC["hex_address"]:
if (
operand.address_relative
or operand.address_immediate
or operand.address_absolute
):
if hex(value).endswith("L"):
return hex(value)[:-1]
return hex(value)
else:
return str(value)
else:
raise NotImplementedError
# Classes
[docs]@typeguard_testsuite
class OperandDescriptor:
"""Class to represent an operand descriptor."""
[docs] def __init__(self, mtype, is_input, is_output):
"""
:param mtype:
:param is_input:
:param is_output:
"""
self._type = mtype
self._is_input = is_input
self._is_output = is_output
@property
def type(self):
"""Type of the operand descriptor (:class:`~.Operand`)"""
return self._type
@property
def is_input(self):
"""Is input flag (:class:`~.bool`)"""
return self._is_input
@property
def is_output(self):
"""Is output flag (:class:`~.bool`)"""
return self._is_output
[docs] def set_type(self, new_type):
"""
:param new_type:
"""
self._type = new_type
[docs] def copy(self):
"""Return a copy of the Operand descriptor.
:rtype: :class:`~.OperandDescriptor`
"""
return OperandDescriptor(self.type, self.is_input, self.is_output)
def __repr__(self):
return "%s(%s, %s, %s)" % (
self.__class__.__name__,
self._type,
self._is_input,
self._is_output,
)
[docs]@typeguard_testsuite
class MemoryOperandDescriptor:
"""Class to represent a memory operand descriptor."""
[docs] def __init__(self, otype, io, bit_rate):
"""
:param otype:
:param io:
:param bit_rate:
"""
self._type = otype
self._is_load = "I" in io
self._is_store = "O" in io
self._is_prefetch = "P" in io
self._is_agen = "agen" in io
self._is_branch_target = "B" in io
self._bit_rate = bit_rate
assert (
(self.is_load or self.is_store)
^ self.is_agen
^ self.is_prefetch
^ self.is_branch_target
)
@property
def type(self):
"""Memory operand descriptor type (:class:`~.Operand`)"""
return self._type
@property
def is_load(self):
"""Is load flag (:class:`~.bool`)"""
return self._is_load
@property
def is_store(self):
"""Is store flag (:class:`~.bool`)"""
return self._is_store
@property
def is_agen(self):
"""Is an address generator flag (:class:`~.bool`)"""
return self._is_agen
@property
def is_prefetch(self):
"""Is prefech flag (:class:`~.bool`)"""
return self._is_prefetch
@property
def is_branch_target(self):
"""Is a branch target (:class:`~.bool`)"""
return self._is_branch_target
@property
def bit_rate(self):
"""Memory operand bit rate (::class:`~.int`)"""
return self._bit_rate
def __str__(self):
"""Return string representation.
:rtype: :class:`~.str`
"""
rstr = f"{self.__class__.__name__}({self.type},"
if self.is_load:
rstr = f"{rstr}load,"
if self.is_store:
rstr = f"{rstr}store,"
if self.is_prefetch:
rstr = f"{rstr}prefetch,"
if self.is_agen:
rstr = f"{rstr}address_generator,"
if self.is_branch_target:
rstr = f"{rstr}branch_target,"
rstr = f"{rstr})"
return rstr
[docs] def full_report(self, tabs=0):
shift = "\t" * (tabs + 1)
# rstr = shift + "Type: \n"
rstr = f"{self.type.full_report(tabs=tabs)}\n"
rstr += f"{shift}Load: {self.is_load}\n"
rstr += f"{shift}Store: {self.is_store}\n"
rstr += f"{shift}Prefetch: {self.is_prefetch}\n"
rstr += f"{shift}Address generator: {self.is_agen}\n"
rstr += f"{shift}Branch target: {self.is_branch_target}"
return rstr
[docs]@typeguard_testsuite
class MemoryOperand:
"""This represents a machine instruction memory operand. It contains
the operands, the formula, the
"""
_cmp_attributes = ["_address", "_length"]
[docs] def __init__(self, address_formula, length_formula):
"""
:param address_formula:
:param length_formula:
"""
self._address = address_formula
self._length = length_formula
@property
def address_operands(self):
""" """
return self._address
@property
def length_operands(self):
return self._length
def _check_cmp(self, other):
if not isinstance(other, self.__class__):
raise NotImplementedError(f"{other.__class__} != {self.__class__}")
def __eq__(self, other):
"""x.__eq__(y) <==> x==y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if not getattr(self, attr) == getattr(other, attr):
return False
return True
def __ne__(self, other):
"""x.__ne__(y) <==> x!=y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if not getattr(self, attr) == getattr(other, attr):
return True
return False
def __lt__(self, other):
"""x.__lt__(y) <==> x<y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if getattr(self, attr) < getattr(other, attr):
return True
elif getattr(self, attr) > getattr(other, attr):
return False
return False
def __gt__(self, other):
"""x.__gt__(y) <==> x>y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if getattr(self, attr) > getattr(other, attr):
return True
elif getattr(self, attr) < getattr(other, attr):
return False
return False
def __le__(self, other):
"""x.__le__(y) <==> x<=y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if getattr(self, attr) <= getattr(other, attr):
continue
else:
return False
return True
def __ge__(self, other):
"""x.__ge__(y) <==> x>=y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if getattr(self, attr) >= getattr(other, attr):
continue
else:
return False
return True
def __str__(self):
return "%s(Address: %s, Length: %s)" % (
self.__class__.__name__,
self._address,
self._length,
)
[docs] def full_report(self, tabs=0):
shift = "\t" * (tabs + 1)
rstr = f"{shift}Address: {list(self.address_operands.keys())}\n"
rstr += f"{shift}Length: {list(self.length_operands.keys())}"
return rstr
[docs]@typeguard_testsuite
class Operand(abc.ABC):
"""This represents a machine instruction operand"""
_cmp_attributes = [
"_name",
"_descr",
"_ai",
"_ab",
"_aim",
"_imm",
"_const",
"_rel",
"_rela",
"_fp",
"_vector",
]
[docs] @abc.abstractmethod
def __init__(self, name, descr):
"""
:param name:
:param descr:
"""
self._name = name
self._descr = descr
self._ai = False
self._ab = False
self._aim = False
self._imm = False
self._const = False
self._rel = False
self._rela = False
self._fp = False
self._vector = False
@property
def name(self):
"""Operand name (:class:`~.str`)."""
return self._name
@property
def description(self):
"""Operand description (:class:`~.str`)."""
return self._descr
@property
def address_relative(self):
"""Operand is for generating relative addresses (:class:`~.bool`)."""
return self._rel
@property
def address_absolute(self):
"""Operand is for generating absolute addresses (:class:`~.bool`)."""
return self._rela
@property
def address_immediate(self):
"""Operand is an immediate of an address (:class:`~.bool`)."""
return self._aim
@property
def float(self):
"""Operand is float (:class:`~.bool`)."""
return self._fp
@property
def address_base(self):
"""Operand is the base register for an address (:class:`~.bool`)."""
return self._ab
@property
def address_index(self):
"""Operand is the index register for an address (:class:`~.bool`)."""
return self._ai
@property
def immediate(self):
"""Operand is immediate (:class:`~.bool`)."""
return self._imm
@property
def vector(self):
"""Operand is vector (:class:`~.bool`)."""
return self._vector
@property
def constant(self):
"""Operand is constant (:class:`~.bool`)."""
return self._const
[docs] @abc.abstractmethod
def copy(self):
"""Return a copy of the operand."""
raise NotImplementedError
[docs] @abc.abstractmethod
def values(self):
"""Return the possible value of the operand."""
raise NotImplementedError
[docs] @abc.abstractmethod
def random_value(self, rand: random.Random):
"""Return a random possible value for the operand."""
raise NotImplementedError
[docs] @abc.abstractmethod
def representation(self, value):
"""Return the string representation of the operand.
:param value: value of the operand
:type value: :class:`~.str`, :class:`~.Register` or
:class:`int`
:rtype: :class:`~.str`
"""
raise NotImplementedError
[docs] @abc.abstractmethod
def codification(self, value):
"""Return the binary codification of the operand.
:param value: value of the operand.
:type value: :class:`~.str`, :class:`~.Register` or
:class:`int`
:rtype: :class:`~.str`
"""
raise NotImplementedError
[docs] @abc.abstractmethod
def access(self, value):
"""
:param value:
"""
raise NotImplementedError
[docs] @abc.abstractmethod
def set_valid_values(self, values):
"""Sets the set of valid value for the operand.
:param value: value of the operand.
:type value: :class:`list` of :class:`~.str`,
:class:`~.Register` or :class:`int`
"""
raise NotImplementedError
@abc.abstractmethod
def __contains__(self, value):
"""
:param value:
"""
raise NotImplementedError
[docs] def check(self, value):
"""Check if a value is valid for the operand.
:param value: value of the operand.
:type value: :class:`~.str`, :class:`~.Register` or
:class:`int`
:raise microprobe.exceptions.MicroprobeValueError: if
the value is not allowed for the operand
"""
if not self.__contains__(value):
raise MicroprobeValueError(
"Invalid operand value %s not in %s"
% (value, list(self.values()))
)
def __str__(self):
return "%-8s: %s (%s)" % (
self.name,
self.description,
self.__class__.__name__,
)
def __repr__(self):
return '%s("%s", "%s")' % (
self.__class__.__name__,
self.name,
self.description,
)
def _check_cmp(self, other):
if not isinstance(other, self.__class__):
raise NotImplementedError(f"{other.__class__} != {self.__class__}")
def __eq__(self, other):
"""x.__eq__(y) <==> x==y"""
if not isinstance(other, self.__class__):
return False
for attr in self._cmp_attributes:
if not getattr(self, attr) == getattr(other, attr):
return False
return True
def __ne__(self, other):
"""x.__ne__(y) <==> x!=y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if not getattr(self, attr) == getattr(other, attr):
return True
return False
def __lt__(self, other):
"""x.__lt__(y) <==> x<y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if getattr(self, attr) < getattr(other, attr):
return True
elif getattr(self, attr) > getattr(other, attr):
return False
return False
def __gt__(self, other):
"""x.__gt__(y) <==> x>y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if getattr(self, attr) > getattr(other, attr):
return True
elif getattr(self, attr) < getattr(other, attr):
return False
return False
def __le__(self, other):
"""x.__le__(y) <==> x<=y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if getattr(self, attr) <= getattr(other, attr):
continue
else:
return False
return True
def __ge__(self, other):
"""x.__ge__(y) <==> x>=y"""
self._check_cmp(other)
for attr in self._cmp_attributes:
if getattr(self, attr) >= getattr(other, attr):
continue
else:
return False
return True
[docs]@typeguard_testsuite
class OperandReg(Operand):
"""Class to represent a register operand."""
[docs] def __init__(
self,
name,
descr,
regs,
address_base,
address_index,
floating_point,
vector,
):
"""
:param name:
:param descr:
:param regs:
:param address_base:
:param address_index:
:param floating_point:
:param vector:
"""
super(OperandReg, self).__init__(name, descr)
if isinstance(regs, list):
self._regs = OrderedDict()
for reg in regs:
self._regs[reg] = [reg]
else:
self._regs = regs
self._ab = address_base
self._ai = address_index
self._fp = floating_point
self._vector = vector
if self._fp is None:
self._fp = list(set([reg.type for reg in self._regs]))[
0
].used_for_float_arithmetic
if self._vector is None:
self._vector = list(set([reg.type for reg in self._regs]))[
0
].used_for_vector_arithmetic
[docs] def values(self):
"""Return the possible value of the operand.
:rtype: :class:`list` of :class:`~.Register`
"""
return list(self._regs.keys())
[docs] def representation(self, value):
"""
:param value:
"""
return value.representation
[docs] def codification(self, value):
"""
:param value:
"""
return value.codification
[docs] def random_value(self, rand: random.Random):
"""Return a random possible value for the operand.
:rtype: :class:`~.Register`
"""
return list(self._regs.keys())[rand.randrange(0, len(self._regs))]
[docs] def access(self, value):
"""
:param value:
"""
return self._regs[value]
def __contains__(self, value):
"""
:param value:
"""
if not isinstance(value, Register):
return False
return value.name in [reg.name for reg in self.values()]
[docs] def copy(self):
return OperandReg(
self.name,
self.description,
self._regs.copy(),
self._ab,
self._ai,
self._fp,
self._vector,
)
[docs] def set_valid_values(self, values):
"""
:param values:
"""
assert len(values) > 0
for value in self.values():
if value not in values:
del self._regs[value]
assert sorted(self.values()) == sorted(
values
), f"\nValues: {sorted(values)} \nValues(): {sorted(self.values())}"
self._const = len(values) == 1
[docs]@typeguard_testsuite
class OperandImmRange(Operand):
"""Class to represent a immediate range operand."""
[docs] def __init__(
self, name, descr, minvalue, maxvalue, step, aim, shift, novalues, add
):
"""
:param name:
:param descr:
:param minvalue:
:param maxvalue:
:param step:
:param aim:
:param shift:
:param novalues:
:param add:
"""
super(OperandImmRange, self).__init__(name, descr)
self._min = minvalue
self._max = maxvalue
self._step = step
self._aim = aim # Address Immediate?
self._shift = shift
self._imm = True
self._novalues = novalues
self._add = add
self._computed_values = None
[docs] def copy(self):
return OperandImmRange(
self.name,
self.description,
self._min,
self._max,
self._step,
self._aim,
self._shift,
self._novalues,
self._add,
)
[docs] def values(self):
"""Return the possible value of the operand.
:rtype: list of ::class:`~.int`
"""
if self._computed_values is None:
self._computed_values = [
elem
for elem in range(self._min, self._max + 1, self._step)
if elem not in self._novalues
]
return self._computed_values
[docs] def set_valid_values(self, values):
"""
:param values:
"""
if len(values) == 0:
raise MicroprobeCodeGenerationError(
"Setting an operand without any valid value. Please check "
"the definition files. Previous value: '%s'. New values: '%s'"
"." % (list(self.values()), values)
)
for value in values:
assert value in list(self.values())
self._computed_values = values
self._const = len(values) == 1
[docs] def random_value(self, rand: random.Random):
"""Return a random possible value for the operand.
:rtype: ::class:`~.int`
"""
if self._computed_values is not None:
return self._computed_values[
rand.randrange(0, len(self._computed_values))
]
if MICROPROBE_RC["pattern"] != "random":
bits = math.ceil(math.log2(self._max + 1))
mask = (1 << bits) - 1
value = MICROPROBE_RC["pattern"] & mask
val = MICROPROBE_RC["pattern"]
if MICROPROBE_RC["pattern_update"] == "negate":
MICROPROBE_RC["pattern"] = ~val & 0xFFFFFFFFFFFFFFFF
if MICROPROBE_RC["pattern_update"] == "rotate":
MICROPROBE_RC["pattern"] = (
(val << 1) | (val >> (64 - 1))
) & 0xFFFFFFFFFFFFFFFF
valid = False
count = 0
nvalue = value
while not valid:
try:
valid = self.check(nvalue)
except MicroprobeValueError:
valid = False
nvalue = nvalue + 1
count = count + 1
if count == 1000:
break
if not valid:
count = 0
nvalue = value
while not valid:
try:
valid = self.check(nvalue)
except MicroprobeValueError:
valid = False
nvalue = nvalue - 1
count = count + 1
if count == 1000:
break
if not valid:
nvalue = rand.randrange(self._min, self._max + 1, self._step)
return nvalue
value = rand.randrange(self._min, self._max + 1, self._step)
if value not in self._novalues:
return value
else:
return self.random_value(rand)
[docs] def representation(self, value):
"""
:param value:
"""
# Immediate displacements sometimes contain
# variable names instead of a number
if isinstance(value, str):
# print value
return value
# return _format_integer(self, (value >> self._shift) + self._add)
return _format_integer(self, value + self._add)
[docs] def codification(self, value):
"""
:param value:
"""
return str(value >> self._shift)
@property
def max(self):
return self._max
@property
def min(self):
return self._min
@property
def step(self):
return self._step
@property
def shift(self):
return self._shift
@property
def add(self):
return self._add
[docs] def check(self, value):
"""
:param value:
"""
if not isinstance(value, int):
raise MicroprobeValueError(
"Invalid operand value: '%s'. Integer"
" required and '%s' provided" % (value, type(value))
)
# value = value >> self._shift
if (
value <= self._max
and value >= self._min
and (value - self._min) % self._step == 0
and value not in self._novalues
):
return True
else:
raise MicroprobeValueError(
"Invalid operand value: %d (max: %d,"
" min: %d)" % (value, self._max, self._min)
)
[docs] def access(self, dummy):
"""
:param dummy:
"""
return []
def __contains__(self, value):
"""
:param value:
"""
raise NotImplementedError
[docs]@typeguard_testsuite
class OperandValueSet(Operand):
"""Class to represent a value set operand."""
[docs] def __init__(self, name, descr, values, rep):
"""
:param name:
:param descr:
:param values:
"""
super(OperandValueSet, self).__init__(name, descr)
# TODO: add input value checking
self._values = values
self._imm = True
self._rep = None
if rep is not None and len(rep) != len(values):
raise MicroprobeArchitectureDefinitionError(
"Values and representation of operand definition "
"'%s' do not have the same length." % name
)
if rep is not None:
self._rep = dict(zip(values, rep))
[docs] def copy(self):
return OperandValueSet(
self.name, self.description, self._values, self._rep
)
[docs] def values(self):
"""Return the possible value of the operand.
:rtype: list of ::class:`~.int`
"""
return self._values
[docs] def representation(self, value):
"""
:param value:
"""
if self._rep is None:
return _format_integer(self, value)
return self._rep[value]
[docs] def codification(self, value):
"""
:param value:
"""
return str(value)
[docs] def random_value(self, rand: random.Random):
"""Return a random possible value for the operand.
:rtype: ::class:`~.int`
"""
return self._values[rand.randrange(0, len(self._values))]
[docs] def access(self, dummy):
"""
:param dummy:
"""
return []
@property
def shift(self):
return 0
@property
def min(self):
return min(self._values)
def __contains__(self, value):
"""
:param value:
"""
return value in list(self.values())
[docs] def set_valid_values(self, values):
"""
:param values:
"""
assert len(values) > 0
for value in values:
assert value in list(self.values())
self._values = values
self._const = len(values) == 1
[docs]@typeguard_testsuite
class OperandConst(Operand):
"""Class to represent a constant operand."""
[docs] def __init__(self, name, descr, value, aim=False, arel=False):
"""
:param name:
:param descr:
:param value:
"""
super(OperandConst, self).__init__(name, descr)
self._value = value
self._imm = True
self._aim = aim
self._rel = arel
self._const = True
[docs] def copy(self):
return OperandConst(self.name, self.description, self._value)
[docs] def values(self):
"""Return the possible value of the operand.
:rtype: list of ::class:`~.int`
"""
return [self._value]
[docs] def representation(self, value):
"""
:param value:
"""
return _format_integer(self, value)
[docs] def codification(self, value):
"""
:param value:
"""
return str(value)
[docs] def random_value(self, rand: random.Random):
"""Return a random possible value for the operand.
:rtype: ::class:`~.int`
"""
return self._value
@property
def shift(self):
return 0
@property
def min(self):
return self._value
[docs] def access(self, dummy):
"""
:param dummy:
"""
return []
def __contains__(self, value):
"""
:param value:
"""
return value in list(self.values())
[docs] def set_valid_values(self, values):
"""
:param values:
"""
assert len(values) == 1
for value in values:
assert value in list(self.values())
self._value = values[0]
[docs]@typeguard_testsuite
class OperandConstReg(Operand):
"""Class to represent a constant register operand."""
[docs] def __init__(
self,
name,
descr,
reg,
address_base,
address_index,
floating_point,
vector,
):
"""
:param name:
:param descr:
:param reg:
:param address_base:
:param address_index:
:param floating_point:
:param vector:
"""
super(OperandConstReg, self).__init__(name, descr)
self._reg = reg
self._regs = [reg]
self._const = True
self._ab = address_base
self._ai = address_index
self._fp = floating_point
self._vector = vector
if self._fp is None:
self._fp = list(set([reg.type for reg in self._regs]))[
0
].used_for_float_arithmetic
if self._vector is None:
self._vector = list(set([reg.type for reg in self._regs]))[
0
].used_for_float_arithmetic
[docs] def copy(self):
return OperandConstReg(
self.name,
self.description,
self._reg,
self._ab,
self._ai,
self._fp,
self._vector,
)
[docs] def values(self):
"""Return the possible value of the operand.
:rtype: list of :class:`~.Register`
"""
return [self._reg]
[docs] def random_value(self, rand: random.Random):
"""Return a random possible value for the operand.
:rtype: :class:`~.Register`
"""
return self._reg
[docs] def representation(self, value):
"""
:param value:
"""
return value.representation
[docs] def codification(self, value):
"""
:param value:
"""
return value.codification
[docs] def access(self, value):
"""
:param value:
"""
return [value]
def __contains__(self, value):
"""
:param value:
"""
if not isinstance(value, Register):
return False
return value.name in [reg.name for reg in self.values()]
[docs] def set_valid_values(self, values):
"""
:param values:
"""
assert len(values) == 1
for value in values:
assert value in list(self.values())
self._reg = values[0]
[docs]@typeguard_testsuite
class InstructionAddressRelativeOperand(Operand):
"""Class to represent a relative instruction address operand.
Relative instruction address operands are used for immediates operands
used to compute relative distance between the current instruction
and the target. Examples are: branch relative, or load address relative.
"""
[docs] def __init__(
self,
name,
descr,
maxdispl,
mindispl,
shift,
except_range,
relative,
step,
):
"""Create a InstructionAddressRelativeOperand object.
:param name: Operand name
:type name: :class:`~.str`
:param descr: Operand description
:type descr: :class:`~.str`
:param maxdispl: Maximum displacement allowed
:type maxdispl: ::class:`~.int`
:param mindispl: Minimum displacement allowed
:type mindispl: ::class:`~.int`
:param shift: Number of shifted bits
:type shift: ::class:`~.int`
:param except_range: list of forbidden ranges for displacement. Ranges
are represented using (lower_bound, upper_bound)
:type except_range: :class:`~.list` of \
:func:`tuple` with :class:`~.int`
:rtype: :class:`~.InstructionAddressRelativeOperand`
"""
super(InstructionAddressRelativeOperand, self).__init__(name, descr)
self._maxdispl = maxdispl
self._mindispl = mindispl
self._rel = relative
self._rela = not relative
self._shift = shift
self._except = except_range
self._step = step
[docs] def copy(self):
return InstructionAddressRelativeOperand(
self.name,
self.description,
self._maxdispl,
self._mindispl,
self._shift,
self._except,
self._rel,
self._step,
)
[docs] def values(self):
"""Return the possible value of the operand.
:rtype: list of ::class:`~.int`
"""
return [self._mindispl << self._shift]
[docs] def random_value(self, rand: random.Random):
"""Return a random possible value for the operand.
:rtype: ::class:`~.int`
"""
value = rand.randrange(self._mindispl, self._maxdispl) << self._shift
if (
value <= (self._maxdispl << self._shift)
and value >= (self._mindispl << self._shift)
and not self._in_except_ranges(value)
and value % self._step == 0
):
return value
else:
return self.random_value(rand)
return value
[docs] def representation(self, value):
"""
:param value:
"""
assert isinstance(value, tuple([int, Address]))
if isinstance(value, int):
# print(value, self._shift)
# assert value % (self._shift + 1) == 0
return _format_integer(self, value)
else:
base_address = value.base_address
displacement = value.displacement
if isinstance(base_address, Variable):
str_value = base_address.name
elif isinstance(base_address, str):
str_value = base_address
else:
raise MicroprobeCodeGenerationError(
"Unable to generate the string representation of '%s'"
" with value: '%s'" % (self, value)
)
if displacement > 0:
str_value = f"{str_value}+0x{displacement:x}"
elif displacement < 0:
str_value = f"{str_value}-0x{abs(displacement):x}"
return str_value
def _in_except_ranges(self, value):
"""
:param value:
"""
for irange in self._except:
if value >= (irange[0] << self._shift) and value <= (
irange[1] << self._shift
):
return True
return False
[docs] def check(self, value):
"""
:param value:
"""
if isinstance(value, int):
cvalue = value
elif isinstance(value, Address):
# Warning!
return
else:
if not isinstance(value[0], Address) or not isinstance(
value[1], Address
):
raise MicroprobeValueError(
"Invalid operand value '%s'." " Any Address?" % (value)
)
cvalue = value[0] - value[1]
if (
cvalue > (self._maxdispl << self._shift)
or cvalue < (self._mindispl << self._shift)
or self._in_except_ranges(cvalue)
or cvalue % self._step != 0
):
raise MicroprobeValueError(
"Invalid operand value '%d' "
"not within the"
" allowed range (%d, %d) and exceptions"
" '%s' "
% (cvalue, self._mindispl, self._maxdispl, self._except)
)
[docs] def codification(self, value):
"""
:param value:
"""
if isinstance(value, int):
return str(value >> self._shift)
elif isinstance(value, Address):
raise MicroprobeCodeGenerationError(
"Unable to codify the"
" symbolic address: %s ."
" Consider to add a pass to"
" translate them to actual "
"values " % value
)
else:
raise NotImplementedError
@property
def shift(self):
return self._shift
[docs] def access(self, dummy):
"""
:param dummy:
"""
return []
[docs] def set_valid_values(self, values):
"""
:param values:
"""
raise NotImplementedError
def __contains__(self, value):
"""
:param value:
"""
raise NotImplementedError