589 lines
19 KiB
Python
589 lines
19 KiB
Python
import logging
|
|
|
|
from ScEpTIC import tools
|
|
from ScEpTIC.AST.elements.instruction import Instruction
|
|
from ScEpTIC.AST.misc.virtual_memory_enum import VirtualMemoryEnum
|
|
|
|
|
|
class AllocaOperation(Instruction):
|
|
"""
|
|
AST node of the LLVM Memory Instructions group - Alloca Instruction
|
|
https://llvm.org/docs/LangRef.html#memoryops
|
|
"""
|
|
|
|
def __init__(self, target, element_type, elements_number, align):
|
|
super().__init__()
|
|
|
|
self.target = target
|
|
self.type = element_type
|
|
self.elements_number = int(elements_number)
|
|
self.align = int(align)
|
|
self.is_first = False
|
|
|
|
|
|
def __str__(self):
|
|
retstr = super().__str__()
|
|
retstr += 'alloca {} x {}'.format(self.type, self.elements_number)
|
|
return retstr
|
|
|
|
def run(self, update_program_counter=True):
|
|
"""
|
|
Executes the operation and the target assignment.
|
|
(Update program counter ignored -> always True)
|
|
"""
|
|
|
|
target = self.target.value
|
|
dimension = len(self.type) * self.elements_number
|
|
|
|
address = self._vmstate.memory.stack.allocate(dimension, True, self.metadata, self.align)
|
|
|
|
# use special write_address (datalayout omitted, value can't be stored in a physical register
|
|
# since it is resolved and directly-placed by the compiler's backend)
|
|
self._vmstate.register_file.write_address(target, address)
|
|
|
|
# call run's callback
|
|
self._vmstate.on_run(self.tick_count)
|
|
|
|
logging.info('[{}] Allocating {} bits in stack at address {} ({}).'.format(self.instruction_type, dimension, address, target))
|
|
|
|
|
|
def get_uses(self):
|
|
"""
|
|
Returns a list containing the names of the registers used by this instruction.
|
|
(used by register allocation)
|
|
"""
|
|
|
|
# no register used
|
|
return []
|
|
|
|
|
|
# TODO: check if this is necessary (before was returning an empty set)
|
|
#def get_defs(self):
|
|
# """
|
|
# Returns a list of registers defined by this instruction.
|
|
# (used by register allocation)
|
|
# """
|
|
#
|
|
# # no register defined
|
|
# return []
|
|
|
|
|
|
def get_ignore(self):
|
|
"""
|
|
Returns a list of register names to be ignored by register allocation.
|
|
For alloca operation it is the target register.
|
|
"""
|
|
|
|
return self.target.get_uses()
|
|
|
|
|
|
def get_input_lookup(self):
|
|
"""
|
|
Returns the input lookup data for the current operation
|
|
"""
|
|
|
|
return tools.build_input_lookup_data(None, None)
|
|
|
|
|
|
def resolve_memory_tag(self, elements):
|
|
"""
|
|
Resolves and returns the memory tag of the targeted element
|
|
"""
|
|
if self.memory_tag is None:
|
|
|
|
if self.metadata is None:
|
|
if self.is_first:
|
|
self.memory_tag = "first_alloca"
|
|
else:
|
|
raise Exception(f"No metadata available for {self}")
|
|
|
|
else:
|
|
metadata = self.metadata.retrieve()
|
|
self.memory_tag = metadata['variable_name']
|
|
|
|
return self.memory_tag
|
|
|
|
|
|
def resolve_memory_tag_dependency(self, elements):
|
|
if self.memory_tag_dependency is None:
|
|
self.memory_tag_dependency = self.resolve_memory_tag(elements)
|
|
|
|
return self.memory_tag_dependency
|
|
|
|
|
|
def resolve_memory_address_chain(self, elements):
|
|
"""
|
|
Returns a list of all the instructions required to get the address of the targeted element(s)
|
|
"""
|
|
return [self]
|
|
|
|
|
|
class LoadOperation(Instruction):
|
|
"""
|
|
AST node of the LLVM Memory Instructions group - Load Instruction
|
|
https://llvm.org/docs/LangRef.html#memoryops
|
|
"""
|
|
|
|
def __init__(self, target, load_type, element, align, volatile):
|
|
super().__init__()
|
|
|
|
self.target = target
|
|
self.type = load_type
|
|
self.element = element
|
|
self.align = int(align)
|
|
self.is_volatile = volatile
|
|
|
|
self.virtual_memory_target = VirtualMemoryEnum.VOLATILE
|
|
self.virtual_memory_normalized = False
|
|
self.has_virtual_memory_copy = False
|
|
self.virtual_memory_copy = None
|
|
self.is_virtual_memory_copy = False
|
|
|
|
# In llvmir the arguments of a function are not stored in stack, but passed as virtual registers
|
|
# or immediate values to the call().
|
|
# To emulate the storing of the values onto the stack, save those values as address registers.
|
|
# The register allocation step needs only to set self.is_arg_of_function_call to True, without touching
|
|
# the target virtual register.
|
|
# The arguments are loaded then onto the stack when the function needs them (the store is generated in the llvm ir already)
|
|
self.is_arg_of_function_call = False
|
|
|
|
|
|
def __str__(self):
|
|
retstr = super().__str__()
|
|
|
|
s_type = str(self.type)
|
|
|
|
if self.element.type is not None:
|
|
s_type = ''
|
|
|
|
retstr += 'load {} {}{}'.format(s_type, self.element, ' [arg]' if self.is_arg_of_function_call else '')
|
|
|
|
retstr += f" (${self.virtual_memory_target.value})"
|
|
if self.virtual_memory_normalized:
|
|
retstr += " {NORMALIZED}"
|
|
|
|
if self.is_virtual_memory_copy:
|
|
retstr += " {COPY}"
|
|
|
|
return retstr
|
|
|
|
def run(self, update_program_counter=True):
|
|
"""
|
|
Executes the load operation and the target assignment.
|
|
(Update program counter ignored -> always True)
|
|
"""
|
|
|
|
value = self.get_val()
|
|
|
|
# explained in __init__ comment
|
|
if self.is_arg_of_function_call:
|
|
target = self.target.value
|
|
self._vmstate.register_file.write_address(target, value)
|
|
|
|
if self._vmstate.input_lookup_enabled:
|
|
input_lookup_data = self.get_input_lookup()
|
|
self._vmstate.register_file.set_address_input_lookup(target, input_lookup_data)
|
|
|
|
else:
|
|
self.save_in_target_register(value)
|
|
|
|
# call run's callback
|
|
self._vmstate.on_run(self.tick_count)
|
|
|
|
logging.info('[{}] Loading {} into {}.'.format(self.instruction_type, value, self.target.value))
|
|
|
|
|
|
def get_val(self):
|
|
"""
|
|
Executes the operation and the target assignment.
|
|
"""
|
|
|
|
address = self.element.get_val()
|
|
dimension = len(self.type)
|
|
|
|
# read from memory
|
|
value = self._vmstate.memory.read(address, dimension)
|
|
|
|
return value
|
|
|
|
|
|
def get_input_lookup(self):
|
|
"""
|
|
Returns the input lookup data for the current operation
|
|
"""
|
|
|
|
address = self.element.get_val()
|
|
|
|
return self._vmstate.memory.get_cell_input_lookup(address)
|
|
|
|
|
|
def get_load_address(self):
|
|
"""
|
|
Returns the address to be loaded.
|
|
"""
|
|
|
|
return self.element.get_val()
|
|
|
|
|
|
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.element.replace_reg_name(old_reg_name, new_reg_name)
|
|
self.target.replace_reg_name(old_reg_name, new_reg_name)
|
|
|
|
|
|
def get_uses(self):
|
|
"""
|
|
Returns a list containing the names of the registers used by this instruction.
|
|
(used by register allocation)
|
|
"""
|
|
|
|
return self.element.get_uses()
|
|
|
|
def get_ignore(self):
|
|
"""
|
|
Returns a list of register names to be ignored by register allocation.
|
|
For load operation it is the target register, if the load operation is marked to be used
|
|
as argument of a function call, since it will be loaded as a stack offset.
|
|
"""
|
|
|
|
# if loads an argument of a function call, it is an address register (stack location)
|
|
# so it must be ignored on register allocation.
|
|
if self.is_arg_of_function_call:
|
|
return self.target.get_uses()
|
|
|
|
return []
|
|
|
|
|
|
def resolve_memory_tag(self, elements):
|
|
"""
|
|
Resolves and returns the memory tag of the targeted element
|
|
"""
|
|
if self.memory_tag is None:
|
|
self.memory_tag = self.element.resolve_memory_tag(elements)
|
|
|
|
return self.memory_tag
|
|
|
|
def resolve_memory_tag_dependency(self, elements):
|
|
if self.memory_tag_dependency is None:
|
|
self.memory_tag_dependency = self.element.resolve_memory_tag_dependency(elements)
|
|
|
|
return self.memory_tag_dependency
|
|
|
|
def resolve_memory_address_chain(self, elements):
|
|
"""
|
|
Returns a list of all the instructions required to get the address of the targeted element(s)
|
|
"""
|
|
return [self, self.element.resolve_memory_address_chain(elements)]
|
|
|
|
def get_memory_address(self):
|
|
"""
|
|
:return: the address of the targeted memory cell
|
|
"""
|
|
return self.get_load_address()
|
|
|
|
|
|
class StoreOperation(Instruction):
|
|
"""
|
|
AST node of the LLVM Memory Instructions group - Store Instruction
|
|
https://llvm.org/docs/LangRef.html#memoryops
|
|
"""
|
|
|
|
def __init__(self, target, value, align, volatile):
|
|
super().__init__()
|
|
|
|
self.target = target
|
|
self.value = value
|
|
self.align = int(align)
|
|
self.is_volatile = volatile
|
|
|
|
self.virtual_memory_target = VirtualMemoryEnum.VOLATILE
|
|
self.virtual_memory_normalized = False
|
|
self.has_virtual_memory_copy = False
|
|
self.virtual_memory_copy = None
|
|
self.is_virtual_memory_copy = False
|
|
|
|
self._omit_target = True
|
|
|
|
|
|
def __str__(self):
|
|
retstr = super().__str__()
|
|
|
|
retstr += 'store {} in {}'.format(self.value, self.target)
|
|
|
|
retstr += f" (${self.virtual_memory_target.value})"
|
|
if self.virtual_memory_normalized:
|
|
retstr += " {NORMALIZED}"
|
|
|
|
if self.is_virtual_memory_copy:
|
|
retstr += " {COPY}"
|
|
|
|
return retstr
|
|
|
|
def run(self, update_program_counter=True):
|
|
"""
|
|
Executes the operation and the target assignment.
|
|
(Update program counter ignored -> always True)
|
|
"""
|
|
|
|
address = self.target.get_val()
|
|
dimension = len(self.value.type)
|
|
content = self.value.get_val()
|
|
|
|
# write into memory if it is not a dummy write
|
|
if not self.is_part_of_dummy_write:
|
|
self._vmstate.memory.write(address, dimension, content)
|
|
|
|
if self._vmstate.input_lookup_enabled:
|
|
input_lookup_data = self.value.get_input_lookup()
|
|
self._vmstate.memory.set_cell_input_lookup(address, input_lookup_data)
|
|
|
|
# call run's callback
|
|
self._vmstate.on_run(self.tick_count)
|
|
|
|
logging.info('[{}] Saving {} into {}.'.format(self.instruction_type, content, self.target.value))
|
|
|
|
|
|
def get_uses(self):
|
|
"""
|
|
Returns a list containing the names of the registers used by this instruction.
|
|
(used by register allocation)
|
|
"""
|
|
|
|
return self.value.get_uses() + self.target.get_uses()
|
|
|
|
|
|
def get_defs(self):
|
|
"""
|
|
Returns a list of registers defined by this instruction.
|
|
(used by register allocation)
|
|
"""
|
|
|
|
return []
|
|
|
|
|
|
def get_store_address(self):
|
|
"""
|
|
Returns the address in which the value will be stored.
|
|
"""
|
|
|
|
return self.target.get_val()
|
|
|
|
|
|
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.value.replace_reg_name(old_reg_name, new_reg_name)
|
|
self.target.replace_reg_name(old_reg_name, new_reg_name)
|
|
|
|
|
|
def resolve_memory_tag(self, elements):
|
|
"""
|
|
Resolves and returns the memory tag of the targeted element
|
|
"""
|
|
if self.memory_tag is None:
|
|
self.memory_tag = self.target.resolve_memory_tag(elements)
|
|
|
|
return self.memory_tag
|
|
|
|
def resolve_memory_tag_dependency(self, elements):
|
|
if self.memory_tag_dependency is None:
|
|
self.memory_tag_dependency = self.target.resolve_memory_tag_dependency(elements)
|
|
|
|
return self.memory_tag_dependency
|
|
|
|
def resolve_memory_address_chain(self, elements):
|
|
"""
|
|
Returns a list of all the instructions required to get the address of the targeted element(s)
|
|
"""
|
|
return [self, self.target.resolve_memory_address_chain(elements)]
|
|
|
|
def get_memory_address(self):
|
|
"""
|
|
:return: the target address of the store
|
|
"""
|
|
return self.get_store_address()
|
|
|
|
|
|
class GetElementPointerOperation(Instruction):
|
|
"""
|
|
AST node of the LLVM Memory Instructions group - GetElementPointer Instruction
|
|
https://llvm.org/docs/LangRef.html#memoryops
|
|
"""
|
|
|
|
def __init__(self, target, element, base_type, indexes, inbounds):
|
|
super().__init__()
|
|
|
|
self.target = target
|
|
self.element = element
|
|
self.type = base_type
|
|
# indexes is a list of Values with an additional inrage attribute (either True or False)
|
|
self.indexes = indexes
|
|
self.inbounds = inbounds
|
|
|
|
|
|
def __str__(self):
|
|
retstr = super().__str__()
|
|
|
|
s_indexes = ''
|
|
|
|
for i in self.indexes:
|
|
s_indexes = '{}[{}]'.format(s_indexes, i)
|
|
|
|
retstr += 'getelementpointer {} {}'.format(self.element, s_indexes)
|
|
return retstr
|
|
|
|
|
|
def get_val(self):
|
|
"""
|
|
Returns the represented absolute address.
|
|
"""
|
|
|
|
# get relative address to perform computation
|
|
address = self.element.get_val()
|
|
|
|
if address in self._vmstate.functions:
|
|
raise NotImplementedError("Direct manipulation of function pointers not supported!")
|
|
|
|
prefix, base_address = self._vmstate.memory._parse_absolute_address(address)
|
|
|
|
# first index is the offset from the base_address (dimension of spacing = overall size of one element of composition self.type)
|
|
# (is like "pointer" spacing)
|
|
offset = len(self.type) * self.indexes[0].get_val()
|
|
|
|
# C-like indexes starts from the second element of self.indexes
|
|
indexes = self.indexes[1:]
|
|
composition = self.type.get_memory_composition()
|
|
#print(f"Index: {indexes}; Composition: {composition}; Initial offset = {offset}; First index: {self.indexes[0]}")
|
|
|
|
# each index is an instance of Value
|
|
for index in indexes:
|
|
index = index.get_val()
|
|
|
|
composition_elements = composition[0]
|
|
flat = []
|
|
|
|
# Array index (composition_elements = number of elements in the array)
|
|
# E.g. [3, 32] or [3, [3, 32]]
|
|
if isinstance(composition_elements, int):
|
|
self.type.flat_composition(composition[1], flat)
|
|
offset += index * sum(flat)
|
|
composition = composition[1]
|
|
|
|
# Struct selector (composition is the entire struct data)
|
|
# E.g. [[1, 32], [1, 16], [1, 18]]
|
|
elif isinstance(composition_elements, list):
|
|
prev_elements = composition[:index]
|
|
self.type.flat_composition(prev_elements, flat)
|
|
offset += sum(flat)
|
|
# next element -> sub-struct element
|
|
composition = composition[index]
|
|
|
|
else:
|
|
raise NotImplementedError("GetElementPointerOperation() error")
|
|
|
|
# offset is calculated in bits, address is in bytes
|
|
if offset % 8 != 0:
|
|
raise ValueError('Offset {} is not a multiple of a byte.'.format(offset))
|
|
|
|
#print("-> Offset: {}\n".format(offset))
|
|
offset = offset // 8 # int division
|
|
|
|
value = '{}{}'.format(prefix, hex(base_address + offset))
|
|
|
|
logging.info('[{}] Address resolved to {}.'.format(self.instruction_type, value))
|
|
|
|
return value
|
|
|
|
|
|
def get_uses(self):
|
|
"""
|
|
Returns a list containing the names of the registers used by this instruction.
|
|
(used by register allocation)
|
|
"""
|
|
|
|
uses = self.element.get_uses()
|
|
for index in self.indexes:
|
|
uses = uses + index.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)
|
|
"""
|
|
|
|
self.element.replace_reg_name(old_reg_name, new_reg_name)
|
|
|
|
for index in self.indexes:
|
|
index.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)
|
|
|
|
|
|
def get_input_lookup(self):
|
|
"""
|
|
Returns the input lookup data for the current operation
|
|
"""
|
|
|
|
# element is an address (or a reference to it), so does not have any input lookup info.
|
|
# indexes may have input lookup infos
|
|
lookup = tools.build_input_lookup_data(None, None)
|
|
|
|
for index in self.indexes:
|
|
lookup = tools.merge_input_lookup_data(lookup, index.get_input_lookup())
|
|
|
|
return lookup
|
|
|
|
|
|
def resolve_memory_tag(self, elements):
|
|
"""
|
|
Resolves and returns the memory tag of the targeted element
|
|
"""
|
|
if self.memory_tag is None:
|
|
memory_tag = str(self.element.resolve_memory_tag(elements))
|
|
|
|
for index in self.indexes:
|
|
memory_tag += f"[{index.resolve_memory_tag(elements)}]"
|
|
|
|
self.memory_tag = memory_tag
|
|
|
|
return self.memory_tag
|
|
|
|
def resolve_memory_tag_dependency(self, elements):
|
|
if self.memory_tag_dependency is None:
|
|
dep = [None]
|
|
|
|
for index in self.indexes:
|
|
dep.append(index.resolve_memory_tag(elements))
|
|
dep.append(index.resolve_memory_tag_dependency(elements))
|
|
|
|
self.memory_tag_dependency = dep
|
|
|
|
return self.memory_tag_dependency
|
|
|
|
def resolve_memory_address_chain(self, elements):
|
|
"""
|
|
Returns a list of all the instructions required to get the address of the targeted element(s)
|
|
"""
|
|
chain = []
|
|
|
|
if self.target is not None:
|
|
chain.append(self)
|
|
|
|
chain.append(self.element.resolve_memory_address_chain(elements))
|
|
|
|
for index in self.indexes:
|
|
chain.append(index.resolve_memory_address_chain(elements))
|
|
|
|
return chain
|