2026-07-10 10:38:57 +02:00

647 lines
23 KiB
Python

import copy
import logging
import math
from ScEpTIC import tools
from ScEpTIC.AST.elements.instruction import Instruction
from ScEpTIC.AST.elements.types import BaseType
from ScEpTIC.AST.misc.virtual_memory_enum import VirtualMemoryEnum
from ScEpTIC.exceptions import RuntimeException, RegAllocException
class CompareOperation(Instruction):
"""
AST node of the LLVM Other Instructions group - Compare Instruction
https://llvm.org/docs/LangRef.html#otherops
"""
def __init__(self, target, first_operand, second_operand, condition, comparation_type, operation_code):
super().__init__()
self.target = target
self.first_operand = first_operand
self.second_operand = second_operand
self.condition = condition
self.type = comparation_type
self.operation_code = operation_code
def __str__(self):
retstr = super().__str__()
retstr += 'cmp {}: {} vs {}'.format(self.condition, self.first_operand, self.second_operand)
return retstr
@staticmethod
def is_qnan(value):
"""
Returns if a value is a QNAN
"""
# if value is None -> QNAN (for my memory representation)
return value is None or math.isnan(value)
@staticmethod
def is_one_qnan(value1, value2):
"""
Returns if at least one of two values are QNAN
"""
return CompareOperation.is_qnan(value1) or CompareOperation.is_qnan(value2)
def _check_prefix(self, prefix1, prefix2):
"""
Verify if address prefixes are consistent.
"""
if prefix1 != prefix2:
raise RuntimeException('Unable to compare different address spaces ({} with {}) for the compare operation {}.'.format(prefix1, prefix2, self.condition))
def get_val(self):
"""
Returns the value obtained from the operation.
It converts address operands (if present) to relative spaces, and applies the comparisons.
"""
dim = len(self.first_operand.type)
first_operand = self.first_operand.get_val()
second_operand = self.second_operand.get_val()
# address prefixes (populated if an operand is an address)
prefix1 = None
prefix2 = None
# if first_operand is an address, get prefix and relative address (which can be used in comparisons)
if self._vmstate.memory._is_absolute_address(first_operand):
prefix1, first_operand = self._vmstate.memory._parse_absolute_address(first_operand)
# if second_operand is an address, get prefix and relative address (which can be used in comparisons)
if self._vmstate.memory._is_absolute_address(second_operand):
prefix2, second_operand = self._vmstate.memory._parse_absolute_address(second_operand)
# adjust prefixes if only one element is an address
# NB: is possible to compare an address with a direct value, and in this case the prefix can be ignored.
# (so I just copy it to the other prefix)
if prefix1 is None:
prefix1 = prefix2
elif prefix2 is None:
prefix2 = prefix1
logging.info('[{}] Executing {} {}{}, {}{}'.format(self.instruction_type, self.condition, '' if prefix1 is None else prefix1, first_operand, '' if prefix2 is None else prefix2, second_operand))
# comparison types
if self.condition == 'eq':
return prefix1 == prefix2 and first_operand == second_operand
elif self.condition == 'ne':
return prefix1 != prefix2 or first_operand != second_operand
elif self.condition == 'sgt':
self._check_prefix(prefix1, prefix2)
return first_operand > second_operand
elif self.condition == 'sge':
self._check_prefix(prefix1, prefix2)
return first_operand >= second_operand
elif self.condition == 'slt':
self._check_prefix(prefix1, prefix2)
return first_operand < second_operand
elif self.condition == 'sle':
self._check_prefix(prefix1, prefix2)
return first_operand <= second_operand
elif self.condition == 'ugt':
self._check_prefix(prefix1, prefix2)
if self.operation_code == 'fcmp':
# unordered check -> true if one is QNAN
return self.is_one_qnan(first_operand, second_operand) or first_operand > second_operand
else:
# converts to unsigned ints
first_operand = self.first_operand.convert_sint_to_uint(first_operand, dim)
second_operand = self.second_operand.convert_sint_to_uint(second_operand, dim)
return first_operand > second_operand
elif self.condition == 'uge':
self._check_prefix(prefix1, prefix2)
if self.operation_code == 'fcmp':
# unordered check -> true if one is QNAN
return self.is_one_qnan(first_operand, second_operand) or first_operand >= second_operand
else:
# converts to unsigned ints
first_operand = self.first_operand.convert_sint_to_uint(first_operand, dim)
second_operand = self.second_operand.convert_sint_to_uint(second_operand, dim)
return first_operand >= second_operand
elif self.condition == 'ult':
self._check_prefix(prefix1, prefix2)
if self.operation_code == 'fcmp':
# unordered check -> true if one is QNAN
return self.is_one_qnan(first_operand, second_operand) or first_operand < second_operand
else:
# converts to unsigned ints
first_operand = self.first_operand.convert_sint_to_uint(first_operand, dim)
second_operand = self.second_operand.convert_sint_to_uint(second_operand, dim)
return first_operand < second_operand
elif self.condition == 'ule':
self._check_prefix(prefix1, prefix2)
if self.operation_code == 'fcmp':
# unordered check -> true if one is QNAN
return self.is_one_qnan(first_operand, second_operand) or first_operand <= second_operand
else:
# converts to unsigned ints
first_operand = self.first_operand.convert_sint_to_uint(first_operand, dim)
second_operand = self.second_operand.convert_sint_to_uint(second_operand, dim)
return first_operand <= second_operand
elif self.condition == 'true':
return True
elif self.condition == 'false':
return False
elif self.condition == 'ord':
self._check_prefix(prefix1, prefix2)
return not self.is_one_qnan(first_operand, second_operand)
elif self.condition == 'oeq':
return not self.is_one_qnan(first_operand, second_operand) and prefix1 == prefix2 and first_operand == second_operand
elif self.condition == 'one':
return not self.is_one_qnan(first_operand, second_operand) and (prefix1 != prefix2 or first_operand != second_operand)
elif self.condition == 'ogt':
self._check_prefix(prefix1, prefix2)
return not self.is_one_qnan(first_operand, second_operand) and first_operand > second_operand
elif self.condition == 'oge':
self._check_prefix(prefix1, prefix2)
return not self.is_one_qnan(first_operand, second_operand) and first_operand >= second_operand
elif self.condition == 'olt':
self._check_prefix(prefix1, prefix2)
return not self.is_one_qnan(first_operand, second_operand) and first_operand < second_operand
elif self.condition == 'ole':
self._check_prefix(prefix1, prefix2)
return not self.is_one_qnan(first_operand, second_operand) and first_operand <= second_operand
elif self.condition == 'uno':
self._check_prefix(prefix1, prefix2)
return self.is_one_qnan(first_operand, second_operand)
elif self.condition == 'ueq':
return self.is_one_qnan(first_operand, second_operand) or (prefix1 == prefix2 and first_operand == second_operand)
elif self.condition == 'une':
return self.is_one_qnan(first_operand, second_operand) or (prefix1 != prefix2 or first_operand != second_operand)
# if gets there, the comparison mode is not supported.
raise NotImplementedError('Comparison mode "{}" not supported for now!'.format(self.condition))
def get_uses(self):
"""
Returns a list containing the names of the registers used by this instruction.
(used by register allocation)
"""
first_reg = self.first_operand.get_uses()
second_reg = self.second_operand.get_uses()
return first_reg + second_reg
def get_input_lookup(self):
"""
Returns the input lookup data for the current operation
"""
first = self.first_operand.get_input_lookup()
second = self.second_operand.get_input_lookup()
return tools.merge_input_lookup_data(first, second)
def replace_reg_name(self, old_reg_name, new_reg_name):
"""
Replaces the name of a register used by the instruction with a new one.
(used by register allocation)
"""
self.first_operand.replace_reg_name(old_reg_name, new_reg_name)
self.second_operand.replace_reg_name(old_reg_name, new_reg_name)
self.target.replace_reg_name(old_reg_name, new_reg_name)
class PhiOperation(Instruction):
"""
AST node of the LLVM Other Instructions group - Phi Instruction
https://llvm.org/docs/LangRef.html#otherops
"""
def __init__(self, target, return_type, values):
super().__init__()
self.target = target
self.type = return_type
# values is a dict with label as keys {labelid: value}
self.values = values
def __str__(self):
retstr = super().__str__()
retstr += 'phi {}: {}'.format(self.type, self.values)
return retstr
def get_val(self):
"""
Returns the value obtained from the operation.
"""
# get the basic block from which the current one is entered.
label = self._vmstate.register_file.last_basic_block
if label not in self.values:
raise RuntimeException('Basic Block {} not present in the choices of phi operation.'.format(label))
# get value
value = self.values[label].get_val()
logging.info('[{}] Executing PHI operation with result {} ({} taken).'.format(self.instruction_type, value, label))
return value
def get_uses(self):
"""
Returns a list containing the names of the registers used by this instruction.
(used by register allocation)
"""
uses = []
for value in self.values:
value = self.values[value].get_uses()
uses = uses + value
return uses
def get_input_lookup(self):
"""
Returns the input lookup data for the current operation
"""
label = self._vmstate.register_file.last_basic_block
return self.values[label].get_input_lookup()
def replace_reg_name(self, old_reg_name, new_reg_name):
"""
Replaces the name of a register used by the instruction with a new one.
(used by register allocation)
"""
for value in self.values:
self.values[value].replace_reg_name(old_reg_name, new_reg_name)
self.target.replace_reg_name(old_reg_name, new_reg_name)
class SelectOperation(Instruction):
"""
AST node of the LLVM Other Instructions group - Select Instruction
https://llvm.org/docs/LangRef.html#otherops
"""
def __init__(self, target, condition, first_operand, second_operand):
super().__init__()
self.target = target
self.condition = condition
self.first_operand = first_operand
self.second_operand = second_operand
def __str__(self):
retstr = super().__str__()
retstr += 'select {}: {}, {}'.format(self.condition, self.first_operand, self.second_operand)
return retstr
def get_val(self):
"""
Returns the result of the operation.
"""
# if condition resolves to 1 returns the first operand, otherwise the second one.
condition = self.condition.get_val()
logging.info('[{}] Executing Select operation with condition {}.'.format(self.instruction_type, condition))
if int(condition) == 1:
return self.first_operand.get_val()
else:
return self.second_operand.get_val()
def get_input_lookup(self):
"""
Returns the input lookup data for the current operation
"""
condition = self.condition.get_val()
if int(condition) == 1:
return self.first_operand.get_input_lookup()
else:
return self.second_operand.get_input_lookup()
def get_uses(self):
"""
Returns a list containing the names of the registers used by this instruction.
(used by register allocation)
"""
first_reg = self.first_operand.get_uses()
second_reg = self.second_operand.get_uses()
cond_reg = self.condition.get_uses()
return first_reg + second_reg + cond_reg
def replace_reg_name(self, old_reg_name, new_reg_name):
"""
Replaces the name of a register used by the instruction with a new one.
(used by register allocation)
"""
self.first_operand.replace_reg_name(old_reg_name, new_reg_name)
self.second_operand.replace_reg_name(old_reg_name, new_reg_name)
self.condition.replace_reg_name(old_reg_name, new_reg_name)
self.target.replace_reg_name(old_reg_name, new_reg_name)
class CallOperation(Instruction):
"""
AST node of the LLVM Other Instructions group - Call Instruction
https://llvm.org/docs/LangRef.html#otherops
"""
# push pc
# push ebp
# esp <- ebp
# pc = 0x...
# args are pushed/loaded by previously-inserted operations
tick_count = 4
memory_tick_count = 2
n_memory_instructions = 2
ignore = []
def __init__(self, target, function_name, return_type, function_signature, function_args, attrs):
super().__init__()
self.target = target
self.target_function = function_name
self.type = return_type
# is a possible empty list containing the function signature when var_args is used
# is a list of Type with additional attributes field
self.function_signature = function_signature
# is a list of Values with additional attributes field.
self.function_args = function_args
# all attributes used for back-end / middle-end optimizations.
# the only one needed are from 'return' and are zeroext and signext.
# the same applies for the function arguments's attributes.
# for how data is represented, only signext applies a modification on data,
# and since functions are user-defined and no 1bit data structure exists in C,
# sext won't have any unattended behaviour (e.g. i1 1 = -1), so can be omitted.
self.attributes = attrs
self.virtual_memory_target = VirtualMemoryEnum.VOLATILE
# checkpoint-related infos
self.checkpoint_save_regs = set()
self.checkpoint_save_pc = False
self.checkpoint_save_esp = False
self.checkpoint_save_ram = False
# Custom callback used for ISR and other functionalities
self.custom_callback = lambda: None
self.is_isr_call = False
def __str__(self):
retstr = super().__str__()
retstr += 'call {}({})'.format(self.name, self.function_args)
retstr += f" (${self.virtual_memory_target.value})"
if self.checkpoint_save_pc:
retstr += f"\n - save_pc: {self.checkpoint_save_pc}"
retstr += f"\n - save_esp: {self.checkpoint_save_esp}"
retstr += f"\n - save_regs: {self.checkpoint_save_regs}"
return retstr
def get_function_max_reg_usage(self):
"""
Returns the number of physical registers used by this function.
"""
function = self._vmstate.functions[self.name]
if 'max_reg_usage' not in function.__dict__:
raise RegAllocException('Register allocation not done on function {}'.format(self.name))
return function.max_reg_usage
@property
def name(self):
if isinstance(self.target_function, str):
return self.target_function
return self.target_function.value
def resolve_function_name(self):
if isinstance(self.target_function, str):
return self.target_function
return self.target_function.get_val()
def run(self, update_program_counter=True):
"""
Runs the call operation.
(Update program counter ignored -> always True)
"""
# run custom callback
self.custom_callback()
function_name = self.resolve_function_name()
logging.info('[{}] Calling function {}.'.format(self.instruction_type, function_name))
# instructions to be skipped (checkpoint, restore)
if function_name in self.ignore:
self._vmstate.register_file.pc.increment_pc()
return
args = []
input_lookups = []
# In a real scenario, args are saved onto the stack. In this case, instead, they are re-loaded before the
# function call and directly passed as values.
# The loading of each arguments results in a very similar number of operations as saving them into the stack
# but since the RegisterFile is stacked when the function call happens, I need to save their values and restore
# them back as virtual_registers or address_registers (in case of physical regfile) to be able to use them.
for arg in self.function_args:
arg_val = arg.get_val()
args.append(arg_val)
if self._vmstate.input_lookup_enabled:
if arg.value_class == 'virtual_reg':
input_lookup_data = self._vmstate.register_file.get_input_lookup(arg.value)
input_lookups.append(input_lookup_data)
else:
input_lookups.append(None)
# emulate stacking of arguments for stack's anomalies analysis
arg_count = len(self.function_args)
saved_params_number = min(arg_count - self._vmstate.register_file.param_regs_count, 0)
arg_count -= 1
# push parameters in reverse order. NB: some parameters are passed using registers
for i in range(arg_count, arg_count-saved_params_number, -1):
arg = self.function_args[i]
arg_len = len(arg.type)
arg_val = arg.get_val()
if self._vmstate.do_data_anomaly_check:
self._vmstate.function_call_lookup['args'].append(self._vmstate.memory.stack.top_address)
self._vmstate.memory.stack.push(arg_len, arg_val, 'function argument')
# callback
self._vmstate.on_function_call(function_name)
self._vmstate.global_clock += self.tick_count
f_args = self._vmstate.functions[function_name].arguments
var_arg = BaseType('var_args', '8')
for i in range(0, len(f_args)):
is_var_arg = f_args[i].base_type == var_arg
# if is a variable argument, from now insert elements inside a single list
if is_var_arg:
arg = []
for j in range(i, len(args)):
arg.append(args[j])
else:
arg = args[i]
# NB: here I use write_address, since for virtual regfile is like write()
# and for physical one is needed since the stack placement is emulated as a sequence of
# laod before their actual usage.
target = '%{}'.format(i)
self._vmstate.register_file.write_address(target, arg)
# set input lookup
if self._vmstate.input_lookup_enabled and not is_var_arg and input_lookups[i] is not None:
self._vmstate.register_file.set_address_input_lookup(target, input_lookups[i])
# if is var_arg, all arguments have been passed, so stop
if is_var_arg:
break
def get_type_from_virtual_reg(self, virtual_reg):
"""
Returns the type of an argument given its virtual register name.
"""
for arg in self.function_args:
# if the virtual reg is in the uses of the argument, the
# given type is the one of the argument.
if virtual_reg in arg.get_uses():
# returns a copy so to not change the original element
return copy.deepcopy(arg.type)
return None
def get_uses(self):
"""
Returns a list containing the names of the registers used by this instruction.
(used by register allocation)
"""
uses = []
for arg in self.function_args:
uses = uses + arg.get_uses()
return uses
def replace_reg_name(self, old_reg_name, new_reg_name):
"""
Replaces the name of a register used by the instruction with a new one.
(used by register allocation)
"""
for arg in self.function_args:
arg.replace_reg_name(old_reg_name, new_reg_name)
if self.target is not None:
self.target.replace_reg_name(old_reg_name, new_reg_name)
class VaArgOperation(Instruction):
"""
AST node of the LLVM Other Instructions group - VaArg Instruction
NOT SUPPORTED IN ScEpTIC
https://llvm.org/docs/LangRef.html#otherops
"""
def __init__(self, target, element, arg_type):
super().__init__()
self.target = target
self.element = element
self.type = arg_type
def __str__(self):
retstr = super().__str__()
retstr += 'va_arg {} {}'.format(self.condition, self.element, self.type)
return retstr
def run(self, update_program_counter=True):
raise RuntimeException('va_arg llvm instruction not supported for now.')