First commit
This commit is contained in:
commit
ace3695e48
BIN
ScEpTIC/.DS_Store
vendored
Normal file
BIN
ScEpTIC/.DS_Store
vendored
Normal file
Binary file not shown.
0
ScEpTIC/AST/__init__.py
Normal file
0
ScEpTIC/AST/__init__.py
Normal file
BIN
ScEpTIC/AST/__pycache__/__init__.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/__pycache__/__init__.pypy310.pyc
Normal file
Binary file not shown.
18
ScEpTIC/AST/builtins/__init__.py
Normal file
18
ScEpTIC/AST/builtins/__init__.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from ScEpTIC.AST.builtins.builtin import Builtin
|
||||||
|
|
||||||
|
def load_libraries():
|
||||||
|
"""
|
||||||
|
Loads the libraries of builtins
|
||||||
|
"""
|
||||||
|
import ScEpTIC.AST.builtins.libs.math
|
||||||
|
import ScEpTIC.AST.builtins.libs.mem
|
||||||
|
import ScEpTIC.AST.builtins.libs.std
|
||||||
|
import ScEpTIC.AST.builtins.libs.llvm
|
||||||
|
import ScEpTIC.AST.builtins.libs.sceptic
|
||||||
|
|
||||||
|
|
||||||
|
def link_builtins():
|
||||||
|
"""
|
||||||
|
Links the builtins (both library-defined and user-defined)
|
||||||
|
"""
|
||||||
|
Builtin.link()
|
||||||
BIN
ScEpTIC/AST/builtins/__pycache__/__init__.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/builtins/__pycache__/__init__.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/builtins/__pycache__/builtin.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/builtins/__pycache__/builtin.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/builtins/__pycache__/linker.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/builtins/__pycache__/linker.pypy310.pyc
Normal file
Binary file not shown.
129
ScEpTIC/AST/builtins/builtin.py
Normal file
129
ScEpTIC/AST/builtins/builtin.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from ScEpTIC.AST.builtins.linker import BuiltinLinker
|
||||||
|
from ScEpTIC.AST.elements.instruction import Instruction
|
||||||
|
|
||||||
|
|
||||||
|
class Builtin(Instruction):
|
||||||
|
"""
|
||||||
|
Base object to be extended for builtin functions.
|
||||||
|
|
||||||
|
HOW TO:
|
||||||
|
1) create the new instruction to be executed
|
||||||
|
2) Implement the get_val() method, returning the result of the instruction.
|
||||||
|
Arguments can be obatined using the list self.args (index = index of arg), their
|
||||||
|
values will be automatically resolved on runtime.
|
||||||
|
3) Call the class method define_builtin on the class
|
||||||
|
4) All the builtins will be loaded by calling link()
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
class LenTestInstruction(Builtin):
|
||||||
|
def get_val(self):
|
||||||
|
return len(self.args)
|
||||||
|
|
||||||
|
LenTestInstruction.define_builtin('let_test_instruction', 'i32, i32, double, float, i8', 'i64')
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this value is used to increment the global clock.
|
||||||
|
# Set this in the Builtin implementations equal to the number
|
||||||
|
# of corresponding assemly instruction of the builtin.
|
||||||
|
tick_count = 1
|
||||||
|
builtins = []
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def link():
|
||||||
|
"""
|
||||||
|
Links the builtin Instructions creating a function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for builtin in Builtin.builtins:
|
||||||
|
logging.debug('[Builtin] Linking {}'.format(builtin['name']))
|
||||||
|
|
||||||
|
# Resolve lambdas
|
||||||
|
for arg in builtin.keys():
|
||||||
|
if arg != "instruction" and callable(builtin[arg]):
|
||||||
|
builtin[arg] = builtin[arg]()
|
||||||
|
|
||||||
|
BuiltinLinker(builtin['name'], builtin['arguments'], builtin['return_type'], builtin['instruction'])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def define_builtin(cls, name, arguments, return_type):
|
||||||
|
"""
|
||||||
|
Define builtins, which will be loaded by the link() function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
element = {'instruction': cls, 'name': name, 'arguments': arguments, 'return_type': return_type}
|
||||||
|
cls.builtins.append(element)
|
||||||
|
|
||||||
|
def set_args(self, args):
|
||||||
|
"""
|
||||||
|
Sets the arguments of the builtin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._args = args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def args(self):
|
||||||
|
"""
|
||||||
|
Returns the resolved value of the arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = []
|
||||||
|
|
||||||
|
for arg in self._args:
|
||||||
|
arg = arg.get_val()
|
||||||
|
args.append(arg)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
"""
|
||||||
|
NB: must be implemented by each builtin!
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError('get_val() method for builtins is mandatory to be implemented!')
|
||||||
|
|
||||||
|
def get_uses(self):
|
||||||
|
"""
|
||||||
|
Returns the registers used by this instruction as a list of strings.
|
||||||
|
(used by register allocation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
uses = []
|
||||||
|
|
||||||
|
for arg in self._args:
|
||||||
|
# support for var_args
|
||||||
|
if isinstance(arg, list):
|
||||||
|
for arg_i in arg:
|
||||||
|
uses = uses + arg_i.get_uses()
|
||||||
|
else:
|
||||||
|
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._args:
|
||||||
|
# support for var_args
|
||||||
|
if isinstance(arg, list):
|
||||||
|
for arg_i in arg:
|
||||||
|
arg_i.replace_reg_name(old_reg_name, new_reg_name)
|
||||||
|
else:
|
||||||
|
arg.replace_reg_name(old_reg_name, new_reg_name)
|
||||||
|
|
||||||
|
if 'target' in self.__dict__ and self.target is not None:
|
||||||
|
self.target.replace_reg_name(old_reg_name, new_reg_name)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = super().__str__()
|
||||||
|
retstr += '{}({})'.format(self.__class__.__name__, self._args)
|
||||||
|
return retstr
|
||||||
0
ScEpTIC/AST/builtins/libs/__init__.py
Normal file
0
ScEpTIC/AST/builtins/libs/__init__.py
Normal file
BIN
ScEpTIC/AST/builtins/libs/__pycache__/__init__.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/builtins/libs/__pycache__/__init__.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/builtins/libs/__pycache__/llvm.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/builtins/libs/__pycache__/llvm.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/builtins/libs/__pycache__/math.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/builtins/libs/__pycache__/math.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/builtins/libs/__pycache__/mem.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/builtins/libs/__pycache__/mem.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/builtins/libs/__pycache__/sceptic.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/builtins/libs/__pycache__/sceptic.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/builtins/libs/__pycache__/std.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/builtins/libs/__pycache__/std.pypy310.pyc
Normal file
Binary file not shown.
165
ScEpTIC/AST/builtins/libs/llvm.py
Normal file
165
ScEpTIC/AST/builtins/libs/llvm.py
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
from ScEpTIC.AST.builtins.builtin import Builtin
|
||||||
|
from ScEpTIC.AST.elements.value import Value
|
||||||
|
|
||||||
|
"""
|
||||||
|
LLVM library functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_unaligned_cells(cells, nbytes, stack_limit_address, stack_prefix):
|
||||||
|
"""
|
||||||
|
ScEpTIC align memory to single bytes.
|
||||||
|
To make llvm mem-based functions work, we need to count the number of cells instead of using the memory size.
|
||||||
|
Further, when allocating memory without initializing the cells with a value, ScEpTIC creates a single cell of the entire data structure size.
|
||||||
|
To ensure we do not smash the stack, this function limits the cells to EBP-2 cells.
|
||||||
|
This is possible as these Builtins are function calls and allocate a new stack frame.
|
||||||
|
:param cells: List of cells
|
||||||
|
:param nbytes: number of requested bytes
|
||||||
|
:param stack_limit_address: start address of the new frame (EBP - 2 cells, for saved PC and EBP)
|
||||||
|
:param stack_prefix: address prefix of the stack
|
||||||
|
"""
|
||||||
|
|
||||||
|
# LLVM uses cell's alignment to calculate nbytes
|
||||||
|
if not isinstance(cells, list) or len(cells) == 0:
|
||||||
|
return cells
|
||||||
|
|
||||||
|
alignment = cells[0].alignment
|
||||||
|
if alignment is not None and alignment > 0:
|
||||||
|
# Calculate real number of cells
|
||||||
|
n_cells = nbytes // alignment
|
||||||
|
# Resize cells using ScEpTIC no-alignment policy
|
||||||
|
cells = cells[:n_cells]
|
||||||
|
|
||||||
|
if len(cells) > 0 and cells[0].address_prefix == stack_prefix:
|
||||||
|
for idx, cell in enumerate(cells):
|
||||||
|
if cell.address >= stack_limit_address:
|
||||||
|
return cells[:idx]
|
||||||
|
|
||||||
|
return cells
|
||||||
|
|
||||||
|
class msp430_dma_word_copy(Builtin):
|
||||||
|
"""
|
||||||
|
msp430 __dma_word_copy() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
args = self.args
|
||||||
|
destination = args[0]
|
||||||
|
source = args[1]
|
||||||
|
# compiled for x86 -> word size 32 bit -> 4 bytes
|
||||||
|
nbytes = args[2]
|
||||||
|
|
||||||
|
# Stack limit address
|
||||||
|
stack_limit_address = self._vmstate.register_file.ebp - 2 * (self._vmstate.memory.address_dimension // 8)
|
||||||
|
stack_prefix = self._vmstate.memory.stack.address_prefix
|
||||||
|
|
||||||
|
cells = self._vmstate.memory.get_cells_from_address(source, nbytes, False)
|
||||||
|
# No cells alignment to be considered
|
||||||
|
self._vmstate.memory.simulate_mmu_read_on_cells(cells)
|
||||||
|
|
||||||
|
self._vmstate.memory.set_cells_from_address(destination, cells)
|
||||||
|
|
||||||
|
class memcpy(Builtin):
|
||||||
|
"""
|
||||||
|
memcpy() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
args = self.args
|
||||||
|
destination = args[0]
|
||||||
|
source = args[1]
|
||||||
|
nbytes = args[2]
|
||||||
|
|
||||||
|
# Stack limit address
|
||||||
|
stack_limit_address = self._vmstate.register_file.ebp - 2 * (self._vmstate.memory.address_dimension // 8)
|
||||||
|
stack_prefix = self._vmstate.memory.stack.address_prefix
|
||||||
|
|
||||||
|
cells = self._vmstate.memory.get_cells_from_address(source, nbytes, False)
|
||||||
|
cells = get_unaligned_cells(cells, nbytes, stack_limit_address, stack_prefix)
|
||||||
|
self._vmstate.memory.simulate_mmu_read_on_cells(cells)
|
||||||
|
|
||||||
|
self._vmstate.memory.set_cells_from_address(destination, cells)
|
||||||
|
|
||||||
|
|
||||||
|
class memmove(Builtin):
|
||||||
|
"""
|
||||||
|
memmove() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
args = self.args
|
||||||
|
destination = args[0]
|
||||||
|
source = args[1]
|
||||||
|
nbytes = args[2]
|
||||||
|
|
||||||
|
# Stack limit address
|
||||||
|
stack_limit_address = self._vmstate.register_file.ebp - 2 * (self._vmstate.memory.address_dimension // 8)
|
||||||
|
stack_prefix = self._vmstate.memory.stack.address_prefix
|
||||||
|
|
||||||
|
cells = self._vmstate.memory.get_cells_from_address(source, nbytes, False)
|
||||||
|
cells = get_unaligned_cells(cells, nbytes, stack_limit_address, stack_prefix)
|
||||||
|
self._vmstate.memory.simulate_mmu_read_on_cells(cells)
|
||||||
|
self._vmstate.memory.set_cells_from_address(destination, cells)
|
||||||
|
|
||||||
|
cells = self._vmstate.memory.get_cells_from_address(destination, nbytes, False)
|
||||||
|
cells = get_unaligned_cells(cells, nbytes, stack_limit_address, stack_prefix)
|
||||||
|
self._vmstate.memory.simulate_mmu_read_on_cells(cells)
|
||||||
|
for cell in cells:
|
||||||
|
self._vmstate.memory.write(cell.absolute_address, cell.dimension, None, False)
|
||||||
|
|
||||||
|
|
||||||
|
class memset(Builtin):
|
||||||
|
"""
|
||||||
|
memset() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
args = self.args
|
||||||
|
destination = args[0]
|
||||||
|
nbytes = args[2]
|
||||||
|
value = args[1]
|
||||||
|
|
||||||
|
# Stack limit address
|
||||||
|
stack_limit_address = self._vmstate.register_file.ebp - 2 * (self._vmstate.memory.address_dimension // 8)
|
||||||
|
stack_prefix = self._vmstate.memory.stack.address_prefix
|
||||||
|
|
||||||
|
cells = self._vmstate.memory.get_cells_from_address(destination, nbytes, False)
|
||||||
|
# Keep this here to ensure data structures works
|
||||||
|
# TODO: change alignment to the one of the operation, and not the one of the cell!!
|
||||||
|
cells = get_unaligned_cells(cells, nbytes, stack_limit_address, stack_prefix)
|
||||||
|
self._vmstate.memory.simulate_mmu_read_on_cells(cells)
|
||||||
|
for cell in cells:
|
||||||
|
self._vmstate.memory.write(cell.absolute_address, cell.dimension, value, False)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Definitions for the various versions of llvm.memcpy, llvm.memmove, llvm.memset
|
||||||
|
"""
|
||||||
|
# Called on linking, as it depends on vmstate
|
||||||
|
def address_dimension_16():
|
||||||
|
dim = f"i{Builtin._vmstate.memory.address_dimension}"
|
||||||
|
return f"{dim}, {dim}, i16"
|
||||||
|
|
||||||
|
def address_dimension_32():
|
||||||
|
dim = f"i{Builtin._vmstate.memory.address_dimension}"
|
||||||
|
return f"{dim}, {dim}, i32"
|
||||||
|
|
||||||
|
def address_dimension_64():
|
||||||
|
dim = f"i{Builtin._vmstate.memory.address_dimension}"
|
||||||
|
return f"{dim}, {dim}, i64"
|
||||||
|
|
||||||
|
# in llvm 5, source destination len align isvolatile, in llvm7 align is missing.
|
||||||
|
# To support every version, it is defined only as if it has 3 arguments.
|
||||||
|
memcpy.define_builtin('llvm.memcpy.p0i8.p0i8.i16', address_dimension_16, 'void')
|
||||||
|
memcpy.define_builtin('llvm.memcpy.p0i8.p0i8.i32', address_dimension_32, 'void')
|
||||||
|
memcpy.define_builtin('llvm.memcpy.p0i8.p0i8.i64', address_dimension_64, 'void')
|
||||||
|
|
||||||
|
memmove.define_builtin('llvm.memmove.p0i8.p0i8.i16', address_dimension_16, 'void')
|
||||||
|
memmove.define_builtin('llvm.memmove.p0i8.p0i8.i32', address_dimension_32, 'void')
|
||||||
|
memmove.define_builtin('llvm.memmove.p0i8.p0i8.i64', address_dimension_64, 'void')
|
||||||
|
|
||||||
|
memset.define_builtin('llvm.memset.p0i8.i16', address_dimension_16, 'void')
|
||||||
|
memset.define_builtin('llvm.memset.p0i8.i32', address_dimension_32, 'void')
|
||||||
|
memset.define_builtin('llvm.memset.p0i8.i64', address_dimension_64, 'void')
|
||||||
|
|
||||||
|
memcpy.define_builtin('__memcpy_chk', address_dimension_64, 'void')
|
||||||
|
msp430_dma_word_copy.define_builtin('__dma_word_copy', address_dimension_32, 'void')
|
||||||
|
|
||||||
246
ScEpTIC/AST/builtins/libs/math.py
Normal file
246
ScEpTIC/AST/builtins/libs/math.py
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
from ScEpTIC.AST.builtins.builtin import Builtin
|
||||||
|
|
||||||
|
"""
|
||||||
|
math.h most used library functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
class ceil(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the ceil() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.ceil(self.args[0])
|
||||||
|
|
||||||
|
class floor(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the floor() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.floor(self.args[0])
|
||||||
|
|
||||||
|
class fabs(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the fabs() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.fabs(self.args[0])
|
||||||
|
|
||||||
|
class abs(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the abs() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.abs(self.args[0])
|
||||||
|
|
||||||
|
class fmod(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the fmod() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.fmod(self.args[0], self.args[1])
|
||||||
|
|
||||||
|
class sqrt(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the sqrt() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.sqrt(self.args[0])
|
||||||
|
|
||||||
|
class pow(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the pow() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.pow(self.args[0], self.args[1])
|
||||||
|
|
||||||
|
class exp(Builtin):
|
||||||
|
"""
|
||||||
|
definition of the exp() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.exp(self.args[0])
|
||||||
|
|
||||||
|
class log(Builtin):
|
||||||
|
"""
|
||||||
|
definition of the log() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.log(self.args[0])
|
||||||
|
|
||||||
|
class log10(Builtin):
|
||||||
|
"""
|
||||||
|
definition of the log10() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.log10(self.args[0])
|
||||||
|
|
||||||
|
class acos(Builtin):
|
||||||
|
"""
|
||||||
|
definition of the acos() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.acos(self.args[0])
|
||||||
|
|
||||||
|
class asin(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the asin() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.asin(self.args[0])
|
||||||
|
|
||||||
|
class atan(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the atan() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.atan(self.args[0])
|
||||||
|
|
||||||
|
class atan2(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the atan2() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.atan2(self.args[0], self.args[1])
|
||||||
|
|
||||||
|
class cos(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the cos() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.cos(self.args[0])
|
||||||
|
|
||||||
|
class cosh(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the cosh() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.cosh(self.args[0])
|
||||||
|
|
||||||
|
class sin(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the sin() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.sin(self.args[0])
|
||||||
|
|
||||||
|
class sinh(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the sinh() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.sinh(self.args[0])
|
||||||
|
|
||||||
|
class tan(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the tan() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.tan(self.args[0])
|
||||||
|
|
||||||
|
class tanh(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the tanh() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return math.tanh(self.args[0])
|
||||||
|
|
||||||
|
class math_max(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the MAX() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return max(self.args[0], self.args[1])
|
||||||
|
|
||||||
|
class math_min(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the MIN() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return min(self.args[0], self.args[1])
|
||||||
|
|
||||||
|
ceil.define_builtin('ceil', 'double', 'double')
|
||||||
|
ceil.define_builtin('llvm.ceil.f64', 'double', 'double')
|
||||||
|
|
||||||
|
floor.define_builtin('floor', 'double', 'double')
|
||||||
|
floor.define_builtin('llvm.floor.f64', 'double', 'double')
|
||||||
|
|
||||||
|
fabs.define_builtin('fabs', 'double', 'double')
|
||||||
|
fabs.define_builtin('llvm.fabs.f64', 'double', 'double')
|
||||||
|
|
||||||
|
abs.define_builtin('abs', 'double', 'double')
|
||||||
|
abs.define_builtin('llvm.abs.f64', 'double', 'double')
|
||||||
|
|
||||||
|
fmod.define_builtin('fmod', 'double, double', 'double')
|
||||||
|
fmod.define_builtin('llvm.fmod.f64', 'double, double', 'double')
|
||||||
|
|
||||||
|
sqrt.define_builtin('sqrt', 'double', 'double')
|
||||||
|
sqrt.define_builtin('llvm.sqrt.f64', 'double', 'double')
|
||||||
|
|
||||||
|
pow.define_builtin('pow', 'double, double', 'double')
|
||||||
|
pow.define_builtin('llvm.pow.f64', 'double, double', 'double')
|
||||||
|
|
||||||
|
exp.define_builtin('exp', 'double', 'double')
|
||||||
|
exp.define_builtin('llvm.exp.f64', 'double', 'double')
|
||||||
|
|
||||||
|
log.define_builtin('log', 'double', 'double')
|
||||||
|
log.define_builtin('llvm.log.f64', 'double', 'double')
|
||||||
|
|
||||||
|
log10.define_builtin('log10', 'double', 'double')
|
||||||
|
log10.define_builtin('llvm.log10.f64', 'double', 'double')
|
||||||
|
|
||||||
|
acos.define_builtin('acos', 'double', 'double')
|
||||||
|
acos.define_builtin('llvm.acos.f64', 'double', 'double')
|
||||||
|
|
||||||
|
asin.define_builtin('asin', 'double', 'double')
|
||||||
|
asin.define_builtin('llvm.asin.f64', 'double', 'double')
|
||||||
|
|
||||||
|
atan.define_builtin('atan', 'double', 'double')
|
||||||
|
atan.define_builtin('llvm.atan.f64', 'double', 'double')
|
||||||
|
|
||||||
|
atan2.define_builtin('atan2', 'double, double', 'double')
|
||||||
|
atan2.define_builtin('llvm.atan2.f64', 'double, double', 'double')
|
||||||
|
|
||||||
|
cos.define_builtin('cos', 'double', 'double')
|
||||||
|
cos.define_builtin('llvm.cos.f64', 'double', 'double')
|
||||||
|
|
||||||
|
cosh.define_builtin('cosh', 'double', 'double')
|
||||||
|
cosh.define_builtin('llvm.cosh.f64', 'double', 'double')
|
||||||
|
|
||||||
|
sin.define_builtin('sin', 'double', 'double')
|
||||||
|
sin.define_builtin('llvm.sin.f64', 'double', 'double')
|
||||||
|
|
||||||
|
sinh.define_builtin('sinh', 'double', 'double')
|
||||||
|
sinh.define_builtin('llvm.sinh.f64', 'double', 'double')
|
||||||
|
|
||||||
|
tan.define_builtin('tan', 'double', 'double')
|
||||||
|
tan.define_builtin('llvm.tan.f64', 'double', 'double')
|
||||||
|
|
||||||
|
tanh.define_builtin('tanh', 'double', 'double')
|
||||||
|
tanh.define_builtin('llvm.tanh.f64', 'double', 'double')
|
||||||
|
|
||||||
|
math_max.define_builtin('MAX', 'double, double', 'double')
|
||||||
|
math_min.define_builtin('MAX', 'double, double', 'double')
|
||||||
72
ScEpTIC/AST/builtins/libs/mem.py
Normal file
72
ScEpTIC/AST/builtins/libs/mem.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
from ScEpTIC.AST.builtins.builtin import Builtin
|
||||||
|
|
||||||
|
"""
|
||||||
|
Heap-related library functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
class malloc(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the malloc() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
address = self._vmstate.memory.heap.allocate(self.args[0], False)
|
||||||
|
return address
|
||||||
|
|
||||||
|
|
||||||
|
class calloc(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the calloc() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
# two parameters: number of elements and element dimension
|
||||||
|
num = self.args[0]
|
||||||
|
dim = self.args[1]
|
||||||
|
dimension = num * dim
|
||||||
|
address = self._vmstate.memory.heap.allocate(dimension, False)
|
||||||
|
|
||||||
|
# initialize to 0
|
||||||
|
for i in range(0, num):
|
||||||
|
|
||||||
|
addr = self._vmstate.memory.add_offset(address, i*dim, False)
|
||||||
|
self._vmstate.memory.heap.write(addr, dim, 0, False)
|
||||||
|
|
||||||
|
return address
|
||||||
|
|
||||||
|
|
||||||
|
class realloc(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the realloc() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
address = self._vmstate.memory.heap.reallocate(self.args[0], self.args[1], False)
|
||||||
|
return address
|
||||||
|
|
||||||
|
|
||||||
|
class free(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the free() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
self._vmstate.memory.heap.deallocate(self.args[0])
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# Called on linking, as it depends on vmstate
|
||||||
|
def address_dimension():
|
||||||
|
return f"i{Builtin._vmstate.memory.address_dimension}"
|
||||||
|
|
||||||
|
# define malloc()
|
||||||
|
malloc.define_builtin('malloc', 'i32', address_dimension)
|
||||||
|
|
||||||
|
# define calloc()
|
||||||
|
calloc.define_builtin('calloc', 'i32, i32', address_dimension)
|
||||||
|
|
||||||
|
# define realloc()
|
||||||
|
realloc.define_builtin('realloc', '{}, i32'.format(address_dimension), address_dimension)
|
||||||
|
|
||||||
|
# define free()
|
||||||
|
free.define_builtin('free', 'i32', address_dimension)
|
||||||
6
ScEpTIC/AST/builtins/libs/sceptic.h
Normal file
6
ScEpTIC/AST/builtins/libs/sceptic.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
float sceptic_get_capacitor_voltage(void);
|
||||||
|
int sceptic_get_mcu_clock_cycles(void);
|
||||||
|
float sceptic_get_mcu_time(void);
|
||||||
|
void sceptic_set_mcu_power_on_voltage(float);
|
||||||
|
void sceptic_mcu_lpm(void);
|
||||||
|
float sceptic_timekeeper_get_time(void);
|
||||||
480
ScEpTIC/AST/builtins/libs/sceptic.py
Normal file
480
ScEpTIC/AST/builtins/libs/sceptic.py
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
from ScEpTIC.AST.builtins.builtin import Builtin
|
||||||
|
from ScEpTIC.AST.elements.value import Value
|
||||||
|
from ScEpTIC.emulator.energy.mcu import ADCPowerState
|
||||||
|
from ScEpTIC.emulator.energy.mcu.options import MCUClockCycleAction
|
||||||
|
from ScEpTIC.emulator.energy.options import OpModeName
|
||||||
|
|
||||||
|
class voc_read_data(Builtin):
|
||||||
|
"""
|
||||||
|
voc_read_data() builtin that reads voc from SGP40 sensor
|
||||||
|
"""
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
sgp40 = self._vmstate.config.analysis.energy.system_model.get_component('SGP40')
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
if sgp40 is None:
|
||||||
|
raise Exception("SHT85 not attached to ScEpTIC")
|
||||||
|
|
||||||
|
# read
|
||||||
|
self._vmstate.global_clock += sgp40.simulate_read()
|
||||||
|
|
||||||
|
|
||||||
|
class temperature_read_data(Builtin):
|
||||||
|
"""
|
||||||
|
temperature_read_data() builtin that reads temperature from SHT85 sensor
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
sht85 = self._vmstate.config.analysis.energy.system_model.get_component('SHT85')
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
if sht85 is None:
|
||||||
|
raise Exception("SHT85 not attached to ScEpTIC")
|
||||||
|
|
||||||
|
# read
|
||||||
|
self._vmstate.global_clock += sht85.simulate_read()
|
||||||
|
|
||||||
|
|
||||||
|
class camera_read_data(Builtin):
|
||||||
|
"""
|
||||||
|
camera_read_data() builtin that reads a camera
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
camera = self._vmstate.config.analysis.energy.system_model.get_component('0V7620')
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
if camera is None:
|
||||||
|
raise Exception("Camera not attached to ScEpTIC")
|
||||||
|
|
||||||
|
# word size
|
||||||
|
self._vmstate.global_clock += camera.simulate_read(int(self.args[0]))
|
||||||
|
|
||||||
|
|
||||||
|
class generic_adc_sensor_read(Builtin):
|
||||||
|
"""
|
||||||
|
generic_adc_sensor_read() builtin that reads a generic ADC sensor
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
generic_sensor = self._vmstate.config.analysis.energy.system_model.get_component('generic_sensor')
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
if generic_sensor is None:
|
||||||
|
raise Exception("GenericADCSensor not attached to ScEpTIC")
|
||||||
|
|
||||||
|
self._vmstate.global_clock += generic_sensor.simulate_read(int(self.args[0]))
|
||||||
|
|
||||||
|
class cc1101_set_state(Builtin):
|
||||||
|
"""
|
||||||
|
cc1101_set_state() builtin that sets the state of cc1101
|
||||||
|
"""
|
||||||
|
|
||||||
|
states = {0: 'off', 1: 'inactive', 2: 'active'}
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
cc1101 = self._vmstate.config.analysis.energy.system_model.get_component('cc1101')
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
if cc1101 is None:
|
||||||
|
raise Exception("CC1101 not attached to ScEpTIC")
|
||||||
|
|
||||||
|
self._vmstate.global_clock += cc1101.simulate_set_state(self.states[int(self.args[0])])
|
||||||
|
|
||||||
|
|
||||||
|
class cc1101_transmit(Builtin):
|
||||||
|
"""
|
||||||
|
cc1101_transmit() builtin that turns on the cc1101, transmit data, and turns it off
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
cc1101 = self._vmstate.config.analysis.energy.system_model.get_component('cc1101')
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
if cc1101 is None:
|
||||||
|
raise Exception("CC1101 not attached to ScEpTIC")
|
||||||
|
|
||||||
|
# args: bytes, word_size
|
||||||
|
self._vmstate.global_clock += cc1101.simulate_transmit(int(self.args[0]), int(self.args[1]))
|
||||||
|
|
||||||
|
class adc_off(Builtin):
|
||||||
|
"""
|
||||||
|
adc_off() builtin that turns off the ADCon
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
system_model = self._vmstate.config.analysis.energy.system_model
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
# Turn off ADC
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, OpModeName.SIMULATE_ADC)
|
||||||
|
system_model.mcu.set_adc_state(ADCPowerState.OFF)
|
||||||
|
|
||||||
|
class adc_on(Builtin):
|
||||||
|
"""
|
||||||
|
adc_on() builtin that turns on the ADC
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
system_model = self._vmstate.config.analysis.energy.system_model
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
# Turn on ADC
|
||||||
|
# - Run instructions
|
||||||
|
# - ADCPowerState -> ON
|
||||||
|
for _ in range(system_model.mcu.adc_instructions['on']):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, OpModeName.SIMULATE_ADC)
|
||||||
|
|
||||||
|
system_model.mcu.set_adc_state(ADCPowerState.ON)
|
||||||
|
|
||||||
|
class adc_read(Builtin):
|
||||||
|
"""
|
||||||
|
adc_read() builtin that reads data from ADC
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
system_model = self._vmstate.config.analysis.energy.system_model
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
# Wait for startup and data to be ready
|
||||||
|
for _ in range(system_model.mcu.get_ADC_wait_cycles()):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name)
|
||||||
|
|
||||||
|
# Transfer
|
||||||
|
for _ in range(system_model.mcu.adc_instructions['transfer']):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name)
|
||||||
|
|
||||||
|
|
||||||
|
class adc_get_data(Builtin):
|
||||||
|
"""
|
||||||
|
adc_get_data(n_samples, wait_between_samples) builtin that turns the ADC on, collects n_samples (one every wait_between_samples ms), and turns the ADC off.
|
||||||
|
"""
|
||||||
|
op_mode_name = OpModeName.SIMULATE_ADC
|
||||||
|
|
||||||
|
def __adc_on(self, system_model):
|
||||||
|
# Turn on ADC
|
||||||
|
# - Run instructions
|
||||||
|
# - ADCPowerState -> ON
|
||||||
|
for _ in range(system_model.mcu.adc_instructions['on']):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name)
|
||||||
|
|
||||||
|
system_model.mcu.set_adc_state(ADCPowerState.ON)
|
||||||
|
|
||||||
|
def __adc_off(self, system_model):
|
||||||
|
# Turn off ADC
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name)
|
||||||
|
system_model.mcu.set_adc_state(ADCPowerState.OFF)
|
||||||
|
|
||||||
|
def __adc_transfer_data(self, system_model):
|
||||||
|
# Wait for startup and data to be ready
|
||||||
|
for _ in range(system_model.mcu.get_ADC_wait_cycles()):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name)
|
||||||
|
|
||||||
|
# Transfer
|
||||||
|
for _ in range(system_model.mcu.adc_instructions['transfer']):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name)
|
||||||
|
|
||||||
|
def __wait_cycles(self, system_model, wait_cycles):
|
||||||
|
if wait_cycles <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
for _ in range(wait_cycles):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name)
|
||||||
|
|
||||||
|
def __adc_get_samples_on_off(self, system_model, n_samples, wait_cycles):
|
||||||
|
for i in range(n_samples):
|
||||||
|
self.__adc_on(system_model)
|
||||||
|
self.__adc_transfer_data(system_model)
|
||||||
|
self.__adc_off(system_model)
|
||||||
|
|
||||||
|
if i < (n_samples - 1):
|
||||||
|
self.__wait_cycles(system_model, wait_cycles)
|
||||||
|
|
||||||
|
def __adc_get_samples(self, system_model, n_samples, wait_cycles):
|
||||||
|
self.__adc_on(system_model)
|
||||||
|
|
||||||
|
for i in range(n_samples):
|
||||||
|
self.__adc_transfer_data(system_model)
|
||||||
|
if i < (n_samples - 1):
|
||||||
|
self.__wait_cycles(system_model, wait_cycles)
|
||||||
|
|
||||||
|
self.__adc_off(system_model)
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
system_model = self._vmstate.config.analysis.energy.system_model
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
# Samples to collect
|
||||||
|
n_samples = self.args[0]
|
||||||
|
|
||||||
|
# ms to wait between samples
|
||||||
|
samples_s_wait = float(self.args[1]) / 1000.0
|
||||||
|
|
||||||
|
frequency = system_model.mcu.get_frequency()
|
||||||
|
sample_wait_cycles = max(0, math.ceil(samples_s_wait*frequency) - system_model.mcu.get_ADC_wait_cycles())
|
||||||
|
|
||||||
|
wait_cycles = sample_wait_cycles - system_model.mcu.adc_instructions['on'] - 1
|
||||||
|
|
||||||
|
if self.args[2] == 0:
|
||||||
|
self.__adc_get_samples(system_model, n_samples, sample_wait_cycles)
|
||||||
|
else:
|
||||||
|
self.__adc_get_samples_on_off(system_model, n_samples, wait_cycles)
|
||||||
|
|
||||||
|
class timekeeper_get_time_us(Builtin):
|
||||||
|
"""
|
||||||
|
timekeeper_get_time_us() builtin that returns timekeeper power-off time in us
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
return self._vmstate.config.analysis.energy.system_model.timekeeper.get_time() * 1000000.0
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/Timekeeper not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class timekeeper_get_time(Builtin):
|
||||||
|
"""
|
||||||
|
timekeeper_get_time() builtin that returns timekeeper power-off time
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
return self._vmstate.config.analysis.energy.system_model.timekeeper.get_time()
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/Timekeeper not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class set_mcu_power_on_voltage(Builtin):
|
||||||
|
"""
|
||||||
|
set_mcu_power_on_voltage() builtin that sets the power on voltage of the MCU
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
voltage = self.args[0]
|
||||||
|
#print(f"Set MCU Von={voltage}V")
|
||||||
|
return self._vmstate.config.analysis.energy.system_model.mcu.set_v_on(voltage)
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class get_mcu_time(Builtin):
|
||||||
|
"""
|
||||||
|
get_mcu_time() builtin that returns the mcu time
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
#t = self._vmstate.config.analysis.energy.system_model.mcu.get_time()
|
||||||
|
#print(f"Time: {t}")
|
||||||
|
return self._vmstate.config.analysis.energy.system_model.mcu.get_time()
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class get_mcu_time_us(Builtin):
|
||||||
|
"""
|
||||||
|
get_mcu_time_us() builtin that returns the mcu time in us
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
return self._vmstate.config.analysis.energy.system_model.mcu.get_time() * 1000000
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class set_mcu_time(Builtin):
|
||||||
|
"""
|
||||||
|
set_mcu_time() builtin that sets the mcu time
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
t = float(self.args[0])
|
||||||
|
self._vmstate.config.analysis.energy.system_model.mcu.set_time(t)
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class set_mcu_time_us(Builtin):
|
||||||
|
"""
|
||||||
|
set_mcu_time_us() builtin that sets the mcu time in us
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
t = float(self.args[0]) / 1000000.0
|
||||||
|
self._vmstate.config.analysis.energy.system_model.mcu.set_time(t)
|
||||||
|
except BaseException as e:
|
||||||
|
print(self.args)
|
||||||
|
print(e)
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class get_simulation_time(Builtin):
|
||||||
|
"""
|
||||||
|
get_simulation_time() builtin that returns the simulation time
|
||||||
|
"""
|
||||||
|
print_enabled = False
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
t = self._vmstate.config.analysis.energy.system_model.get_simulation_time()
|
||||||
|
if self.print_enabled:
|
||||||
|
t_us = t * 1000000.0
|
||||||
|
print(f"Time (us): {round(t_us, 2)}")
|
||||||
|
return t
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class reset_simulation_time(Builtin):
|
||||||
|
"""
|
||||||
|
reset_simulation_time() builtin that resets the simulation time
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
self._vmstate.config.analysis.energy.system_model.reset_simulation_time()
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class get_mcu_clock_cycles(Builtin):
|
||||||
|
"""
|
||||||
|
get_mcu_clock_cycles() builtin that returns the number of clock cycles in the latest active cycle
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
return self._vmstate.config.analysis.energy.system_model.mcu.get_clock_cycles()
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class get_used_energy(Builtin):
|
||||||
|
"""
|
||||||
|
get_used_energy() builtin that returns the used energy
|
||||||
|
"""
|
||||||
|
print_enabled = False
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
e = self._vmstate.config.analysis.energy.system_model.get_used_energy()
|
||||||
|
if self.print_enabled:
|
||||||
|
e_uj = e * 1000000.0
|
||||||
|
print(f"Energy consumption (uJ): {round(e_uj, 2)}")
|
||||||
|
return e
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class set_capacitor_voltage(Builtin):
|
||||||
|
"""
|
||||||
|
set_voltage() bultin that sets the energy buffer voltage
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
system_model = self._vmstate.config.analysis.energy.system_model
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
system_model.energy_buffer.set_voltage(self.args[0])
|
||||||
|
|
||||||
|
class reset_used_energy(Builtin):
|
||||||
|
"""
|
||||||
|
reset_energy() builtin that resets the energy consumption
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
self._vmstate.config.analysis.energy.system_model.reset_used_energy()
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel/MCU not attached to ScEpTIC")
|
||||||
|
|
||||||
|
class get_capacitor_voltage(Builtin):
|
||||||
|
"""
|
||||||
|
get_voltage() builtin that returns capacitor voltage
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
try:
|
||||||
|
system_model = self._vmstate.config.analysis.energy.system_model
|
||||||
|
except BaseException:
|
||||||
|
raise Exception("SystemModel not attached to ScEpTIC")
|
||||||
|
|
||||||
|
op_mode_name = OpModeName.PROBE_ENERGY_BUFFER
|
||||||
|
|
||||||
|
# Turn on ADC
|
||||||
|
# - Run instructions
|
||||||
|
# - ADCPowerState -> ON
|
||||||
|
for _ in range(system_model.mcu.adc_instructions['on']):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name)
|
||||||
|
|
||||||
|
system_model.mcu.set_adc_state(ADCPowerState.ON)
|
||||||
|
# Wait for startup and data to be ready
|
||||||
|
for _ in range(system_model.mcu.get_ADC_wait_cycles()):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name, True)
|
||||||
|
|
||||||
|
# Transfer data to reg
|
||||||
|
for _ in range(system_model.mcu.adc_instructions['transfer']):
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name)
|
||||||
|
|
||||||
|
v = system_model.energy_buffer.get_voltage()
|
||||||
|
|
||||||
|
# Turn off ADC
|
||||||
|
self._vmstate.global_clock += system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name)
|
||||||
|
system_model.mcu.set_adc_state(ADCPowerState.OFF)
|
||||||
|
|
||||||
|
return v
|
||||||
|
|
||||||
|
get_capacitor_voltage.define_builtin('sceptic_get_capacitor_voltage', '', 'double')
|
||||||
|
set_capacitor_voltage.define_builtin('sceptic_set_capacitor_voltage', 'double', 'void')
|
||||||
|
get_mcu_clock_cycles.define_builtin('sceptic_get_mcu_clock_cycles', '', 'i32')
|
||||||
|
get_mcu_time.define_builtin('sceptic_get_mcu_time', '', 'double')
|
||||||
|
get_mcu_time_us.define_builtin('sceptic_get_mcu_time_us', '', 'double')
|
||||||
|
get_used_energy.define_builtin('sceptic_get_used_energy', '', 'double')
|
||||||
|
reset_used_energy.define_builtin('sceptic_reset_used_energy', '', 'void')
|
||||||
|
get_simulation_time.define_builtin('sceptic_get_simulation_time', '', 'double')
|
||||||
|
set_mcu_time.define_builtin('sceptic_set_mcu_time', 'double', 'void')
|
||||||
|
set_mcu_time_us.define_builtin('sceptic_set_mcu_time_us', 'double', 'void')
|
||||||
|
reset_simulation_time.define_builtin('sceptic_reset_simulation_time', '', 'void')
|
||||||
|
# voltage
|
||||||
|
set_mcu_power_on_voltage.define_builtin('sceptic_set_mcu_power_on_voltage', 'double', 'void')
|
||||||
|
timekeeper_get_time.define_builtin('sceptic_timekeeper_get_time', '', 'double')
|
||||||
|
timekeeper_get_time_us.define_builtin('sceptic_timekeeper_get_time_us', '', 'double')
|
||||||
|
|
||||||
|
# n_samples, t_wait (ms), on_off
|
||||||
|
adc_get_data.define_builtin('sceptic_adc_get_data', 'i32, double, i32', 'void')
|
||||||
|
adc_on.define_builtin('sceptic_adc_on', '', 'void')
|
||||||
|
adc_off.define_builtin('sceptic_adc_off', '', 'void')
|
||||||
|
adc_read.define_builtin('sceptic_adc_read', '', 'void')
|
||||||
|
|
||||||
|
# bytes, word_size
|
||||||
|
cc1101_transmit.define_builtin('sceptic_cc1101_transmit', 'i32, i32', 'void')
|
||||||
|
|
||||||
|
# state_id
|
||||||
|
cc1101_set_state.define_builtin('sceptic_cc1101_set_state', 'i32', 'void')
|
||||||
|
|
||||||
|
# n_samples
|
||||||
|
generic_adc_sensor_read.define_builtin('sceptic_generic_adc_sensor_read', 'i32', 'void')
|
||||||
|
|
||||||
|
# word size
|
||||||
|
camera_read_data.define_builtin('sceptic_camera_read_data', 'i32', 'void')
|
||||||
|
|
||||||
|
temperature_read_data.define_builtin('sceptic_read_temperature', '', 'void')
|
||||||
|
voc_read_data.define_builtin('sceptic_read_voc', '', 'void')
|
||||||
|
|
||||||
143
ScEpTIC/AST/builtins/libs/std.py
Normal file
143
ScEpTIC/AST/builtins/libs/std.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
|
from ScEpTIC.AST.builtins.builtin import Builtin
|
||||||
|
from ScEpTIC.AST.elements.value import Value
|
||||||
|
|
||||||
|
"""
|
||||||
|
some stdio.h library functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
class debug_print(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the debug_print() builtin
|
||||||
|
Note: you can use debug_print() inside your code and will print all the arguments that it receives.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
print('[Debug Print]: {}'.format(self.args))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class memcmp(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the memcmp() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
buffer_1_addr = self.args[0]
|
||||||
|
buffer_2_addr = self.args[1]
|
||||||
|
|
||||||
|
# element number of i8 -> bytes
|
||||||
|
buffer_length = self.args[2]
|
||||||
|
|
||||||
|
buffer_1 = self._vmstate.memory.get_cells_from_address(buffer_1_addr, buffer_length, False)
|
||||||
|
self._vmstate.memory.simulate_mmu_read_on_cells(buffer_1)
|
||||||
|
buffer_2 = self._vmstate.memory.get_cells_from_address(buffer_2_addr, buffer_length, False)
|
||||||
|
self._vmstate.memory.simulate_mmu_read_on_cells(buffer_2)
|
||||||
|
|
||||||
|
for i in range(0, len(buffer_1)):
|
||||||
|
val_1 = buffer_1[i].content
|
||||||
|
if isinstance(val_1, str):
|
||||||
|
val_1 = ord(val_1)
|
||||||
|
|
||||||
|
val_2 = buffer_2[i].content
|
||||||
|
if isinstance(val_2, str):
|
||||||
|
val_2 = ord(val_2)
|
||||||
|
|
||||||
|
# compensate for memory representation
|
||||||
|
if val_1 < 0:
|
||||||
|
val_1 = Value.convert_sint_to_uint(val_1, buffer_1[i].dimension*8)
|
||||||
|
|
||||||
|
if val_2 < 0:
|
||||||
|
val_2 = Value.convert_sint_to_uint(val_2, buffer_2[i].dimension*8)
|
||||||
|
|
||||||
|
if val_1 != val_2:
|
||||||
|
return val_1 - val_2
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class randombuiltin(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the rand() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
RAND_MAX = 32767
|
||||||
|
return random.randint(0, RAND_MAX)
|
||||||
|
|
||||||
|
|
||||||
|
class srandombuiltin(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the srand() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
random.seed(int(self.args[0]))
|
||||||
|
|
||||||
|
|
||||||
|
class exitbuiltin(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the exit() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
# set program to its end
|
||||||
|
self._vmstate.register_file.pc.update(self._vmstate.main_function_name, self._vmstate.main_function_len)
|
||||||
|
|
||||||
|
# set main return value to the one passed to exit
|
||||||
|
self._vmstate.main_return_value = self.args[0]
|
||||||
|
|
||||||
|
class timebuiltin(Builtin):
|
||||||
|
"""
|
||||||
|
Definiton of the time() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return int(time.time())
|
||||||
|
|
||||||
|
|
||||||
|
class strlen(Builtin):
|
||||||
|
"""
|
||||||
|
Definition of the strlen() builtin
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
base_addr = self.args[0]
|
||||||
|
str_len = 0
|
||||||
|
bits = 8
|
||||||
|
|
||||||
|
while True:
|
||||||
|
addr = self._vmstate.memory.add_offset(base_addr, bits*str_len)
|
||||||
|
data = self._vmstate.memory.get_cells_from_address(addr, bits)
|
||||||
|
self._vmstate.memory.simulate_mmu_read_on_cells(data)
|
||||||
|
|
||||||
|
if len(data) != 1:
|
||||||
|
raise Exception("strlen() error: multiple cells within 8 bits!")
|
||||||
|
|
||||||
|
if data[0].content == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
str_len += 1
|
||||||
|
|
||||||
|
return str_len
|
||||||
|
|
||||||
|
|
||||||
|
# custom print
|
||||||
|
debug_print.define_builtin('debug_print', 'i32', 'void')
|
||||||
|
|
||||||
|
# definition for memcmp() and strncmp()
|
||||||
|
memcmp.define_builtin('memcmp', 'i8, i8, i64', 'i32')
|
||||||
|
memcmp.define_builtin('strncmp', 'i8, i8, i64', 'i32')
|
||||||
|
|
||||||
|
# random functions
|
||||||
|
randombuiltin.define_builtin('rand', '', 'i32')
|
||||||
|
srandombuiltin.define_builtin('srand', 'i32', 'void')
|
||||||
|
|
||||||
|
# definition for exit()
|
||||||
|
exitbuiltin.define_builtin('exit', 'i32', 'void')
|
||||||
|
|
||||||
|
strlen.define_builtin('strlen', 'i8', 'i64')
|
||||||
|
timebuiltin.define_builtin('time', '', 'i32')
|
||||||
249
ScEpTIC/AST/builtins/linker.py
Normal file
249
ScEpTIC/AST/builtins/linker.py
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from ScEpTIC.AST.elements.function import Function
|
||||||
|
from ScEpTIC.AST.elements.instructions.memory_operations import AllocaOperation, LoadOperation, StoreOperation
|
||||||
|
from ScEpTIC.AST.elements.instructions.termination_instructions import ReturnOperation
|
||||||
|
from ScEpTIC.AST.elements.value import Value
|
||||||
|
from ScEpTIC.llvmir_parser.sections_parser import global_vars
|
||||||
|
|
||||||
|
|
||||||
|
class BuiltinLinker:
|
||||||
|
"""
|
||||||
|
This class generates the code of a builtin function.
|
||||||
|
Builtin functions are implementation of library functions (e.g. sqrt, pow) that performs some
|
||||||
|
architecture-dependent operations, which doesn't modify the control flow nor the memory (SRAM, NVM).
|
||||||
|
|
||||||
|
In case control-flow modification or memory accesses are needed, it is better to directly create some C code
|
||||||
|
for them and include it in the final code to be analyzed.
|
||||||
|
|
||||||
|
Builtins are just used to not include in the final code library operations, and should not be used to
|
||||||
|
emulate memory accesses, global variables accesses, direct-memory writes and other major operations,
|
||||||
|
which can be easily implemented in the program code.
|
||||||
|
|
||||||
|
An example of a builtins are mathematical functions and printing functions.
|
||||||
|
|
||||||
|
The core of a builtin is an extension of the Instruction operation. This is needed to be able to analyze the code.
|
||||||
|
|
||||||
|
|
||||||
|
Normally LLVM will consider the arguments to be from virtual register %0 to %(#args - 1)
|
||||||
|
and the %(#args) is the ID of the first basic block. Since the builtins should not have labels and control-flow operations,
|
||||||
|
it is omitted.
|
||||||
|
|
||||||
|
Arguments are passed from %0 to %(len(args) - 1)
|
||||||
|
Arguments addresses in stack are stored from %(len(args)) to %(len(args)*2 - 1)
|
||||||
|
Arguments values are loaded from %(len(args) * 2) to %(len(args) * 3 - 1)
|
||||||
|
Return value will be on %(len(args) * 3)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
prefix = None
|
||||||
|
|
||||||
|
def __init__(self, name, arguments, return_type, instruction):
|
||||||
|
self.name = "{}{}".format(self.prefix if self.prefix is not None else '', name)
|
||||||
|
|
||||||
|
# link only if a declaration exists, which means that the function is used inside the program.
|
||||||
|
if self.name not in Function.declarations:
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.arguments = self._parse_arguments(arguments)
|
||||||
|
self.return_type = global_vars.parse_type(return_type, True, True)
|
||||||
|
self.body = []
|
||||||
|
self._attach_instruction(instruction)
|
||||||
|
|
||||||
|
logging.info('[BuiltinLinker] Linking {} into {}'.format(instruction.__name__, self.name))
|
||||||
|
|
||||||
|
self.is_input = instruction.is_input if hasattr(instruction, 'is_input') else False
|
||||||
|
self.input_name = instruction.input_name if self.is_input else None
|
||||||
|
|
||||||
|
self._link()
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_arguments(arguments):
|
||||||
|
"""
|
||||||
|
Parses the argument string and returns a list of types
|
||||||
|
"""
|
||||||
|
|
||||||
|
# no arguments
|
||||||
|
if len(arguments) == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
arguments = arguments.split(',')
|
||||||
|
args = []
|
||||||
|
for i in arguments:
|
||||||
|
# remove initial spaces
|
||||||
|
if i[0] == ' ':
|
||||||
|
i = i[1:]
|
||||||
|
|
||||||
|
arg = global_vars.parse_type(i, True, True)
|
||||||
|
args.append(arg)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def _link(self):
|
||||||
|
"""
|
||||||
|
Creates the function, attach the body and maps it into function_definitions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
f = Function(self.name, self.return_type, self.arguments, None, None)
|
||||||
|
f.attach_body(self.body)
|
||||||
|
f.is_input = self.is_input
|
||||||
|
f.input_name = self.input_name
|
||||||
|
f.is_builtin = True
|
||||||
|
|
||||||
|
|
||||||
|
def _attach_instruction(self, instruction):
|
||||||
|
"""
|
||||||
|
Attach the actual ad-hoc code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._arguments_init()
|
||||||
|
|
||||||
|
# set target register
|
||||||
|
target = self._get_return_register()
|
||||||
|
instruction = instruction()
|
||||||
|
instruction.target = target
|
||||||
|
|
||||||
|
args = self._get_arguments()
|
||||||
|
instruction.set_args(args)
|
||||||
|
|
||||||
|
self.body.append(instruction)
|
||||||
|
|
||||||
|
self._generate_return()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_argument_type(self, argument_id):
|
||||||
|
"""
|
||||||
|
Returns the type of a given argument
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.arguments[argument_id]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_argument_register_address(self, argument_id):
|
||||||
|
"""
|
||||||
|
Returns the register which contains the address in which the argument will be, as a Value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
reg_id = '%{}'.format(len(self.arguments) + argument_id)
|
||||||
|
reg_type = self._get_argument_type(argument_id)
|
||||||
|
|
||||||
|
return Value('virtual_reg', reg_id, reg_type)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_argument_register(self, argument_id):
|
||||||
|
"""
|
||||||
|
Returns the register containing the value of the given argument, as a Value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
reg_id = '%{}'.format((len(self.arguments) * 2) + argument_id)
|
||||||
|
reg_type = self._get_argument_type(argument_id)
|
||||||
|
|
||||||
|
return Value('virtual_reg', reg_id, reg_type)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_passed_argument_register(self, argument_id):
|
||||||
|
"""
|
||||||
|
Returns the virtual register in which the passed argument is.
|
||||||
|
"""
|
||||||
|
|
||||||
|
reg_id = '%{}'.format(argument_id)
|
||||||
|
reg_type = self._get_argument_type(argument_id)
|
||||||
|
|
||||||
|
return Value('virtual_reg', reg_id, reg_type)
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_save_argument_in_stack(self, argument_id):
|
||||||
|
"""
|
||||||
|
Generates the operations needed to save the given argument onto the stack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# allocate space on stack
|
||||||
|
target = self._get_argument_register_address(argument_id)
|
||||||
|
elements_number = 1
|
||||||
|
align = 0
|
||||||
|
alloca = AllocaOperation(target, target.type, elements_number, align)
|
||||||
|
self.body.append(alloca)
|
||||||
|
|
||||||
|
# save passed argument onto the stack
|
||||||
|
value = self._get_passed_argument_register(argument_id)
|
||||||
|
align = 0
|
||||||
|
volatile = False
|
||||||
|
store = StoreOperation(target, value, align, volatile)
|
||||||
|
self.body.append(store)
|
||||||
|
|
||||||
|
|
||||||
|
def _save_arguments(self):
|
||||||
|
"""
|
||||||
|
Saves the arguments onto the stack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for i in range(0, len(self.arguments)):
|
||||||
|
self._generate_save_argument_in_stack(i)
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_load_argument_from_stack(self, argument_id):
|
||||||
|
"""
|
||||||
|
Generates the load operation which loads from stack into a register the given argument.
|
||||||
|
"""
|
||||||
|
|
||||||
|
target = self._get_argument_register(argument_id)
|
||||||
|
load_type = self._get_argument_type(argument_id)
|
||||||
|
element = self._get_argument_register_address(argument_id)
|
||||||
|
align = 0
|
||||||
|
volatile = False
|
||||||
|
load = LoadOperation(target, load_type, element, align, volatile)
|
||||||
|
self.body.append(load)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_arguments(self):
|
||||||
|
"""
|
||||||
|
Loads the arguments onto the stack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for i in range(0, len(self.arguments)):
|
||||||
|
self._generate_load_argument_from_stack(i)
|
||||||
|
|
||||||
|
|
||||||
|
def _arguments_init(self):
|
||||||
|
"""
|
||||||
|
Saves onto the stack the arguments and loads them into the corresponding register.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._save_arguments()
|
||||||
|
self._load_arguments()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_arguments(self):
|
||||||
|
"""
|
||||||
|
Returns the arguments (as Value virtual_reg) of the builtin.
|
||||||
|
This must be used by the actual builtin-code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = []
|
||||||
|
for i in range(0, len(self.arguments)):
|
||||||
|
arg = self._get_argument_register(i)
|
||||||
|
args.append(arg)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def _get_return_register(self):
|
||||||
|
"""
|
||||||
|
Returns the register in which the return value will be contained.
|
||||||
|
"""
|
||||||
|
|
||||||
|
reg_id = '%{}'.format(len(self.arguments) * 3)
|
||||||
|
|
||||||
|
return Value('virtual_reg', reg_id, self.return_type)
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_return(self):
|
||||||
|
"""
|
||||||
|
Generates the return instruction needed at the end of the builtin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
value = self._get_return_register()
|
||||||
|
ret = ReturnOperation(value)
|
||||||
|
self.body.append(ret)
|
||||||
0
ScEpTIC/AST/elements/__init__.py
Normal file
0
ScEpTIC/AST/elements/__init__.py
Normal file
BIN
ScEpTIC/AST/elements/__pycache__/__init__.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/elements/__pycache__/__init__.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/elements/__pycache__/function.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/elements/__pycache__/function.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/elements/__pycache__/global_var.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/elements/__pycache__/global_var.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/elements/__pycache__/instruction.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/elements/__pycache__/instruction.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/elements/__pycache__/metadata.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/elements/__pycache__/metadata.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/elements/__pycache__/types.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/elements/__pycache__/types.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/elements/__pycache__/value.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/elements/__pycache__/value.pypy310.pyc
Normal file
Binary file not shown.
226
ScEpTIC/AST/elements/function.py
Normal file
226
ScEpTIC/AST/elements/function.py
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import copy
|
||||||
|
|
||||||
|
from ScEpTIC import tools
|
||||||
|
from ScEpTIC.AST.elements.instructions.memory_operations import AllocaOperation
|
||||||
|
from ScEpTIC.AST.elements.instructions.other_operations import CallOperation
|
||||||
|
from ScEpTIC.AST.elements.instructions.termination_instructions import BranchOperation
|
||||||
|
from ScEpTIC.exceptions import RuntimeException
|
||||||
|
|
||||||
|
|
||||||
|
class Function:
|
||||||
|
"""
|
||||||
|
AST node representing a function.
|
||||||
|
Contains the function's definition and body.
|
||||||
|
"""
|
||||||
|
|
||||||
|
elements = {}
|
||||||
|
declarations = {}
|
||||||
|
|
||||||
|
def __init__(self, name, return_type, arguments, attr_groups, metadata):
|
||||||
|
self.name = name
|
||||||
|
self.type = return_type
|
||||||
|
|
||||||
|
# is a list of Types with additional attributes field.
|
||||||
|
self.arguments = arguments
|
||||||
|
|
||||||
|
self.attr_groups = attr_groups
|
||||||
|
self.metadata = metadata
|
||||||
|
self.body = None
|
||||||
|
|
||||||
|
# dictionary that maps a label with the first instruction of the corresponding basic block
|
||||||
|
self._instr_label_maps = {}
|
||||||
|
self.declarations[self.name] = self
|
||||||
|
|
||||||
|
# instance of the object in charge of performing register allocation over this function.
|
||||||
|
# it is assigned by the register allocation and is used to perform pre and post processing.
|
||||||
|
self.register_allocator = None
|
||||||
|
|
||||||
|
# contains the names of the functions called by the current one. It is used by register
|
||||||
|
# allocation, for finding the overall number of used registers, to emulate the saving of registers
|
||||||
|
# before running a function. It also helps in finding the best possible allocation of registers
|
||||||
|
# w.r.t. different functions.
|
||||||
|
# NB: in self._calls recursion is not considered, since the number of registers to be saved
|
||||||
|
# will be equal to the number of registers used by the function.
|
||||||
|
self._calls = []
|
||||||
|
|
||||||
|
self.is_input = False
|
||||||
|
self.input_name = None
|
||||||
|
self.is_builtin = False
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retval = '{} {}()'.format(self.type, self.name, self.arguments)
|
||||||
|
|
||||||
|
if self.body is not None:
|
||||||
|
retval += '\n'+tools.fancy_list_to_str(self.body)
|
||||||
|
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.body)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def first_basic_block_id(self):
|
||||||
|
"""
|
||||||
|
Returns the id of the first basic block.
|
||||||
|
If the function has some arguments, each argument is mapped starting from %0 to %(#args - 1)
|
||||||
|
The first basic block label will be %(#args)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return '%{}'.format(len(self.arguments))
|
||||||
|
|
||||||
|
|
||||||
|
def attach_body(self, body):
|
||||||
|
"""
|
||||||
|
Sets the body of a function definition and creates the mapping between instruction id and labels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.elements[self.name] = self
|
||||||
|
del self.declarations[self.name]
|
||||||
|
self.body = body
|
||||||
|
self.update_labels()
|
||||||
|
self._populate_calls()
|
||||||
|
self._adjust_useless_branch_ticks()
|
||||||
|
|
||||||
|
|
||||||
|
def update_body(self, new_body):
|
||||||
|
"""
|
||||||
|
Updates the body of the function
|
||||||
|
"""
|
||||||
|
self.body = copy.deepcopy(new_body)
|
||||||
|
self.update_labels()
|
||||||
|
self._populate_calls()
|
||||||
|
|
||||||
|
|
||||||
|
def update_labels(self):
|
||||||
|
"""
|
||||||
|
Updates the label-instruction mappings
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._instr_label_maps = {}
|
||||||
|
for i in range(0, len(self.body)):
|
||||||
|
instruction = self.body[i]
|
||||||
|
|
||||||
|
if instruction.label is not None:
|
||||||
|
self._instr_label_maps[instruction.label] = i
|
||||||
|
|
||||||
|
|
||||||
|
def get_instruction_id(self, label):
|
||||||
|
"""
|
||||||
|
Returns the instruction which has a given label.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if label not in self._instr_label_maps:
|
||||||
|
raise RuntimeException('Unable to find label {} in function {}.'.format(label, self.name))
|
||||||
|
|
||||||
|
return self._instr_label_maps[label]
|
||||||
|
|
||||||
|
|
||||||
|
def get_ignore(self):
|
||||||
|
"""
|
||||||
|
Returns a list of register names to be ignored by register allocation.
|
||||||
|
For a function corresponds to the registers used to pass the parameters, which are from
|
||||||
|
%0 to %(n-1) (n = number of arguments).
|
||||||
|
"""
|
||||||
|
|
||||||
|
ignores = []
|
||||||
|
for i in range(0, len(self.arguments)):
|
||||||
|
ignores.append('%{}'.format(i))
|
||||||
|
|
||||||
|
return ignores
|
||||||
|
|
||||||
|
|
||||||
|
def _populate_calls(self):
|
||||||
|
"""
|
||||||
|
Populates the self._call list.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._calls = []
|
||||||
|
|
||||||
|
for i in self.body:
|
||||||
|
|
||||||
|
# if is a call operation, and is not a call to the same function append it to self._calls
|
||||||
|
# recursion is not considered, since the obtained list is used to verify the maximun number of
|
||||||
|
# register used by a function and a call to the same function won't change the number of registers
|
||||||
|
# used by the function.
|
||||||
|
if isinstance(i, CallOperation):
|
||||||
|
name = i.name
|
||||||
|
if name != self.name and name not in self._calls:
|
||||||
|
self._calls.append(name)
|
||||||
|
|
||||||
|
def _remove_from_calls(self, name):
|
||||||
|
"""
|
||||||
|
Removes a given function name from the self._call list
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._calls.remove(name)
|
||||||
|
# element not in list
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _adjust_useless_branch_ticks(self):
|
||||||
|
"""
|
||||||
|
This method virtually removes the useless branch instructions, which are
|
||||||
|
unconditional branches that jumps to the next instruction. Those branches are
|
||||||
|
necessary to have a basic-block division, but in the real final machine code they will
|
||||||
|
be removed. For such reason, the number of clock ticks to be increased by those branches is set to 0,
|
||||||
|
so to run them but to do not interfere with the analysis. (This allows me to not remove them)
|
||||||
|
"""
|
||||||
|
|
||||||
|
body_len = len(self.body)
|
||||||
|
|
||||||
|
# nb: unconditional branches have condition = None
|
||||||
|
|
||||||
|
for i in range(0, body_len):
|
||||||
|
code = self.body[i]
|
||||||
|
|
||||||
|
if isinstance(code, BranchOperation):
|
||||||
|
# if is not an unconditional branch, skip
|
||||||
|
if code.condition is not None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# next instruction id
|
||||||
|
next_instr_index = i+1
|
||||||
|
|
||||||
|
# if a next instruction exists && if the branch jumps to the next instruction, set its tick to 0
|
||||||
|
if next_instr_index < body_len and code.target_true == self.body[next_instr_index].label:
|
||||||
|
code.tick_count = 0
|
||||||
|
|
||||||
|
def adjust_alloca_ticks(self):
|
||||||
|
"""
|
||||||
|
This method sets all the ticks for alloca operations equal to 0, except for the last one.
|
||||||
|
Alloca operations are merged together during datalayout into a single ebp increment.
|
||||||
|
The last alloca is the one with tick = 1 for skipping the entire alloca section when running in
|
||||||
|
intermittent execution (otherwise multiple alloca operations might be run, but the result would be wrong)
|
||||||
|
"""
|
||||||
|
|
||||||
|
body_len = len(self.body)
|
||||||
|
last_alloca_id = -1
|
||||||
|
|
||||||
|
args = len(self.arguments)
|
||||||
|
|
||||||
|
# scan the code for alloca operations
|
||||||
|
for i in range(0, body_len):
|
||||||
|
code = self.body[i]
|
||||||
|
|
||||||
|
if not isinstance(code, AllocaOperation):
|
||||||
|
continue
|
||||||
|
|
||||||
|
last_alloca_id = i
|
||||||
|
code.tick_count = 0
|
||||||
|
|
||||||
|
var_metadata = code.metadata.retrieve() if code.metadata is not None else None
|
||||||
|
|
||||||
|
if var_metadata is None or 'variable_name' not in var_metadata:
|
||||||
|
metadata_append = ''
|
||||||
|
else:
|
||||||
|
metadata_append = ' "{}"'.format(var_metadata['variable_name'])
|
||||||
|
|
||||||
|
metadata_prepend = 'function argument' if i < args else 'local variable'
|
||||||
|
|
||||||
|
code.metadata = '{}{}'.format(metadata_prepend, metadata_append)
|
||||||
|
|
||||||
|
if last_alloca_id >= 0:
|
||||||
|
self.body[last_alloca_id].tick_count = 1
|
||||||
43
ScEpTIC/AST/elements/global_var.py
Normal file
43
ScEpTIC/AST/elements/global_var.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import collections
|
||||||
|
|
||||||
|
class GlobalVar:
|
||||||
|
"""
|
||||||
|
AST node representing a global variable
|
||||||
|
"""
|
||||||
|
|
||||||
|
# use a ordered dict to keep global variables in the same order as specified by the programmer.
|
||||||
|
elements = collections.OrderedDict()
|
||||||
|
|
||||||
|
def __init__(self, name, var_type, initial_val, is_constant, align, section, comdat, metadata):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
self.is_constant = is_constant
|
||||||
|
|
||||||
|
self.type = var_type
|
||||||
|
self.initial_val = initial_val
|
||||||
|
|
||||||
|
if align is None:
|
||||||
|
align = 0
|
||||||
|
|
||||||
|
self.align = int(align)
|
||||||
|
|
||||||
|
self.section = section
|
||||||
|
self.comdat = comdat
|
||||||
|
|
||||||
|
self.metadata = metadata
|
||||||
|
|
||||||
|
self.elements[self.name] = self
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retval = 'global_var: '
|
||||||
|
|
||||||
|
if self.section is not None:
|
||||||
|
retval += '['+str(self.section)+'] '
|
||||||
|
|
||||||
|
retval += '{} {}'.format(self.type, self.name)
|
||||||
|
|
||||||
|
if self.initial_val is not None:
|
||||||
|
retval += ' = {}'.format(self.initial_val)
|
||||||
|
|
||||||
|
return retval
|
||||||
142
ScEpTIC/AST/elements/instruction.py
Normal file
142
ScEpTIC/AST/elements/instruction.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from ScEpTIC import tools
|
||||||
|
|
||||||
|
|
||||||
|
class Instruction:
|
||||||
|
"""
|
||||||
|
Generic AST Instruction
|
||||||
|
"""
|
||||||
|
|
||||||
|
_vmstate = None
|
||||||
|
|
||||||
|
# number of clock tick to be increased by this instruction
|
||||||
|
tick_count = 1
|
||||||
|
memory_tick_count = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.basic_block_id = None
|
||||||
|
self.label = None
|
||||||
|
self.preds = None
|
||||||
|
self.metadata = None
|
||||||
|
self._omit_target = False
|
||||||
|
self.memory_tag = None
|
||||||
|
self.memory_tag_dependency = None
|
||||||
|
|
||||||
|
self.is_part_of_dummy_write = False
|
||||||
|
self.is_dummy_write_master = False
|
||||||
|
self.dummy_write_master = None
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = ''
|
||||||
|
|
||||||
|
if self.label is not None:
|
||||||
|
retstr += '[{}] '.format(self.label)
|
||||||
|
|
||||||
|
if 'target' in self.__dict__ and not self._omit_target and self.target is not None:
|
||||||
|
retstr += '{} = '.format(self.target)
|
||||||
|
|
||||||
|
return retstr
|
||||||
|
|
||||||
|
|
||||||
|
def save_in_target_register(self, value):
|
||||||
|
"""
|
||||||
|
Saves a given value in the target register of the instruction, if present.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._omit_target or self.target is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# get target register name
|
||||||
|
target = self.target.value
|
||||||
|
|
||||||
|
self._vmstate.register_file.write(target, value)
|
||||||
|
|
||||||
|
if self._vmstate.input_lookup_enabled:
|
||||||
|
input_lookup_data = self.get_input_lookup()
|
||||||
|
self._vmstate.register_file.set_input_lookup(target, input_lookup_data)
|
||||||
|
|
||||||
|
logging.info('[{}] Saving result in {}'.format(self.instruction_type, target))
|
||||||
|
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
"""
|
||||||
|
Executes the operation and the target assignment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
value = self.get_val()
|
||||||
|
self.save_in_target_register(value)
|
||||||
|
|
||||||
|
# call run's callback
|
||||||
|
self._vmstate.on_run(self.tick_count, update_program_counter)
|
||||||
|
|
||||||
|
|
||||||
|
def get_defs(self):
|
||||||
|
"""
|
||||||
|
Returns a list of registers defined by this instruction. Usually its len will be 1 or 0.
|
||||||
|
(used by register allocation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if 'target' in self.__dict__.keys() and self.target is not None:
|
||||||
|
return self.target.get_uses()
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_uses(self):
|
||||||
|
"""
|
||||||
|
Returns a list containing the names of the registers used by this instruction.
|
||||||
|
(used by register allocation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_ignore(self):
|
||||||
|
"""
|
||||||
|
Returns a list of register names to be ignored by register allocation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_lookup(self):
|
||||||
|
"""
|
||||||
|
Returns the input lookup data for the current operation
|
||||||
|
"""
|
||||||
|
|
||||||
|
return tools.build_input_lookup_data(None, None)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def instruction_type(self):
|
||||||
|
"""
|
||||||
|
Returns the instruction type
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def replace_reg_name(self, old_reg_name, new_reg_name):
|
||||||
|
"""
|
||||||
|
Skeleton.
|
||||||
|
Replaces the name of a register used by the instruction with a new one.
|
||||||
|
(used by register allocation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_memory_tag(self, elements):
|
||||||
|
"""
|
||||||
|
Resolves and returns the memory tag of the targeted element
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement resolve_memory_tag()")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_memory_address_chain(self, elements):
|
||||||
|
"""
|
||||||
|
Returns a list of all the instructions required to get the address of the targeted element(s)
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement resolve_memory_address_chain()")
|
||||||
0
ScEpTIC/AST/elements/instructions/__init__.py
Normal file
0
ScEpTIC/AST/elements/instructions/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
342
ScEpTIC/AST/elements/instructions/binary_operation.py
Normal file
342
ScEpTIC/AST/elements/instructions/binary_operation.py
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from ScEpTIC import tools
|
||||||
|
from ScEpTIC.AST.elements.instruction import Instruction
|
||||||
|
from ScEpTIC.exceptions import RuntimeException
|
||||||
|
|
||||||
|
|
||||||
|
class BinaryOperation(Instruction):
|
||||||
|
"""
|
||||||
|
AST nodes for the LLVM Binary Instructions group
|
||||||
|
https://llvm.org/docs/LangRef.html#binaryops
|
||||||
|
|
||||||
|
NB: each result is stored as SIGNED int, even if the operation is unsigned.
|
||||||
|
The sign bit is an interpretation of UNSIGNED operations, which will manage that by converting the int to its unsigned equivalent.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, operation_type, first_operand, second_operand, target, is_bitwise, specific_attributes):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.operation_type = operation_type
|
||||||
|
self.first_operand = first_operand
|
||||||
|
self.second_operand = second_operand
|
||||||
|
self.target = target
|
||||||
|
self.is_bitwise = is_bitwise
|
||||||
|
|
||||||
|
# just used to determine if a result is a "poison value", which is for back-end optimizations.
|
||||||
|
self.exact = 'exact' in specific_attributes
|
||||||
|
self.no_unsigned_wrap = 'nuw' in specific_attributes
|
||||||
|
self.no_signed_wrap = 'nsw' in specific_attributes
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = super().__str__()
|
||||||
|
retstr += '{} {}, {}'.format(self.operation_type, self.first_operand, self.second_operand)
|
||||||
|
return retstr
|
||||||
|
|
||||||
|
|
||||||
|
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 get_val(self):
|
||||||
|
"""
|
||||||
|
Returns the value obtained from the operation.
|
||||||
|
It converts address operands (if present) to relative spaces, applies the operation and converts them back to the absolute space.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 operations)
|
||||||
|
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 operations)
|
||||||
|
if self._vmstate.memory._is_absolute_address(second_operand):
|
||||||
|
prefix2, second_operand = self._vmstate.memory._parse_absolute_address(second_operand)
|
||||||
|
|
||||||
|
# normalize prefixes: if one is none, just copy from the other.
|
||||||
|
if prefix1 is None:
|
||||||
|
prefix1 = prefix2
|
||||||
|
|
||||||
|
elif prefix2 is None:
|
||||||
|
prefix2 = prefix1
|
||||||
|
|
||||||
|
val = self._get_val(first_operand, second_operand)
|
||||||
|
|
||||||
|
# if prefix is set the result is an address, so I must convert it from relative to absolute space.
|
||||||
|
if prefix1 is not None:
|
||||||
|
# NB: prefixes must be the same
|
||||||
|
if prefix1 != prefix2:
|
||||||
|
raise RuntimeException('Uncompatible address space {} {} for binary operation {}.'.format(prefix1, prefix2, self.operation_type))
|
||||||
|
|
||||||
|
val = self._vmstate.memory._convert_to_absolute_address(prefix1, val)
|
||||||
|
|
||||||
|
logging.info('[{}] Executing {} {}, {} with result {}'.format(self.instruction_type, self.operation_type, first_operand, second_operand, val))
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_val(self, first_operand, second_operand):
|
||||||
|
"""
|
||||||
|
Returns the value returned from the operation, given its operands.
|
||||||
|
"""
|
||||||
|
|
||||||
|
dim = len(self.first_operand.type)
|
||||||
|
|
||||||
|
if self.operation_type == 'add':
|
||||||
|
# force encoding
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
val = first_operand + second_operand
|
||||||
|
# force a number of bits
|
||||||
|
|
||||||
|
return self.first_operand.convert_sint_to_sint(val, dim)
|
||||||
|
|
||||||
|
elif self.operation_type == 'fadd':
|
||||||
|
first_operand = float(first_operand)
|
||||||
|
second_operand = float(second_operand)
|
||||||
|
|
||||||
|
return first_operand + second_operand
|
||||||
|
|
||||||
|
elif self.operation_type == 'sub':
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
val = first_operand - second_operand
|
||||||
|
|
||||||
|
return self.first_operand.convert_sint_to_sint(val, dim)
|
||||||
|
|
||||||
|
elif self.operation_type == 'fsub':
|
||||||
|
first_operand = float(first_operand)
|
||||||
|
second_operand = float(second_operand)
|
||||||
|
|
||||||
|
return first_operand - second_operand
|
||||||
|
|
||||||
|
elif self.operation_type == 'mul':
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
val = int(first_operand * second_operand)
|
||||||
|
|
||||||
|
return self.first_operand.convert_sint_to_sint(val, dim)
|
||||||
|
|
||||||
|
elif self.operation_type == 'fmul':
|
||||||
|
first_operand = float(first_operand)
|
||||||
|
second_operand = float(second_operand)
|
||||||
|
|
||||||
|
return float(first_operand * second_operand)
|
||||||
|
|
||||||
|
elif self.operation_type == 'udiv':
|
||||||
|
# unsigned operations considers their operands as unsigned.
|
||||||
|
# http://lists.llvm.org/pipermail/llvm-dev/2017-July/115975.html
|
||||||
|
dim = len(self.first_operand)
|
||||||
|
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
first_operand = self.first_operand.convert_sint_to_uint(first_operand, dim)
|
||||||
|
second_operand = self.second_operand.convert_sint_to_uint(second_operand, dim)
|
||||||
|
|
||||||
|
val = int(first_operand // second_operand)
|
||||||
|
|
||||||
|
# result is unsigned. Memory cells in my representation uses signed only.
|
||||||
|
return self.first_operand.convert_uint_to_sint(val, dim)
|
||||||
|
|
||||||
|
elif self.operation_type == 'sdiv':
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
val = int(first_operand // second_operand)
|
||||||
|
|
||||||
|
return self.first_operand.convert_sint_to_sint(val, dim)
|
||||||
|
|
||||||
|
elif self.operation_type == 'fdiv':
|
||||||
|
return float(first_operand / second_operand)
|
||||||
|
|
||||||
|
elif self.operation_type == 'urem':
|
||||||
|
# unsigned operations considers their operands as unsigned.
|
||||||
|
# http://lists.llvm.org/pipermail/llvm-dev/2017-July/115975.html
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
dim = len(self.first_operand)
|
||||||
|
first_operand = self.first_operand.convert_sint_to_uint(first_operand, dim)
|
||||||
|
second_operand = self.second_operand.convert_sint_to_uint(second_operand, dim)
|
||||||
|
|
||||||
|
val = int(first_operand % second_operand)
|
||||||
|
|
||||||
|
# result is unsigned. Memory cells in my representation uses signed only.
|
||||||
|
return self.first_operand.convert_uint_to_sint(val, dim)
|
||||||
|
|
||||||
|
elif self.operation_type == 'srem':
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
val = int(first_operand % second_operand)
|
||||||
|
|
||||||
|
return self.first_operand.convert_sint_to_sint(val, dim)
|
||||||
|
|
||||||
|
elif self.operation_type == 'frem':
|
||||||
|
first_operand = float(first_operand)
|
||||||
|
second_operand = float(second_operand)
|
||||||
|
|
||||||
|
return float(first_operand % second_operand)
|
||||||
|
|
||||||
|
elif self.operation_type == 'shl':
|
||||||
|
# logic implemented to overcome discrepancies between signed and unsigned results
|
||||||
|
# e.g. -1 << 2 != 65535 << 2 for 16bit operations
|
||||||
|
|
||||||
|
first_operand = self.first_operand.convert_sint_to_bin(first_operand, dim)
|
||||||
|
sh_len = int(second_operand)
|
||||||
|
|
||||||
|
shifted = first_operand[sh_len:]+sh_len*'0'
|
||||||
|
|
||||||
|
return self.first_operand.convert_bin_to_sint(shifted)
|
||||||
|
|
||||||
|
elif self.operation_type == 'lshr':
|
||||||
|
# logical shift right: append 0 to added left bits
|
||||||
|
|
||||||
|
first_operand = self.first_operand.convert_sint_to_bin(first_operand, dim)
|
||||||
|
sh_len = int(second_operand)
|
||||||
|
|
||||||
|
shifted = sh_len*'0'+first_operand[:dim-sh_len]
|
||||||
|
|
||||||
|
return self.first_operand.convert_bin_to_sint(shifted)
|
||||||
|
|
||||||
|
elif self.operation_type == 'ashr':
|
||||||
|
# arithmetical shift right: append sign bit to added left bits
|
||||||
|
|
||||||
|
first_operand = self.first_operand.convert_sint_to_bin(first_operand, dim)
|
||||||
|
sh_len = int(second_operand)
|
||||||
|
|
||||||
|
shifted = sh_len*first_operand[0]+first_operand[:dim-sh_len]
|
||||||
|
|
||||||
|
return self.first_operand.convert_bin_to_sint(shifted)
|
||||||
|
|
||||||
|
elif self.operation_type == 'and':
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
return int(first_operand & second_operand)
|
||||||
|
|
||||||
|
elif self.operation_type == 'or':
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
return int(first_operand | second_operand)
|
||||||
|
|
||||||
|
elif self.operation_type == 'xor':
|
||||||
|
first_operand = int(first_operand)
|
||||||
|
second_operand = int(second_operand)
|
||||||
|
|
||||||
|
return int(first_operand ^ second_operand)
|
||||||
|
|
||||||
|
# if gets there, the operation is not supported right now.
|
||||||
|
raise NotImplementedError('{} is not supported for now!'.format(self.operation_type))
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_memory_tag(self, elements):
|
||||||
|
"""
|
||||||
|
Resolves and returns the memory tag of the targeted element
|
||||||
|
"""
|
||||||
|
if self.memory_tag is None:
|
||||||
|
|
||||||
|
if self.operation_type == 'add' or self.operation_type == 'fadd':
|
||||||
|
operation_tag = '+'
|
||||||
|
|
||||||
|
elif self.operation_type == 'sub' or self.operation_type == 'fsub':
|
||||||
|
operation_tag = '-'
|
||||||
|
|
||||||
|
elif self.operation_type == 'mul' or self.operation_type == 'fmul':
|
||||||
|
operation_tag = '*'
|
||||||
|
|
||||||
|
elif self.operation_type == 'udiv' or self.operation_type == 'sdiv' or self.operation_type == 'fdiv':
|
||||||
|
operation_tag = '/'
|
||||||
|
|
||||||
|
elif self.operation_type == 'urem' or self.operation_type == 'srem' or self.operation_type == 'frem':
|
||||||
|
operation_tag = '%'
|
||||||
|
|
||||||
|
elif self.operation_type == 'shl':
|
||||||
|
operation_tag = '<<'
|
||||||
|
|
||||||
|
elif self.operation_type == 'lshr' or self.operation_type == 'ashr':
|
||||||
|
operation_tag = '>>'
|
||||||
|
|
||||||
|
elif self.operation_type == 'and':
|
||||||
|
operation_tag = '&&'
|
||||||
|
|
||||||
|
elif self.operation_type == 'or':
|
||||||
|
operation_tag = '||'
|
||||||
|
|
||||||
|
elif self.operation_type == 'xor':
|
||||||
|
operation_tag = '^'
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f"resolve_memory_tag() not implemented for binary operation type {self.operation_type}")
|
||||||
|
|
||||||
|
first_operand_tag = self.first_operand.resolve_memory_tag(elements)
|
||||||
|
second_operand_tag = self.second_operand.resolve_memory_tag(elements)
|
||||||
|
|
||||||
|
self.memory_tag = f"{first_operand_tag} {operation_tag} {second_operand_tag}"
|
||||||
|
|
||||||
|
return self.memory_tag
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_memory_tag_dependency(self, elements):
|
||||||
|
if self.memory_tag_dependency is None:
|
||||||
|
first_operand_tag = self.first_operand.resolve_memory_tag_dependency(elements)
|
||||||
|
second_operand_tag = self.second_operand.resolve_memory_tag_dependency(elements)
|
||||||
|
self.memory_tag_dependency = [first_operand_tag, second_operand_tag]
|
||||||
|
|
||||||
|
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.first_operand.resolve_memory_address_chain(elements))
|
||||||
|
chain.append(self.second_operand.resolve_memory_address_chain(elements))
|
||||||
|
|
||||||
|
return chain
|
||||||
239
ScEpTIC/AST/elements/instructions/conversion.py
Normal file
239
ScEpTIC/AST/elements/instructions/conversion.py
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from ScEpTIC import tools
|
||||||
|
from ScEpTIC.AST.elements.instruction import Instruction
|
||||||
|
|
||||||
|
|
||||||
|
class ConversionOperation(Instruction):
|
||||||
|
"""
|
||||||
|
AST nodes for the LLVM Bitwise Instructions group
|
||||||
|
https://llvm.org/docs/LangRef.html#bitwiseops
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, conversion_type, operand, target_type, target):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.conversion_type = conversion_type
|
||||||
|
self.operand = operand
|
||||||
|
self.target_type = target_type
|
||||||
|
self.target = target
|
||||||
|
|
||||||
|
# instructions that do not correspond to any ISA instruction
|
||||||
|
if self.conversion_type in ['bitcast', 'addrspacecast', 'inttoptr', 'ptrtoint']:
|
||||||
|
self.tick_count = 0
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = super().__str__()
|
||||||
|
retstr += '{} {} to {}'.format(self.conversion_type, self.operand, self.target_type)
|
||||||
|
return retstr
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_lookup(self):
|
||||||
|
"""
|
||||||
|
Returns the input lookup data for the current operation
|
||||||
|
"""
|
||||||
|
|
||||||
|
operand = self.operand.get_input_lookup()
|
||||||
|
|
||||||
|
return tools.merge_input_lookup_data(operand, tools.build_input_lookup_data(None, None))
|
||||||
|
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
"""
|
||||||
|
Returns the value obtained from the operation.
|
||||||
|
It converts address operands (if present) to relative spaces, applies the operation and converts them back to the absolute space.
|
||||||
|
"""
|
||||||
|
|
||||||
|
operand = self.operand.get_val()
|
||||||
|
|
||||||
|
prefix = None
|
||||||
|
|
||||||
|
# if operand is an address, get prefix and relative address (which can be used in conversion)
|
||||||
|
if self._vmstate.memory._is_absolute_address(operand):
|
||||||
|
prefix, operand = self._vmstate.memory._parse_absolute_address(operand)
|
||||||
|
|
||||||
|
value = self._get_val(operand)
|
||||||
|
|
||||||
|
# if prefix is set, value is an address. If so, convert it from relative to absolute space.
|
||||||
|
if prefix is not None:
|
||||||
|
value = self._vmstate.memory._convert_to_absolute_address(prefix, value)
|
||||||
|
|
||||||
|
logging.info('[{}] Executing {} on {} with result {}'.format(self.instruction_type, self.conversion_type, operand, value))
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_uses(self):
|
||||||
|
"""
|
||||||
|
Returns a list containing the names of the registers used by this instruction.
|
||||||
|
(used by register allocation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.operand.get_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.operand.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_val(self, value):
|
||||||
|
"""
|
||||||
|
Returns the value returned from the operation, given its operands.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.conversion_type == 'addrspacecast':
|
||||||
|
# is like bitcast, but for converting the address space
|
||||||
|
return value
|
||||||
|
|
||||||
|
elif self.conversion_type == 'fpext':
|
||||||
|
value = float(value)
|
||||||
|
|
||||||
|
# converts a fp value to a larger-sized one.
|
||||||
|
# for how data is represented in the simulator, it does nothing
|
||||||
|
return value
|
||||||
|
|
||||||
|
elif self.conversion_type == 'fptosi':
|
||||||
|
# fp to signed int conversion
|
||||||
|
value = float(value)
|
||||||
|
|
||||||
|
dim = len(self.target_type)
|
||||||
|
|
||||||
|
# convert to int value
|
||||||
|
value = int(round(value))
|
||||||
|
|
||||||
|
# apply max dimension
|
||||||
|
value = self.operand.convert_sint_to_sint(value, dim)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
elif self.conversion_type == 'fptoui':
|
||||||
|
# float to unsigned int conversion
|
||||||
|
value = float(value)
|
||||||
|
|
||||||
|
dim = len(self.target_type)
|
||||||
|
|
||||||
|
# convert to int value
|
||||||
|
value = int(round(value))
|
||||||
|
|
||||||
|
# apply max dimension
|
||||||
|
value = self.operand.convert_sint_to_uint(value, dim)
|
||||||
|
|
||||||
|
# NB: elements in registers / memory are saved as SIGNED int.
|
||||||
|
# Operations that uses UNSIGNED operands, first converts the SIGNED operand to UNSIGNED one.
|
||||||
|
# (SIGNED VS UNSIGNED is just a mode of interpretation)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
elif self.conversion_type == 'fptrunc':
|
||||||
|
# converts a fp value to a smaller-sized one.
|
||||||
|
# for how data is represented in the simulator, it does nothing
|
||||||
|
value = float(value)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
elif self.conversion_type == 'bitcast':
|
||||||
|
# bitcast does not apply any conversion.
|
||||||
|
# it just tells the compiler to treat data as it was already written as "target_type"
|
||||||
|
return value
|
||||||
|
|
||||||
|
elif self.conversion_type == 'inttoptr':
|
||||||
|
# returns back the integer as memory location (address)
|
||||||
|
# for how addresses are represented, it does nothing.
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
elif self.conversion_type == 'ptrtoint':
|
||||||
|
# returns the memory address as an integer
|
||||||
|
# for how addresses are represented, it does nothing.
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
elif self.conversion_type == 'sext':
|
||||||
|
value = int(value)
|
||||||
|
|
||||||
|
initial_dim = len(self.operand)
|
||||||
|
value = self.operand.convert_sint_to_bin(value, initial_dim)
|
||||||
|
|
||||||
|
target_dim = len(self.target_type)
|
||||||
|
prefix = value[0] * (target_dim - initial_dim)
|
||||||
|
|
||||||
|
value = prefix + value
|
||||||
|
|
||||||
|
return self.operand.convert_bin_to_sint(value)
|
||||||
|
|
||||||
|
elif self.conversion_type == 'sitofp':
|
||||||
|
# convert an integer to a float
|
||||||
|
return float(value)
|
||||||
|
|
||||||
|
elif self.conversion_type == 'uitofp':
|
||||||
|
# convert an unsigned integer to a float
|
||||||
|
|
||||||
|
# convert memory cell value to unsigned int.
|
||||||
|
value = int(value)
|
||||||
|
|
||||||
|
value = self.operand.convert_sint_to_uint(value, len(self.operand))
|
||||||
|
|
||||||
|
return float(value)
|
||||||
|
|
||||||
|
elif self.conversion_type == 'trunc':
|
||||||
|
dim = len(self.operand)
|
||||||
|
value = self.operand.convert_sint_to_bin(int(value), dim)
|
||||||
|
|
||||||
|
trunc = dim-len(self.target_type)
|
||||||
|
|
||||||
|
value = self.operand.convert_bin_to_sint(value[trunc:])
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
elif self.conversion_type == 'zext':
|
||||||
|
initial_dim = len(self.operand)
|
||||||
|
|
||||||
|
value = self.operand.convert_sint_to_bin(value, initial_dim)
|
||||||
|
|
||||||
|
dim = len(self.target_type)
|
||||||
|
|
||||||
|
value = '0' * (dim - initial_dim) + value
|
||||||
|
|
||||||
|
# for how data is represented, zero expansion does nothing
|
||||||
|
return self.operand.convert_bin_to_sint(value)
|
||||||
|
|
||||||
|
# if gets there, the conversion mode is not supported.
|
||||||
|
raise NotImplementedError('{} is not supported for now!'.format(self.conversion_type))
|
||||||
|
|
||||||
|
|
||||||
|
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.operand.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.operand.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)
|
||||||
|
"""
|
||||||
|
chain = []
|
||||||
|
|
||||||
|
if self.target is not None:
|
||||||
|
chain.append(self)
|
||||||
|
|
||||||
|
chain.append(self.operand.resolve_memory_address_chain(elements))
|
||||||
|
|
||||||
|
return chain
|
||||||
588
ScEpTIC/AST/elements/instructions/memory_operations.py
Normal file
588
ScEpTIC/AST/elements/instructions/memory_operations.py
Normal file
@ -0,0 +1,588 @@
|
|||||||
|
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
|
||||||
646
ScEpTIC/AST/elements/instructions/other_operations.py
Normal file
646
ScEpTIC/AST/elements/instructions/other_operations.py
Normal file
@ -0,0 +1,646 @@
|
|||||||
|
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.')
|
||||||
208
ScEpTIC/AST/elements/instructions/termination_instructions.py
Normal file
208
ScEpTIC/AST/elements/instructions/termination_instructions.py
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from ScEpTIC.AST.elements.instruction import Instruction
|
||||||
|
from ScEpTIC.AST.misc.virtual_memory_enum import VirtualMemoryEnum
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnOperation(Instruction):
|
||||||
|
"""
|
||||||
|
AST node of the LLVM Termination Instructions group - Return Instruction
|
||||||
|
https://llvm.org/docs/LangRef.html#terminators
|
||||||
|
"""
|
||||||
|
|
||||||
|
# esp = ebp
|
||||||
|
# pop ebp
|
||||||
|
# ebp = ebp_pop
|
||||||
|
# pop pc
|
||||||
|
# pc = pc_pop
|
||||||
|
# store retval / place into a reg
|
||||||
|
tick_count = 6
|
||||||
|
memory_tick_count = 2
|
||||||
|
n_memory_instructions = 2
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.value = value
|
||||||
|
self.update_pc = True
|
||||||
|
|
||||||
|
self.virtual_memory_target = VirtualMemoryEnum.VOLATILE
|
||||||
|
|
||||||
|
# Custom callback used for ISR and other functionalities
|
||||||
|
self.custom_callback = lambda: None
|
||||||
|
self.is_isr_return = False
|
||||||
|
self.isr_call = None
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = super().__str__()
|
||||||
|
retstr += 'return {}'.format(self.value)
|
||||||
|
retstr += f" (${self.virtual_memory_target.value})"
|
||||||
|
return retstr
|
||||||
|
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
"""
|
||||||
|
Retrieves the value of the return operation and calls the return's callback.
|
||||||
|
(Update program counter ignored -> always True)
|
||||||
|
"""
|
||||||
|
value = self.value.get_val()
|
||||||
|
input_lookup = self.value.get_input_lookup()
|
||||||
|
|
||||||
|
logging.info('[{}] Returning value {}.'.format(self.instruction_type, value))
|
||||||
|
|
||||||
|
self._vmstate.on_function_return(value, input_lookup, self.tick_count, self.update_pc)
|
||||||
|
|
||||||
|
# run custom callback
|
||||||
|
self.custom_callback()
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class BranchOperation(Instruction):
|
||||||
|
"""
|
||||||
|
AST node of the LLVM Termination Instructions group - Branch Instruction
|
||||||
|
https://llvm.org/docs/LangRef.html#terminators
|
||||||
|
"""
|
||||||
|
|
||||||
|
# if condition is None: target = target_true directly
|
||||||
|
def __init__(self, condition, target_true, target_false):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.condition = condition
|
||||||
|
self.target_true = target_true
|
||||||
|
self.target_false = target_false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = super().__str__()
|
||||||
|
retstr += 'branch {} {} {}{}'.format(self.condition, self.target_true, self.target_false, ' [Useless]' if self.tick_count == 0 else '')
|
||||||
|
return retstr
|
||||||
|
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
"""
|
||||||
|
Executes the branch operation.
|
||||||
|
(Update program counter ignored -> always True)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# if condition is none, is an unconditional branch
|
||||||
|
if self.condition is None:
|
||||||
|
target = self.target_true
|
||||||
|
else:
|
||||||
|
# evaluates the condition
|
||||||
|
value = self.condition.get_val()
|
||||||
|
if int(value) == 1:
|
||||||
|
target = self.target_true
|
||||||
|
else:
|
||||||
|
target = self.target_false
|
||||||
|
|
||||||
|
# performs callback call
|
||||||
|
self._vmstate.on_branch(target, self.basic_block_id, self.tick_count)
|
||||||
|
|
||||||
|
logging.info('[{}] Branching to {} (condition is {}).'.format(self.instruction_type, target, True if self.condition is None else value))
|
||||||
|
|
||||||
|
|
||||||
|
def get_uses(self):
|
||||||
|
"""
|
||||||
|
Returns a list containing the names of the registers used by this instruction.
|
||||||
|
(used by register allocation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.condition is not None:
|
||||||
|
return self.condition.get_uses()
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.condition is not None:
|
||||||
|
self.condition.replace_reg_name(old_reg_name, new_reg_name)
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchOperation(Instruction):
|
||||||
|
"""
|
||||||
|
AST node of the LLVM Termination Instructions group - Switch Instruction
|
||||||
|
https://llvm.org/docs/LangRef.html#terminators
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, element, default_label, switch_pairs):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.element = element
|
||||||
|
self.default_label = default_label
|
||||||
|
# switch_pairs is a list of Value with additional label attribute
|
||||||
|
self.switch_pairs = switch_pairs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = super().__str__()
|
||||||
|
retstr += 'switch {}; default: {}; {}'.format(self.element, self.default_label, self.switch_pairs)
|
||||||
|
return retstr
|
||||||
|
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
"""
|
||||||
|
Executes the switch operation.
|
||||||
|
(Update program counter ignored -> always True)
|
||||||
|
"""
|
||||||
|
|
||||||
|
target_value = self.element.get_val()
|
||||||
|
label = None
|
||||||
|
|
||||||
|
for value in self.switch_pairs:
|
||||||
|
val = value.get_val()
|
||||||
|
|
||||||
|
# if value found, set label
|
||||||
|
if val == target_value:
|
||||||
|
label = value.label
|
||||||
|
break
|
||||||
|
|
||||||
|
# if label not set, use default_label
|
||||||
|
if label is None:
|
||||||
|
label = self.default_label
|
||||||
|
|
||||||
|
# performs callback call
|
||||||
|
self._vmstate.on_branch(label, self.basic_block_id, self.tick_count)
|
||||||
|
|
||||||
|
logging.info('[{}] Branching to {}.'.format(self.instruction_type, label))
|
||||||
|
|
||||||
|
|
||||||
|
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 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)
|
||||||
120
ScEpTIC/AST/elements/metadata.py
Normal file
120
ScEpTIC/AST/elements/metadata.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
class Metadata:
|
||||||
|
"""
|
||||||
|
AST node for LLVM metadata
|
||||||
|
"""
|
||||||
|
|
||||||
|
elements = {}
|
||||||
|
|
||||||
|
def __init__(self, name, metadata_type, metadata_function_name, values, includes):
|
||||||
|
self.name = name
|
||||||
|
self.type = metadata_type
|
||||||
|
self.type_name = metadata_function_name
|
||||||
|
# values can be a dictionary or a list. each element can be a string, a reference to other metadata or a Metadata object.
|
||||||
|
self.values = values
|
||||||
|
self.includes = includes
|
||||||
|
|
||||||
|
if self.name is not None:
|
||||||
|
self.elements[self.name] = self
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{} = {}(includes: {}; values: {})'.format(self.name, self.type_name, self.includes, self.values)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fancy_format(metadata):
|
||||||
|
"""
|
||||||
|
Formats the metadata and returns it
|
||||||
|
"""
|
||||||
|
|
||||||
|
retval = ''
|
||||||
|
|
||||||
|
if 'line' in metadata:
|
||||||
|
retval += 'Line: {}; '.format(metadata['line'])
|
||||||
|
|
||||||
|
if 'column' in metadata:
|
||||||
|
retval += 'Column: {}; '.format(metadata['column'])
|
||||||
|
|
||||||
|
if 'variable_name' in metadata:
|
||||||
|
retval += 'Variable name: {}; '.format(metadata['variable_name'])
|
||||||
|
|
||||||
|
if 'subprogram_name' in metadata:
|
||||||
|
retval += 'Function name: {}; '.format(metadata['subprogram_name'])
|
||||||
|
|
||||||
|
if 'file' in metadata:
|
||||||
|
retval += 'File: {}; '.format(metadata['file'])
|
||||||
|
|
||||||
|
if retval[-2:] == '; ':
|
||||||
|
retval = retval[:-2]
|
||||||
|
|
||||||
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve(self):
|
||||||
|
"""
|
||||||
|
Resolve metadata tree and return only relevant metadata information
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(self.values) == 0 and len(self.includes) == 0:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if self.type_name == 'DIFile':
|
||||||
|
return {'file': '{}/{}'.format(self.values['directory'], self.values['filename'])}
|
||||||
|
|
||||||
|
if self.type_name == 'DILocation':
|
||||||
|
# retrieve scope
|
||||||
|
retrived_vals = self.elements[self.values['scope']].retrieve()
|
||||||
|
|
||||||
|
return {**retrived_vals, **{'line': self.values['line'], 'column': self.values['column']}}
|
||||||
|
|
||||||
|
if self.type_name == 'DISubprogram':
|
||||||
|
# get file name
|
||||||
|
file = self.elements[self.values['file']].retrieve()
|
||||||
|
|
||||||
|
return {**file, **{'subprogram_name': self.values['name']}}
|
||||||
|
|
||||||
|
if self.type_name == 'DILexicalBlock':
|
||||||
|
# retrieve scope
|
||||||
|
retrived_vals = self.elements[self.values['scope']].retrieve()
|
||||||
|
|
||||||
|
if 'file' not in retrived_vals:
|
||||||
|
retrived_vals['file'] = self.elements[self.values['file']].retrieve()['file']
|
||||||
|
|
||||||
|
return retrived_vals
|
||||||
|
|
||||||
|
if self.type_name == 'DILocalVariable':
|
||||||
|
# retrieve scope
|
||||||
|
retrived_vals = self.elements[self.values['scope']].retrieve()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return {**retrived_vals, **{'variable_name': self.values['name'], 'line': self.values['line'], 'column': self.values['column']}}
|
||||||
|
except Exception:
|
||||||
|
return {**retrived_vals, **{'variable_name': self.values['name'], 'line': self.values['line']}}
|
||||||
|
|
||||||
|
location = None
|
||||||
|
local_var = None
|
||||||
|
|
||||||
|
# find the element that contains the relevant metadata informations.
|
||||||
|
for i in self.includes:
|
||||||
|
|
||||||
|
# empty DIExpression()
|
||||||
|
if i == 'empty':
|
||||||
|
#return None
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.elements[i].type_name == 'DILocation':
|
||||||
|
location = i
|
||||||
|
if self.elements[i].type_name == 'DILocalVariable':
|
||||||
|
local_var = i
|
||||||
|
|
||||||
|
# Local Variable has higher priority (contains more information)
|
||||||
|
# NB: is only for local variable declarations
|
||||||
|
if local_var is not None:
|
||||||
|
return self.elements[local_var].retrieve()
|
||||||
|
|
||||||
|
# If the metadata is not associated with local variable
|
||||||
|
# or DILocalVariable not found, use DILocation
|
||||||
|
if location is not None:
|
||||||
|
return self.elements[location].retrieve()
|
||||||
|
|
||||||
|
return None
|
||||||
330
ScEpTIC/AST/elements/types.py
Normal file
330
ScEpTIC/AST/elements/types.py
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
|
||||||
|
class BaseType:
|
||||||
|
"""
|
||||||
|
AST node of LLVM Base Type
|
||||||
|
NOTE: enum data type doesn't appear because it is directly resolved and converted in int by the llvm front-end.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, base_type, bits):
|
||||||
|
self.type = base_type
|
||||||
|
self.bits = int(bits)
|
||||||
|
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.bits
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{} {}bit'.format(self.type, self.bits)
|
||||||
|
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'BaseType({}, {})'.format(self.type, self.bits)
|
||||||
|
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, BaseType):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.type == other.type and self.bits == other.bits
|
||||||
|
|
||||||
|
|
||||||
|
class Type:
|
||||||
|
"""
|
||||||
|
AST node representing a LLVM generic type (Array, Vector, Pointer, Custom Type)
|
||||||
|
"""
|
||||||
|
|
||||||
|
address_dimension = 0
|
||||||
|
|
||||||
|
def __init__(self, is_pointer, pointer_level, is_array, array_composition, is_vector, vector_dimension, is_base_type, custom_type_name, is_ct_defined, custom_type_def, base_type):
|
||||||
|
self.is_pointer = is_pointer
|
||||||
|
self.pointer_level = int(pointer_level)
|
||||||
|
|
||||||
|
self.is_array = is_array
|
||||||
|
self._set_array_composition(array_composition)
|
||||||
|
|
||||||
|
self.is_vector = is_vector
|
||||||
|
self.vector_dimension = int(vector_dimension)
|
||||||
|
|
||||||
|
self.is_base_type = is_base_type
|
||||||
|
self.custom_type_name = custom_type_name
|
||||||
|
|
||||||
|
self.is_ct_defined = is_ct_defined
|
||||||
|
self.custom_type_def = custom_type_def
|
||||||
|
|
||||||
|
self.base_type = base_type
|
||||||
|
|
||||||
|
|
||||||
|
def _set_array_composition(self, array_composition):
|
||||||
|
"""
|
||||||
|
Sets the array composition of the type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.array_composition = []
|
||||||
|
|
||||||
|
for elem in array_composition:
|
||||||
|
elem = int(elem)
|
||||||
|
self.array_composition.append(elem)
|
||||||
|
|
||||||
|
self.array_dimensions = len(self.array_composition)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_array_composition(self, element_dimension):
|
||||||
|
"""
|
||||||
|
Returns the overall array cells number.
|
||||||
|
"""
|
||||||
|
|
||||||
|
array_composition = self.array_composition.copy()
|
||||||
|
array_composition.reverse()
|
||||||
|
|
||||||
|
dim = element_dimension
|
||||||
|
|
||||||
|
for i in array_composition:
|
||||||
|
dim = [i, dim]
|
||||||
|
|
||||||
|
return dim
|
||||||
|
|
||||||
|
|
||||||
|
def get_memory_composition(self, flat_composition = False):
|
||||||
|
"""
|
||||||
|
Returns the memory composition of the Type, which is a list [a, b]
|
||||||
|
with a representing the number of elements and b representing the size of each element.
|
||||||
|
b can be a list of memory composition in case the Type refers to a customtype.
|
||||||
|
"""
|
||||||
|
|
||||||
|
element_dimension = 0
|
||||||
|
|
||||||
|
# set dimension
|
||||||
|
if self.is_pointer:
|
||||||
|
element_dimension = self.address_dimension
|
||||||
|
|
||||||
|
elif self.is_base_type:
|
||||||
|
element_dimension = len(self.base_type)
|
||||||
|
|
||||||
|
elif self.is_ct_defined:
|
||||||
|
raise NotImplementedError('Type not supported')
|
||||||
|
|
||||||
|
else:
|
||||||
|
element_dimension = CustomType.elements[self.custom_type_name].get_memory_composition()
|
||||||
|
|
||||||
|
# is is array, create proper composition
|
||||||
|
if self.is_array:
|
||||||
|
composition = self._get_array_composition(element_dimension)
|
||||||
|
|
||||||
|
elif self.is_vector:
|
||||||
|
elements_number = self.vector_dimension
|
||||||
|
composition = [elements_number, element_dimension]
|
||||||
|
elif self.is_base_type:
|
||||||
|
composition = [1, element_dimension]
|
||||||
|
else:
|
||||||
|
composition = element_dimension
|
||||||
|
|
||||||
|
if flat_composition:
|
||||||
|
flat = []
|
||||||
|
self.flat_composition(composition, flat)
|
||||||
|
return flat
|
||||||
|
|
||||||
|
return composition
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def flat_composition(composition, lst):
|
||||||
|
"""
|
||||||
|
flatterns a memory composition, turning it into a single list of dimensions
|
||||||
|
array: [2, 32] -> [32, 32]
|
||||||
|
array of struct: [3, [[3, 8], [1, 32]]] -> [8, 8, 8, 32, 8, 8, 8, 32, 8, 8, 8, 32]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If composition is an integer -> recursion ended
|
||||||
|
if isinstance(composition, int):
|
||||||
|
lst.append(composition)
|
||||||
|
return
|
||||||
|
|
||||||
|
elif isinstance(composition, list):
|
||||||
|
|
||||||
|
# Empty list
|
||||||
|
if len(composition) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(composition) == 1:
|
||||||
|
composition = composition[0]
|
||||||
|
|
||||||
|
n_elements = composition[0]
|
||||||
|
elements_size = composition[1]
|
||||||
|
|
||||||
|
# n_elements is an int -> array (consider the element_size now)
|
||||||
|
if isinstance(n_elements, int):
|
||||||
|
for _ in range(0, n_elements):
|
||||||
|
Type.flat_composition(elements_size, lst)
|
||||||
|
|
||||||
|
# n_elements is a list -> struct (consider the whole composition now)
|
||||||
|
elif isinstance(n_elements, list):
|
||||||
|
for sub_comp in composition:
|
||||||
|
Type.flat_composition(sub_comp, lst)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Type.flat_composition() error 1')
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Type.flat_composition() error 2')
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def empty(cls):
|
||||||
|
"""
|
||||||
|
Creates an empty type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
is_pointer = False
|
||||||
|
pointer_level = 0
|
||||||
|
|
||||||
|
is_array = False
|
||||||
|
array_composition = []
|
||||||
|
|
||||||
|
is_vector = False
|
||||||
|
vector_dimension = 0
|
||||||
|
|
||||||
|
is_base_type = True
|
||||||
|
custom_type_name = None
|
||||||
|
|
||||||
|
is_ct_defined = False
|
||||||
|
custom_type_def = None
|
||||||
|
|
||||||
|
base_type = None
|
||||||
|
|
||||||
|
return cls(is_pointer, pointer_level, is_array, array_composition, is_vector, vector_dimension, is_base_type, custom_type_name, is_ct_defined, custom_type_def, base_type)
|
||||||
|
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the size in bits of the type
|
||||||
|
"""
|
||||||
|
|
||||||
|
len_multiplier = 1
|
||||||
|
|
||||||
|
if self.is_vector:
|
||||||
|
len_multiplier = self.vector_dimension
|
||||||
|
|
||||||
|
if self.is_array:
|
||||||
|
for i in self.array_composition:
|
||||||
|
len_multiplier = len_multiplier * i
|
||||||
|
|
||||||
|
if self.is_pointer:
|
||||||
|
return len_multiplier * self.address_dimension
|
||||||
|
|
||||||
|
elif self.is_base_type:
|
||||||
|
return len_multiplier * len(self.base_type)
|
||||||
|
|
||||||
|
elif self.is_ct_defined:
|
||||||
|
tmp_len = 0
|
||||||
|
for elem in self.custom_type_def:
|
||||||
|
tmp_len = tmp_len + len(elem)
|
||||||
|
|
||||||
|
return len_multiplier * tmp_len
|
||||||
|
|
||||||
|
else:
|
||||||
|
if self.custom_type_name not in CustomType.elements:
|
||||||
|
raise ValueError('Custom type {} not found!'.format(self.custom_type_name))
|
||||||
|
return len_multiplier * len(CustomType.elements[self.custom_type_name])
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
if self.is_base_type:
|
||||||
|
retstr = str(self.base_type)+'*'*self.pointer_level
|
||||||
|
|
||||||
|
elif self.is_ct_defined:
|
||||||
|
retstr = 'CustomType: {'
|
||||||
|
|
||||||
|
length = len(self.custom_type_def)-1
|
||||||
|
|
||||||
|
for i in range(0, length+1):
|
||||||
|
retstr += '{}'.format(self.custom_type_def[i])
|
||||||
|
|
||||||
|
if i < length:
|
||||||
|
retstr += ', '
|
||||||
|
|
||||||
|
retstr += '}'
|
||||||
|
|
||||||
|
else:
|
||||||
|
retstr = str(self.custom_type_name)
|
||||||
|
|
||||||
|
if self.is_vector:
|
||||||
|
retstr = '<{} x {}>'.format(self.vector_dimension, retstr)
|
||||||
|
|
||||||
|
if self.is_array:
|
||||||
|
retstr = '({})'.format(retstr)
|
||||||
|
|
||||||
|
for i in self.array_composition:
|
||||||
|
retstr = '{}[{}]'.format(retstr, i)
|
||||||
|
|
||||||
|
return retstr
|
||||||
|
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomType:
|
||||||
|
"""
|
||||||
|
AST node representing a LLVM custom type
|
||||||
|
"""
|
||||||
|
|
||||||
|
elements = {}
|
||||||
|
|
||||||
|
def __init__(self, fullname, ct_type, name, type_composition):
|
||||||
|
self.fullname = fullname
|
||||||
|
self.type = ct_type
|
||||||
|
self.name = name
|
||||||
|
self._set_type_composition(type_composition)
|
||||||
|
self.elements[self.fullname] = self
|
||||||
|
|
||||||
|
|
||||||
|
def _set_type_composition(self, type_composition):
|
||||||
|
"""
|
||||||
|
Sets the type composition of the custom type
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.type_composition = []
|
||||||
|
|
||||||
|
for elem in type_composition:
|
||||||
|
self.type_composition.append(elem)
|
||||||
|
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the size in bits of the customtype
|
||||||
|
"""
|
||||||
|
|
||||||
|
total_len = 0
|
||||||
|
|
||||||
|
for elem in self.type_composition:
|
||||||
|
total_len = total_len + len(elem)
|
||||||
|
|
||||||
|
return total_len
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = ''
|
||||||
|
|
||||||
|
length = len(self.type_composition)-1
|
||||||
|
|
||||||
|
for i in range(0, length+1):
|
||||||
|
retstr += '{}'.format(self.type_composition[i])
|
||||||
|
|
||||||
|
if i < length:
|
||||||
|
retstr += ', '
|
||||||
|
|
||||||
|
# two {{ }} for printing single { }
|
||||||
|
# middle {} to print retstr
|
||||||
|
return '{}: {{{}}}'.format(self.fullname, retstr)
|
||||||
|
|
||||||
|
|
||||||
|
def get_memory_composition(self):
|
||||||
|
"""
|
||||||
|
Returns a list of lists, in which each element is a memory composition of the represented type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
elements = []
|
||||||
|
for elem in self.type_composition:
|
||||||
|
elements.append(elem.get_memory_composition())
|
||||||
|
|
||||||
|
return elements
|
||||||
365
ScEpTIC/AST/elements/value.py
Normal file
365
ScEpTIC/AST/elements/value.py
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
import logging
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from ScEpTIC import tools
|
||||||
|
from ScEpTIC.exceptions import MemoryException
|
||||||
|
|
||||||
|
|
||||||
|
class Value:
|
||||||
|
"""
|
||||||
|
AST node for data representation (immediate, register data, etc)
|
||||||
|
"""
|
||||||
|
|
||||||
|
_vmstate = None
|
||||||
|
|
||||||
|
def __init__(self, value_class, operand_value, value_type):
|
||||||
|
self.value_class = value_class
|
||||||
|
self.value = operand_value
|
||||||
|
self.type = value_type
|
||||||
|
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Value({}, {}, {})'.format(self.value_class, self.value, self.type)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.type is None:
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
return 'Value: ({}) {}'.format(self.type, self.value)
|
||||||
|
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.type)
|
||||||
|
|
||||||
|
def is_llvm_string(self):
|
||||||
|
try:
|
||||||
|
return self.value_class == 'address' and '@.str' in self.value.element.value
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
"""
|
||||||
|
Retrieve and returns the value represented by the object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._vmstate is None:
|
||||||
|
raise MemoryException('VM Status not initialized!')
|
||||||
|
|
||||||
|
if self.value_class == 'immediate':
|
||||||
|
value = self._resolve_immediate()
|
||||||
|
|
||||||
|
elif self.value_class == 'array_val':
|
||||||
|
value = self._resolve_array_val()
|
||||||
|
|
||||||
|
elif self.value_class == 'struct_val':
|
||||||
|
value = self._resolve_array_val()
|
||||||
|
|
||||||
|
elif self.value_class == 'virtual_reg':
|
||||||
|
value = self._vmstate.register_file.read(self.value)
|
||||||
|
|
||||||
|
elif self.value_class == 'global_var':
|
||||||
|
# Function pointer -> same initial token of global_vars
|
||||||
|
# If function exists -> return value
|
||||||
|
if self.value in self._vmstate.functions:
|
||||||
|
value = self.value
|
||||||
|
else:
|
||||||
|
value = self._vmstate.memory.gst.get_symbol_address(self.value)
|
||||||
|
|
||||||
|
elif self.value_class == 'vector':
|
||||||
|
raise NotImplementedError('Vector operations not supported at the moment. Please compile with a different target.')
|
||||||
|
|
||||||
|
elif self.value_class == 'address':
|
||||||
|
# GetElementPointerOperation has method get_val that resolves the correct value
|
||||||
|
value = self.value.get_val()
|
||||||
|
|
||||||
|
elif self.value_class == 'conversion':
|
||||||
|
# ConversionOperation has method get_val that resolves the correct value
|
||||||
|
value = self.value.get_val()
|
||||||
|
|
||||||
|
elif self.value_class == 'metadata':
|
||||||
|
# appears only in @llvm.dbg functions, so no need to resolve that address
|
||||||
|
raise ValueError('Metadata should not be present in running functions.')
|
||||||
|
|
||||||
|
logging.info('[Value] Resolved value of {} with {}.'.format(self.value, value))
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_memory_tag(self, elements):
|
||||||
|
"""
|
||||||
|
Resolves and returns the memory tag of the targeted element
|
||||||
|
"""
|
||||||
|
if self.value_class == 'immediate':
|
||||||
|
return self._resolve_immediate()
|
||||||
|
|
||||||
|
elif self.value_class == 'virtual_reg':
|
||||||
|
return elements[self.value].resolve_memory_tag(elements)
|
||||||
|
|
||||||
|
elif self.value_class == 'global_var':
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
elif self.value_class == 'address':
|
||||||
|
return self.value.resolve_memory_tag(elements)
|
||||||
|
|
||||||
|
elif self.value_class == 'conversion':
|
||||||
|
return self.value.resolve_memory_tag(elements)
|
||||||
|
|
||||||
|
raise NotImplementedError(f"resolve_memory_tag() not implemented for value class {self.value_class}")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_memory_tag_dependency(self, elements):
|
||||||
|
if self.value_class == 'immediate':
|
||||||
|
return []
|
||||||
|
|
||||||
|
elif self.value_class == 'virtual_reg':
|
||||||
|
return elements[self.value].resolve_memory_tag_dependency(elements)
|
||||||
|
|
||||||
|
elif self.value_class == 'global_var':
|
||||||
|
return [self.value]
|
||||||
|
|
||||||
|
elif self.value_class == 'address':
|
||||||
|
return self.value.resolve_memory_tag_dependency(elements)
|
||||||
|
|
||||||
|
elif self.value_class == 'conversion':
|
||||||
|
return self.value.resolve_memory_tag_dependency(elements)
|
||||||
|
|
||||||
|
raise NotImplementedError(f"resolve_memory_tag() not implemented for value class {self.value_class}")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_memory_address_chain(self, elements):
|
||||||
|
"""
|
||||||
|
Returns a list of all the instructions required to get the address of the targeted element(s)
|
||||||
|
"""
|
||||||
|
if self.value_class == 'immediate':
|
||||||
|
return []
|
||||||
|
|
||||||
|
elif self.value_class == 'virtual_reg':
|
||||||
|
return [elements[self.value].resolve_memory_address_chain(elements)]
|
||||||
|
|
||||||
|
elif self.value_class == 'global_var':
|
||||||
|
return []
|
||||||
|
|
||||||
|
elif self.value_class == 'address':
|
||||||
|
return [self.value.resolve_memory_address_chain(elements)]
|
||||||
|
|
||||||
|
elif self.value_class == 'conversion':
|
||||||
|
return [self.value.resolve_memory_address_chain(elements)]
|
||||||
|
|
||||||
|
raise NotImplementedError(f"resolve_memory_address_chain() not implemented for value class {self.value_class}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_lookup(self):
|
||||||
|
"""
|
||||||
|
Returns the input lookup information for the current Value object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._vmstate.input_lookup_enabled:
|
||||||
|
if self.value_class == 'virtual_reg':
|
||||||
|
return self._vmstate.register_file.get_input_lookup(self.value)
|
||||||
|
|
||||||
|
elif self.value_class == 'address':
|
||||||
|
return self.value.get_input_lookup()
|
||||||
|
|
||||||
|
elif self.value_class == 'conversion':
|
||||||
|
return self.value.get_input_lookup()
|
||||||
|
|
||||||
|
return tools.build_input_lookup_data(None, None)
|
||||||
|
|
||||||
|
|
||||||
|
def get_uses(self):
|
||||||
|
"""
|
||||||
|
Returns a list containing the names of the registers used by this value.
|
||||||
|
(used by register allocation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.value_class == 'virtual_reg':
|
||||||
|
return [self.value]
|
||||||
|
|
||||||
|
elif self.value_class == 'address':
|
||||||
|
return self.value.get_uses()
|
||||||
|
|
||||||
|
elif self.value_class == 'conversion':
|
||||||
|
return self.value.get_uses()
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_memory_uses(self):
|
||||||
|
if self.value_class == 'global_var':
|
||||||
|
return [self.value]
|
||||||
|
|
||||||
|
|
||||||
|
def get_defs(self):
|
||||||
|
"""
|
||||||
|
Returns a list of registers defined by this instruction.
|
||||||
|
(used by register allocation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def replace_reg_name(self, old_reg_name, new_reg_name):
|
||||||
|
"""
|
||||||
|
Replaces the name of a register contained in this Value object.
|
||||||
|
(used by register allocation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.value_class == 'virtual_reg':
|
||||||
|
if self.value == old_reg_name:
|
||||||
|
self.value = new_reg_name
|
||||||
|
|
||||||
|
elif self.value_class == 'address':
|
||||||
|
self.value.replace_reg_name(old_reg_name, new_reg_name)
|
||||||
|
|
||||||
|
elif self.value_class == 'conversion':
|
||||||
|
self.value.replace_reg_name(old_reg_name, new_reg_name)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_immediate(self):
|
||||||
|
"""
|
||||||
|
Resolves the value of a immediate.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.value == 'null':
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if self.value == 'true':
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if self.value == 'false':
|
||||||
|
return 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
return int(self.value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
return float(self.value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 'e+' in self.value:
|
||||||
|
return float(self.value)
|
||||||
|
|
||||||
|
if '0x' in self.value:
|
||||||
|
# self.value[2:] to remove 0x
|
||||||
|
# each value is converted as a double from llvm, even if is a float
|
||||||
|
|
||||||
|
# NB: target architecture could be in little endian (e in target_datalayout)
|
||||||
|
# or big endian (E in target_datalayout). The conversion in the llvm ir stays the same,
|
||||||
|
# independently of the type of endianness used by the backend compiler.
|
||||||
|
# So the endianness conversion stays ! (network - big-endian)
|
||||||
|
return struct.unpack('!d', bytes.fromhex(self.value[2:]))[0]
|
||||||
|
|
||||||
|
if self.value == 'zeroinitializer':
|
||||||
|
composition = self.type.get_memory_composition(True)
|
||||||
|
|
||||||
|
for i in range(0, len(composition)):
|
||||||
|
composition[i] = 0
|
||||||
|
|
||||||
|
return composition
|
||||||
|
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_array_val(self):
|
||||||
|
"""
|
||||||
|
Resolves the value of array initialization.
|
||||||
|
"""
|
||||||
|
|
||||||
|
flat_values = []
|
||||||
|
tools.inf_depth_lst_flat(self.value, flat_values)
|
||||||
|
|
||||||
|
for i in range(0, len(flat_values)):
|
||||||
|
flat_values[i] = flat_values[i].get_val()
|
||||||
|
|
||||||
|
return flat_values
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_sint_to_bin(val, bits):
|
||||||
|
"""
|
||||||
|
Converts a signed integer to its binary representation, using a certain number of bits
|
||||||
|
"""
|
||||||
|
|
||||||
|
format_str = '{:0'+str(bits)+'b}'
|
||||||
|
|
||||||
|
if val < 0:
|
||||||
|
# all 1s
|
||||||
|
mask = int('1' * bits, 2)
|
||||||
|
val = format_str.format(val & mask)
|
||||||
|
else:
|
||||||
|
val = format_str.format(val)
|
||||||
|
|
||||||
|
# return correct bit dimension
|
||||||
|
return val[-bits:]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_uint_to_bin(val, bits):
|
||||||
|
"""
|
||||||
|
Converts an unsigned integer to its binary representation.
|
||||||
|
For how data is represented, it is the same as converting a signed integer to binary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return Value.convert_sint_to_bin(val, bits)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_bin_to_uint(val):
|
||||||
|
"""
|
||||||
|
Converts a number in binary format to its equivalent unsigned decimal.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return int(val, 2)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_bin_to_sint(val):
|
||||||
|
"""
|
||||||
|
Converts a number in binary format to its equivalent signed decimal.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if val[0] == '0':
|
||||||
|
return int(val, 2)
|
||||||
|
|
||||||
|
if len(val) == 1:
|
||||||
|
return int(val, 2)
|
||||||
|
|
||||||
|
return int(val[1:], 2) - (2 ** (len(val) - 1))
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_sint_to_uint(val, bits):
|
||||||
|
"""
|
||||||
|
Converts a number in signed integer form to its equivalent unsigned integer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
val = Value.convert_sint_to_bin(val, bits)
|
||||||
|
return Value.convert_bin_to_uint(val)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_uint_to_sint(val, bits):
|
||||||
|
"""
|
||||||
|
Converts a number in unsigned integer form to its equivalent signed integer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
val = Value.convert_uint_to_bin(val, bits)
|
||||||
|
return Value.convert_bin_to_sint(val)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_sint_to_sint(val, bits):
|
||||||
|
"""
|
||||||
|
Convert a signed integer to a signed integer, using a maximum number of bits.
|
||||||
|
"""
|
||||||
|
|
||||||
|
val = Value.convert_sint_to_bin(val, bits)
|
||||||
|
return Value.convert_bin_to_sint(val)
|
||||||
0
ScEpTIC/AST/misc/__init__.py
Normal file
0
ScEpTIC/AST/misc/__init__.py
Normal file
BIN
ScEpTIC/AST/misc/__pycache__/__init__.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/misc/__pycache__/__init__.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/misc/__pycache__/virtual_memory_enum.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/misc/__pycache__/virtual_memory_enum.pypy310.pyc
Normal file
Binary file not shown.
5
ScEpTIC/AST/misc/virtual_memory_enum.py
Normal file
5
ScEpTIC/AST/misc/virtual_memory_enum.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class VirtualMemoryEnum(Enum):
|
||||||
|
VOLATILE = 'VM'
|
||||||
|
NON_VOLATILE = 'NVM'
|
||||||
17
ScEpTIC/AST/register_allocation/__init__.py
Normal file
17
ScEpTIC/AST/register_allocation/__init__.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
def get_register_allocator(config):
|
||||||
|
"""
|
||||||
|
Function that dynamically loads the requested register allocation module and returns its main function,
|
||||||
|
to be used to perform it.
|
||||||
|
"""
|
||||||
|
logging.debug('[RegisterAllocation] from {}.{} import {}'.format(config.module_location, config.module_name, config.allocation_function_name))
|
||||||
|
|
||||||
|
# set absolute module name
|
||||||
|
module_name = '{}.{}'.format(config.module_location, config.module_name)
|
||||||
|
|
||||||
|
# import the corresponding module
|
||||||
|
module = __import__(module_name, fromlist=[config.allocation_function_name])
|
||||||
|
|
||||||
|
# return requested function
|
||||||
|
return getattr(module, config.allocation_function_name)
|
||||||
BIN
ScEpTIC/AST/register_allocation/__pycache__/__init__.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/register_allocation/__pycache__/__init__.pypy310.pyc
Normal file
Binary file not shown.
96
ScEpTIC/AST/register_allocation/linear_scan/__init__.py
Normal file
96
ScEpTIC/AST/register_allocation/linear_scan/__init__.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import copy
|
||||||
|
|
||||||
|
from ScEpTIC.AST.register_allocation.linear_scan.linear_scan import LinearScanRegisterAllocator
|
||||||
|
|
||||||
|
def allocate_registers(functions, registers_number, config):
|
||||||
|
"""
|
||||||
|
This functions performs the register allocation on the whole code (analysis, pre-processing, register allocation, and post-processing)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# perform register allocation on each function
|
||||||
|
for function_name in functions:
|
||||||
|
func = functions[function_name]
|
||||||
|
|
||||||
|
# set the register allocator and run it
|
||||||
|
func.register_allocator = LinearScanRegisterAllocator(func, registers_number, config.reg_prefix, config.spill_prefix, config.spill_type)
|
||||||
|
func.register_allocator.run_register_allocation()
|
||||||
|
|
||||||
|
# analyze register usage, to support call_post_processing
|
||||||
|
compute_registers_usage(functions, registers_number)
|
||||||
|
|
||||||
|
# do post-processing of functions calls. (insert register saving/restoring operations and set their tick_count)
|
||||||
|
for function_name in functions:
|
||||||
|
func = functions[function_name]
|
||||||
|
func.register_allocator.do_call_post_processing()
|
||||||
|
|
||||||
|
|
||||||
|
def compute_registers_usage(functions, registers_number):
|
||||||
|
"""
|
||||||
|
This function estimates the maximum register usage of each code's function and is used to
|
||||||
|
estimate the actual number of operation performed for saving and restoring registers before
|
||||||
|
a function call.
|
||||||
|
Register allocation must be performed before running this function, so to have the number of registers used.
|
||||||
|
|
||||||
|
A good partitioned register allocation will make registers from frequent functions call to not overlap, so to minimize
|
||||||
|
the register saving operations before a function call.
|
||||||
|
|
||||||
|
To estimate this scenario, is sufficient to find the number of registers used by each function (including the ones from the
|
||||||
|
calls present in such function).
|
||||||
|
|
||||||
|
The number of registers needed by a function is computed as:
|
||||||
|
max(registers_used_by_internal_calls) + registers_used_by_function
|
||||||
|
NB: recursive calls are ignored, since they will save the number of registers used by the current function.
|
||||||
|
|
||||||
|
If this number is lower than the number of available registers, no saving is needed before the call.
|
||||||
|
|
||||||
|
The overall number of register usage is calculated iteratively, since cycles can be possible (a calls b; b calls a;)
|
||||||
|
If the number of needed registers exceed the number of available ones, a cycle-dependecy is found and thus all registers should be
|
||||||
|
saved before such call.
|
||||||
|
|
||||||
|
Initial setup: each function uses reg_count as max_reg_usage, calls is initialized with the functions called
|
||||||
|
|
||||||
|
Iterate:
|
||||||
|
find the max_reg_usage among the functions in calls
|
||||||
|
set max_reg_usage = max_reg_usage of function calls + reg_count
|
||||||
|
if max_reg_usage > max_available_registers -> max_reg_usage = max_available_registers
|
||||||
|
|
||||||
|
until a fixed point is reached
|
||||||
|
|
||||||
|
Once the max_reg_usage is found, call post-processing can be done (addition of registers saving/restoring routines)
|
||||||
|
"""
|
||||||
|
|
||||||
|
function_data = {}
|
||||||
|
old_function_data = {}
|
||||||
|
|
||||||
|
# initialization step
|
||||||
|
for function_name in functions:
|
||||||
|
func = functions[function_name]
|
||||||
|
function_data[function_name] = {'reg_count': func.reg_count, 'calls': func._calls, 'max_reg_usage': func.reg_count}
|
||||||
|
|
||||||
|
# iterate until a fixed point is reached
|
||||||
|
while function_data != old_function_data:
|
||||||
|
old_function_data = copy.deepcopy(function_data)
|
||||||
|
|
||||||
|
# update each value
|
||||||
|
for name in function_data:
|
||||||
|
f_data = function_data[name]
|
||||||
|
|
||||||
|
# if the considered function exceed registers_number, skip (computation already done)
|
||||||
|
if len(f_data['calls']) == 0 or f_data['max_reg_usage'] >= registers_number:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# find max reg usage for called functions and compute current max_reg_usage
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
for sub in f_data['calls']:
|
||||||
|
calls.append(function_data[sub]['max_reg_usage'])
|
||||||
|
|
||||||
|
max_reg_usage = max(calls) + f_data['reg_count']
|
||||||
|
|
||||||
|
# limit max_reg_usage to registers_number
|
||||||
|
f_data['max_reg_usage'] = min(max_reg_usage, registers_number)
|
||||||
|
|
||||||
|
# update functions with final results.
|
||||||
|
for function_name in functions:
|
||||||
|
func = functions[function_name]
|
||||||
|
func.max_reg_usage = function_data[function_name]['max_reg_usage']
|
||||||
610
ScEpTIC/AST/register_allocation/linear_scan/linear_scan.py
Normal file
610
ScEpTIC/AST/register_allocation/linear_scan/linear_scan.py
Normal file
@ -0,0 +1,610 @@
|
|||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ScEpTIC import tools
|
||||||
|
from ScEpTIC.AST.elements import value
|
||||||
|
from ScEpTIC.AST.elements.instructions import memory_operations
|
||||||
|
from ScEpTIC.AST.elements.instructions import other_operations
|
||||||
|
from ScEpTIC.AST.elements.metadata import Metadata
|
||||||
|
from ScEpTIC.AST.register_allocation.linear_scan.register_operations import SaveRegistersOperation, RestoreRegistersOperation
|
||||||
|
from ScEpTIC.AST.register_allocation.linear_scan.register_pool import RegisterPool
|
||||||
|
from ScEpTIC.exceptions import RegAllocException
|
||||||
|
from ScEpTIC.llvmir_parser.sections_parser import global_vars
|
||||||
|
|
||||||
|
|
||||||
|
class LinearScanRegisterAllocator:
|
||||||
|
"""
|
||||||
|
Implementation of linear scan register allocation.
|
||||||
|
It must be done for each function and this class refers to a single function body.
|
||||||
|
Implementation details can be found here:
|
||||||
|
https://www2.seas.gwu.edu/~hchoi/teaching/cs160d/linearscan.pdf
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, function, regs_number, reg_prefix = 'R', spill_prefix = '%spill_', spill_type = 'i32'):
|
||||||
|
self.function = function
|
||||||
|
self.code = function.body
|
||||||
|
|
||||||
|
self.virtual_regs = {}
|
||||||
|
self.intervals = []
|
||||||
|
|
||||||
|
self.active = []
|
||||||
|
|
||||||
|
self.register_pool = RegisterPool(regs_number, reg_prefix)
|
||||||
|
|
||||||
|
# prefix of spill registers
|
||||||
|
self.spill_prefix = spill_prefix
|
||||||
|
self.spill_count = 0
|
||||||
|
self.spill_dimension = global_vars.parse_type(spill_type)
|
||||||
|
self.reg_count = 0
|
||||||
|
|
||||||
|
# registers to be ignored due to data layout
|
||||||
|
self.ignores = []
|
||||||
|
|
||||||
|
# identifies the line of the last alloca
|
||||||
|
# it is used to put alloca operations in the same area of the code, as they are translated with a single ESP increment.
|
||||||
|
self.last_alloca = 0
|
||||||
|
|
||||||
|
# calculate latest alloca operation
|
||||||
|
for i in range(0, len(self.code)):
|
||||||
|
self.last_alloca = i
|
||||||
|
if not isinstance(self.code[i], memory_operations.AllocaOperation):
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def run_register_allocation(self):
|
||||||
|
"""
|
||||||
|
Runs the actual register allocation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.do_call_pre_processing()
|
||||||
|
self.do_liveness_analysis()
|
||||||
|
self.do_register_allocation()
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def spill_type(self):
|
||||||
|
"""
|
||||||
|
Returns an instance of the spill type, to be used in the operations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return copy.deepcopy(self.spill_dimension)
|
||||||
|
|
||||||
|
|
||||||
|
def _name_to_virtual_reg(self, name):
|
||||||
|
"""
|
||||||
|
Returns a Value representing a virtual register with the provided name
|
||||||
|
"""
|
||||||
|
|
||||||
|
return value.Value('virtual_reg', name, None)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_virtual_reg(self):
|
||||||
|
"""
|
||||||
|
Creates a new virtual register.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = '{}{}'.format(self.spill_prefix, self.spill_count)
|
||||||
|
self.spill_count += 1
|
||||||
|
logging.debug('[RegisterAllocation] Creating new virtual register {}.'.format(name))
|
||||||
|
return self._name_to_virtual_reg(name)
|
||||||
|
|
||||||
|
|
||||||
|
def append_alloca_operation(self, alloca_type):
|
||||||
|
"""
|
||||||
|
Appends an alloca operation in the code at the end of the alloca section.
|
||||||
|
It returns the virtual registers contining the alloca address.
|
||||||
|
"""
|
||||||
|
logging.debug('[RegisterAllocation] Appending alloca({}) operation on top.'.format(len(alloca_type)))
|
||||||
|
|
||||||
|
spill_reg = self._create_virtual_reg()
|
||||||
|
target = copy.deepcopy(spill_reg)
|
||||||
|
|
||||||
|
# create the operation
|
||||||
|
alloca = memory_operations.AllocaOperation(target, alloca_type, 1, 1)
|
||||||
|
alloca.metadata = Metadata(None, None, None, [], [])
|
||||||
|
|
||||||
|
# insert it in the code
|
||||||
|
self.code.insert(self.last_alloca, alloca)
|
||||||
|
|
||||||
|
# update last_alloca, so to consider this added one.
|
||||||
|
self.last_alloca += 1
|
||||||
|
|
||||||
|
return spill_reg
|
||||||
|
|
||||||
|
|
||||||
|
def append_store_operation(self, position, target, value, target_type, label, metadata):
|
||||||
|
"""
|
||||||
|
Appends a store operation at the required position.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logging.debug('[RegisterAllocation] Appending store {}, {} at {}.'.format(target.value, value.value, position))
|
||||||
|
|
||||||
|
target = copy.deepcopy(target)
|
||||||
|
value = copy.deepcopy(value)
|
||||||
|
|
||||||
|
value.type = copy.deepcopy(target_type)
|
||||||
|
|
||||||
|
# create the operation
|
||||||
|
store = memory_operations.StoreOperation(target, value, 1, False)
|
||||||
|
store.label = label
|
||||||
|
store.metadata = metadata
|
||||||
|
|
||||||
|
# insert in the code the operation
|
||||||
|
self.code.insert(position, store)
|
||||||
|
|
||||||
|
|
||||||
|
def append_load_operation(self, position, element, load_type, is_arg_of_function_call, metadata):
|
||||||
|
"""
|
||||||
|
Appends a load operation at the required position.
|
||||||
|
It returns the virtual register containing the loaded value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logging.debug('[RegisterAllocation] Appending load {} at {}.'.format(element.value, position))
|
||||||
|
|
||||||
|
spill_reg = self._create_virtual_reg()
|
||||||
|
target = copy.deepcopy(spill_reg)
|
||||||
|
element = copy.deepcopy(element)
|
||||||
|
|
||||||
|
# create the operation
|
||||||
|
load = memory_operations.LoadOperation(target, load_type, element, 1, False)
|
||||||
|
load.metadata = metadata
|
||||||
|
|
||||||
|
# eventually set if it loads a function call's argument
|
||||||
|
if is_arg_of_function_call:
|
||||||
|
load.is_arg_of_function_call = True
|
||||||
|
|
||||||
|
# insert in the code the operation
|
||||||
|
self.code.insert(position, load)
|
||||||
|
|
||||||
|
return spill_reg
|
||||||
|
|
||||||
|
|
||||||
|
def do_call_pre_processing(self):
|
||||||
|
"""
|
||||||
|
Applies call pre-processing, consisting in:
|
||||||
|
- marking load of arguments as is_arg_of_function_call
|
||||||
|
- creating alloca-store-load for immediate
|
||||||
|
- creating alloca-store-load for conversion and other operation s.t. their
|
||||||
|
results are passed as arguments of the function call.
|
||||||
|
"""
|
||||||
|
|
||||||
|
search_from = 0
|
||||||
|
search_to = 0
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
# scan the code by line. while is used since the lines number may change
|
||||||
|
while i < len(self.code):
|
||||||
|
operation = self.code[i]
|
||||||
|
|
||||||
|
# process only call operations which are not part of the checkpoint mechanism.
|
||||||
|
if isinstance(operation, other_operations.CallOperation) and operation.name in self.function._calls:
|
||||||
|
targets = operation.get_uses()
|
||||||
|
search_to = i
|
||||||
|
j = search_from
|
||||||
|
|
||||||
|
# adjust label (if present)
|
||||||
|
append_label = operation.label
|
||||||
|
|
||||||
|
# search for instructions s.t. their target is in function arguments
|
||||||
|
while j < search_to:
|
||||||
|
op = self.code[j]
|
||||||
|
|
||||||
|
# target of operation is an argument of the call
|
||||||
|
if 'target' in op.__dict__ and op.target is not None and op.target.value in targets:
|
||||||
|
targets.remove(op.target.value)
|
||||||
|
|
||||||
|
# if is a load instruction, mark it
|
||||||
|
if isinstance(op, memory_operations.LoadOperation):
|
||||||
|
op.is_arg_of_function_call = True
|
||||||
|
# else generate alloca, store and load, as it would be done
|
||||||
|
# in a real scenario (value saved onto the stack to be passed)
|
||||||
|
# NB: load is marked as is_arg_of_function_call (so to emulate stack passing)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# get type of argument
|
||||||
|
arg_type = operation.get_type_from_virtual_reg(op.target.value)
|
||||||
|
|
||||||
|
# append alloca and compensate for the added instruction
|
||||||
|
tmp_reg = self.append_alloca_operation(arg_type)
|
||||||
|
|
||||||
|
# append store before call and compensate for the added instruction
|
||||||
|
# search_to = call position
|
||||||
|
# nb: here is set the eventual label of the call operation.
|
||||||
|
self.append_store_operation(search_to+1, tmp_reg, op.target, arg_type, append_label, operation.metadata)
|
||||||
|
|
||||||
|
# set the lable to be appended to None
|
||||||
|
append_label = None
|
||||||
|
|
||||||
|
# append load before call and compensate for the added instruction
|
||||||
|
tmp_reg = self.append_load_operation(search_to+2, tmp_reg, arg_type, True, operation.metadata)
|
||||||
|
|
||||||
|
# update the new register name containing the argument value.
|
||||||
|
operation.replace_reg_name(op.target.value, tmp_reg.value)
|
||||||
|
|
||||||
|
# compensate for the 3 added instructions
|
||||||
|
i += 3
|
||||||
|
|
||||||
|
# update search_to value, to consider added instructions
|
||||||
|
search_to = i
|
||||||
|
|
||||||
|
# update code index
|
||||||
|
j += 1
|
||||||
|
|
||||||
|
# create alloca, store and load for immediate values, as it is done
|
||||||
|
# for operations on the above lines.
|
||||||
|
|
||||||
|
for arg in operation.function_args:
|
||||||
|
if arg.value_class == 'immediate':
|
||||||
|
|
||||||
|
# llvm builtins may have 1bit operations, but memory can only work
|
||||||
|
# with bytes, so modify the memory dimension to do not have problems.
|
||||||
|
# data integrity is preserved.
|
||||||
|
if arg.type is not None and arg.type.base_type.bits < 8:
|
||||||
|
arg.type.base_type.bits = 8
|
||||||
|
|
||||||
|
tmp_reg = self.append_alloca_operation(arg.type)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# nb: here is set the eventual label of the call operation.
|
||||||
|
self.append_store_operation(i, tmp_reg, arg, arg.type, append_label, operation.metadata)
|
||||||
|
# set the lable to be appended to None
|
||||||
|
append_label = None
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
tmp_reg = self.append_load_operation(i, tmp_reg, arg.type, True, operation.metadata)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# update the arguments to be a virtual register instead of an immediate
|
||||||
|
arg.value_class = 'virtual_reg'
|
||||||
|
|
||||||
|
# set the virutal register name
|
||||||
|
arg.value = tmp_reg.value
|
||||||
|
|
||||||
|
# update search from. it is used to limit the area in which function arguments are searched.
|
||||||
|
# in llvm all arguments are reloaded if multiple calls happens from them, so there is no need
|
||||||
|
# to search arguments from the top (it is sufficient to go from the previous call to the current one)
|
||||||
|
search_from = i + 1
|
||||||
|
|
||||||
|
# refresh the operation label: if it is assigned to another operation, it will be set to false
|
||||||
|
# otherwise is unchanged.
|
||||||
|
operation.label = append_label
|
||||||
|
|
||||||
|
# update code index
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
def do_call_post_processing(self):
|
||||||
|
"""
|
||||||
|
Applies function call post-processing, which consists in:
|
||||||
|
- Insertion of SaveRegistersOperation and RestoreRegistersOperation respectively before and after
|
||||||
|
a function call
|
||||||
|
- Updating the labels mappings (if code is appended, a label is mapped with a line which won't be exact)
|
||||||
|
"""
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
# scan the code by line. while is used since the lines number may change
|
||||||
|
while i < len(self.code):
|
||||||
|
operation = self.code[i]
|
||||||
|
|
||||||
|
# process only call operations which are not part of the checkpoint mechanism.
|
||||||
|
if isinstance(operation, other_operations.CallOperation) and operation.name in self.function._calls:
|
||||||
|
|
||||||
|
# if recursive call, save used registers
|
||||||
|
if operation.name == self.function.name:
|
||||||
|
tick_count = self.function.reg_count
|
||||||
|
|
||||||
|
# else estimates the number of registers to be saved in case of a well-partitioned
|
||||||
|
# register allocation among functions.
|
||||||
|
# e.g. if main uses 2 registers and calls pow which uses 2 registers, a well-partitioned
|
||||||
|
# register allocation will give R0 and R1 to main, and R2 and R3 to pow
|
||||||
|
# with the result of no register saving needed
|
||||||
|
else:
|
||||||
|
# usage = reg used by current function + max reg used by the called function (and calls inside it)
|
||||||
|
# registers to be saved = usage - available
|
||||||
|
# if I have 10 registers, the function uses 2 registers and the called one uses 2 registers,
|
||||||
|
# no saving needed.
|
||||||
|
# max_reg_usage is calculated iteratively before running do_call_post_processing
|
||||||
|
tick_count = self.function.reg_count + operation.get_function_max_reg_usage() - self.register_pool.regs_number
|
||||||
|
|
||||||
|
# if result is < 0 means that no register should be saved.
|
||||||
|
if tick_count < 0:
|
||||||
|
tick_count = 0
|
||||||
|
|
||||||
|
# get target register name
|
||||||
|
if 'target' in operation.__dict__ and operation.target is not None:
|
||||||
|
target = operation.target.get_uses()[0]
|
||||||
|
else:
|
||||||
|
target = None
|
||||||
|
|
||||||
|
# create the saving and restoring operations
|
||||||
|
save_reg_op = SaveRegistersOperation(tick_count, target, self.register_pool)
|
||||||
|
save_reg_op.metadata = operation.metadata
|
||||||
|
|
||||||
|
# compensate label
|
||||||
|
save_reg_op.label = operation.label
|
||||||
|
operation.label = None
|
||||||
|
|
||||||
|
load_reg_op = RestoreRegistersOperation(save_reg_op)
|
||||||
|
load_reg_op.metadata = operation.metadata
|
||||||
|
|
||||||
|
# insert the save operation before the function call
|
||||||
|
self.code.insert(i, save_reg_op)
|
||||||
|
|
||||||
|
# 1 for compensating save_reg_op, 1 for finding the operation after the call
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
# insert the restore operation after the function call
|
||||||
|
self.code.insert(i, load_reg_op)
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# update the function's label mappings.
|
||||||
|
self.function.update_labels()
|
||||||
|
|
||||||
|
|
||||||
|
def _init_intervals(self):
|
||||||
|
"""
|
||||||
|
Inits the intervals used for calculating the active interva of the registers for the linear scan.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for _ in range(0, len(self.code)):
|
||||||
|
self.intervals.append(None)
|
||||||
|
|
||||||
|
|
||||||
|
def do_liveness_analysis(self):
|
||||||
|
"""
|
||||||
|
This method performs the liveness analysis, which finds the live interval for each register.
|
||||||
|
NB: LLVM IR is in Static Single Assignment (SSA) foarm, so for obtaining the liveness intervals
|
||||||
|
is sufficient to map the definition with its last use.
|
||||||
|
"""
|
||||||
|
# registers to be ignored, since will be considered as stack offsets/addresses
|
||||||
|
# which will be resolved by datalayout operation.
|
||||||
|
self.ignores = self.function.get_ignore()
|
||||||
|
|
||||||
|
# for each line, compute the uses and definitions of each register.
|
||||||
|
for i in range(0, len(self.code)):
|
||||||
|
self.ignores = self.ignores + self.code[i].get_ignore()
|
||||||
|
defs = tools.list_sanitize_from_list(self.code[i].get_defs(), self.ignores)
|
||||||
|
uses = tools.list_sanitize_from_list(self.code[i].get_uses(), self.ignores)
|
||||||
|
|
||||||
|
for reg in defs:
|
||||||
|
self.virtual_regs[reg] = {'uses': [], 'def': i, 'target': None}
|
||||||
|
|
||||||
|
for reg in uses:
|
||||||
|
self.virtual_regs[reg]['uses'].append(i)
|
||||||
|
|
||||||
|
self._init_intervals()
|
||||||
|
|
||||||
|
# find last use of each register and create interval dict
|
||||||
|
for reg_id, reg in self.virtual_regs.items():
|
||||||
|
# if no use, the last use will be in the same instruction in which the register
|
||||||
|
# is defined
|
||||||
|
if len(reg['uses']) == 0:
|
||||||
|
reg['uses'].append(reg['def'])
|
||||||
|
|
||||||
|
reg['last_use'] = max(reg['uses'])
|
||||||
|
|
||||||
|
# set register intervals
|
||||||
|
self.intervals[reg['def']] = {'reg': reg_id, 'last_use': reg['last_use']}
|
||||||
|
|
||||||
|
|
||||||
|
def _compensate_for_alloca(self):
|
||||||
|
"""
|
||||||
|
Updates the liveness analysis to account for the insertion of an alloca operations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logging.debug('[RegisterAllocation] Compensating for alloca operation.')
|
||||||
|
|
||||||
|
# alloca operations do not introduces any use/def and are appended on the top of the code
|
||||||
|
# so i can insert a None interval on top of the list (since all alloca operations are in the same region)
|
||||||
|
self.intervals.insert(0, None)
|
||||||
|
|
||||||
|
# increment uses of each interval by 1
|
||||||
|
for interval in self.intervals:
|
||||||
|
if interval is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
interval['last_use'] += 1
|
||||||
|
|
||||||
|
# increament uses and definitions of each virtual register by one
|
||||||
|
for virtual_reg in self.virtual_regs.values():
|
||||||
|
virtual_reg['def'] += 1
|
||||||
|
virtual_reg['uses'] = [x+1 for x in virtual_reg['uses']]
|
||||||
|
virtual_reg['last_use'] += 1
|
||||||
|
|
||||||
|
|
||||||
|
def _compensate_for_operation(self, instant):
|
||||||
|
"""
|
||||||
|
Updates the liveness analysis to account for the insertion of an operation which may
|
||||||
|
insert use/def and is inserted in a precise line of code.
|
||||||
|
instant specifies also the line of code
|
||||||
|
"""
|
||||||
|
logging.debug('[RegisterAllocation] Compensating for operation at {}.'.format(instant))
|
||||||
|
|
||||||
|
# insert the proper interval
|
||||||
|
self.intervals.insert(instant, None)
|
||||||
|
|
||||||
|
# increment data by 1 for subsequent intervals
|
||||||
|
for interval in self.intervals:
|
||||||
|
if interval is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# if subsequent interval, it needs to be updated
|
||||||
|
if interval['last_use'] >= instant:
|
||||||
|
interval['last_use'] += 1
|
||||||
|
|
||||||
|
# get corresponding virtual register
|
||||||
|
virtual_reg = self.virtual_regs[interval['reg']]
|
||||||
|
|
||||||
|
# increment its definition instant if happens after the given instant
|
||||||
|
if virtual_reg['def'] >= instant:
|
||||||
|
virtual_reg['def'] += 1
|
||||||
|
|
||||||
|
# increment all the uses by 1 if they are after the given instant
|
||||||
|
virtual_reg['uses'] = [x+1 if x >= instant else x for x in virtual_reg['uses']]
|
||||||
|
# increment last use by 1, since it will be certainly after the given instant
|
||||||
|
virtual_reg['last_use'] += 1
|
||||||
|
|
||||||
|
|
||||||
|
def _assign_register(self, instant, new_reg_name):
|
||||||
|
"""
|
||||||
|
Assigns a physical register to a virtual register which is defined in a given instant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
interval = self.intervals[instant]
|
||||||
|
reg_name = interval['reg']
|
||||||
|
logging.debug('[RegisterAllocation] Assigning register {} to virtual register {} at {}.'.format(new_reg_name, reg_name, instant))
|
||||||
|
|
||||||
|
# update each occurrence of the old register with the new register
|
||||||
|
for line in self.virtual_regs[reg_name]['uses']:
|
||||||
|
self.code[line].replace_reg_name(reg_name, new_reg_name)
|
||||||
|
|
||||||
|
self.code[instant].replace_reg_name(reg_name, new_reg_name)
|
||||||
|
self.virtual_regs[reg_name]['target'] = new_reg_name
|
||||||
|
|
||||||
|
# insert register into the active ones.
|
||||||
|
self.active.append(interval['reg'])
|
||||||
|
|
||||||
|
def do_register_allocation(self):
|
||||||
|
"""
|
||||||
|
Performs the linear scan register allocation over the given function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
instant = 0
|
||||||
|
|
||||||
|
# scan all intervals and insert register spills / promotions
|
||||||
|
while instant < len(self.intervals):
|
||||||
|
interval = self.intervals[instant]
|
||||||
|
|
||||||
|
if interval is None:
|
||||||
|
instant += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# expire old registers
|
||||||
|
self.expire_old(instant)
|
||||||
|
|
||||||
|
# set max reg count (used to estimate the tick_count)
|
||||||
|
self.reg_count = max(self.reg_count, len(self.active))
|
||||||
|
|
||||||
|
# if all registers used, need to spill.
|
||||||
|
if len(self.active) == self.register_pool.regs_number:
|
||||||
|
# spill at current instant
|
||||||
|
self.spill_at(instant)
|
||||||
|
# compensate for alloca and store
|
||||||
|
instant += 2
|
||||||
|
|
||||||
|
# else assign register
|
||||||
|
else:
|
||||||
|
new_reg_name = self.register_pool.get_reg()
|
||||||
|
self._assign_register(instant, new_reg_name)
|
||||||
|
|
||||||
|
# increment instant index
|
||||||
|
instant += 1
|
||||||
|
|
||||||
|
# set function regiser count.
|
||||||
|
self.function.reg_count = self.reg_count
|
||||||
|
|
||||||
|
|
||||||
|
def expire_old(self, instant):
|
||||||
|
"""
|
||||||
|
Updates the active registers by removing expired ones.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logging.debug('[RegisterAllocation] Expiring registers at {}.'.format(instant))
|
||||||
|
|
||||||
|
# scan active registers to find if some of them can be freed.
|
||||||
|
for reg in self.active:
|
||||||
|
|
||||||
|
# if the last use is before current instant, the register can be
|
||||||
|
# considered as free.
|
||||||
|
if self.virtual_regs[reg]['last_use'] < instant:
|
||||||
|
|
||||||
|
# remove from active registers
|
||||||
|
self.active.remove(reg)
|
||||||
|
|
||||||
|
# free register
|
||||||
|
reg_name = self.virtual_regs[reg]['target']
|
||||||
|
self.register_pool.free_reg(reg_name)
|
||||||
|
|
||||||
|
def _get_fist_use_after_instant(self, reg, instant):
|
||||||
|
"""
|
||||||
|
Returns the first use of a register after a given instant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
uses = self.virtual_regs[reg]['uses']
|
||||||
|
first_use = self.virtual_regs[reg]['last_use']
|
||||||
|
|
||||||
|
for i in uses:
|
||||||
|
if instant < i < first_use:
|
||||||
|
first_use = i
|
||||||
|
|
||||||
|
return first_use
|
||||||
|
|
||||||
|
def spill_at(self, instant):
|
||||||
|
"""
|
||||||
|
Selects a register to be spilled in the given instant and spills it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
selected_spill = None
|
||||||
|
selected_reg = None
|
||||||
|
latest_use = -1
|
||||||
|
|
||||||
|
# scans each active register.
|
||||||
|
for reg in self.active:
|
||||||
|
reg_data = self.virtual_regs[reg]
|
||||||
|
|
||||||
|
# if register can be spilled (= not used in this instant nor defined)
|
||||||
|
if instant not in reg_data['uses'] and instant != reg_data['def']:
|
||||||
|
# get first use after current instant
|
||||||
|
first_use = self._get_fist_use_after_instant(reg, instant)
|
||||||
|
|
||||||
|
# update max usage informations, used for spilling selection
|
||||||
|
if first_use > latest_use:
|
||||||
|
selected_spill = reg_data['target']
|
||||||
|
selected_reg = reg
|
||||||
|
latest_use = first_use
|
||||||
|
|
||||||
|
# no register can be spilled, exit.
|
||||||
|
if selected_spill is None:
|
||||||
|
raise RegAllocException('Unable to select a register for spilling')
|
||||||
|
|
||||||
|
logging.debug('[RegisterAllocation] Spilling register {} at instant {}'.format(selected_spill, instant))
|
||||||
|
|
||||||
|
# append alloca operation, to account for stack space
|
||||||
|
tmp_reg = self.append_alloca_operation(self.spill_type)
|
||||||
|
|
||||||
|
# compensate for alloca
|
||||||
|
self._compensate_for_alloca()
|
||||||
|
instant += 1
|
||||||
|
|
||||||
|
# save register value onto the stack before current instant
|
||||||
|
value_reg = self._name_to_virtual_reg(selected_spill)
|
||||||
|
self.append_store_operation(instant, tmp_reg, value_reg, self.spill_type, None, self.code[instant].metadata)
|
||||||
|
|
||||||
|
# compensate for store
|
||||||
|
self._compensate_for_operation(instant)
|
||||||
|
instant += 1
|
||||||
|
|
||||||
|
# compensate for alloca and store. NB: use is after alloca and store
|
||||||
|
latest_use += 2
|
||||||
|
# create load before the first use of spilled register and
|
||||||
|
tmp_reg = self.append_load_operation(latest_use, tmp_reg, self.spill_type, False, self.code[instant].metadata)
|
||||||
|
self._compensate_for_operation(latest_use)
|
||||||
|
|
||||||
|
# retarget uses of spilled register to created load (new reg name, which for now is virtual and in
|
||||||
|
# a next step will be updated with a physical one)
|
||||||
|
uses = [x for x in self.virtual_regs[selected_reg]['uses'] if x >= latest_use]
|
||||||
|
for i in uses:
|
||||||
|
self.code[i].replace_reg_name(selected_spill, tmp_reg.value)
|
||||||
|
|
||||||
|
# create interval and register for load, so to assign a physical register to it in a next iteration.
|
||||||
|
reg_id = tmp_reg.value
|
||||||
|
last_use = max(uses)
|
||||||
|
virtual_reg = {'uses': uses, 'def': latest_use, 'target': None, 'last_use': last_use}
|
||||||
|
self.virtual_regs[reg_id] = virtual_reg
|
||||||
|
self.intervals[latest_use] = {'reg': reg_id, 'last_use': last_use}
|
||||||
|
|
||||||
|
# assign register
|
||||||
|
self.active.remove(selected_reg)
|
||||||
|
self._assign_register(instant, selected_spill)
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
import copy
|
||||||
|
|
||||||
|
from ScEpTIC.AST.elements.instruction import Instruction
|
||||||
|
from ScEpTIC.exceptions import MemoryException
|
||||||
|
|
||||||
|
|
||||||
|
class SaveRegistersOperation(Instruction):
|
||||||
|
"""
|
||||||
|
Operation that emulates the saving of registers before a function call.
|
||||||
|
All the register savings are done as caller-save, since saving register in the callee
|
||||||
|
function will lead to multiple SaveRegistersOperation (one before each return).
|
||||||
|
The instruction is set up with:
|
||||||
|
- tick_count: consists in the number of registers used by the called function.
|
||||||
|
In a real scenario, 1 store operation (Rx -> stack) will be inserted for each register
|
||||||
|
that needs to be saved. Once this instruction is executed, the global clock will be incremented
|
||||||
|
by this value ( = number of store operations that should be present)
|
||||||
|
- target_reg: is the name of the register that will contain the return value of the call. It won't be restored, nor saved.
|
||||||
|
|
||||||
|
NB: all registers are saved from R0 to R_n, so it is sufficient to provide the number of registers to be saved
|
||||||
|
to save the first n registers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, tick_count, target_reg, register_pool):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
target_reg_id = register_pool._get_id_from_reg_name(target_reg)
|
||||||
|
|
||||||
|
if target_reg_id is None:
|
||||||
|
target_reg_id = tick_count
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tick count corresponds to the number of registers to be saved, which
|
||||||
|
can be converted as a R_i with i in [0, tick_count)
|
||||||
|
If the target register is inside such range, tick_count should be decreased by 1
|
||||||
|
since the target register will be replaced by the function call and won't be restored
|
||||||
|
(it will contain the call's return value)
|
||||||
|
"""
|
||||||
|
if target_reg_id < tick_count:
|
||||||
|
tick_count -= 1
|
||||||
|
|
||||||
|
self.tick_count = tick_count
|
||||||
|
self.target_reg = target_reg
|
||||||
|
self._omit_target = True
|
||||||
|
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
"""
|
||||||
|
Save all registers, except for the target one.
|
||||||
|
They will be restored by RestoreRegistersOperation.
|
||||||
|
Since saving all registers or a sub-group of them won't change anything,
|
||||||
|
this function saves all of them in order to remove a control of which register
|
||||||
|
needs to be saved. The overall result is the same as it only some registers are saved,
|
||||||
|
since registers computed inside a function won't have any value needed by the caller,
|
||||||
|
except from the return register.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# variables used to store registers
|
||||||
|
registers = {}
|
||||||
|
registers_input_lookup = {}
|
||||||
|
|
||||||
|
for reg in self._vmstate.register_file._registers:
|
||||||
|
if reg != self.target_reg:
|
||||||
|
reg = self._vmstate.register_file._registers[reg]
|
||||||
|
|
||||||
|
# save register's value
|
||||||
|
registers[reg.name] = copy.deepcopy(reg.value)
|
||||||
|
|
||||||
|
# save register's input lookup information
|
||||||
|
if self._vmstate.input_lookup_enabled:
|
||||||
|
input_lookup = copy.deepcopy(self._vmstate.register_file.get_input_lookup(reg.name))
|
||||||
|
if input_lookup is not None:
|
||||||
|
registers_input_lookup[reg.name] = input_lookup
|
||||||
|
|
||||||
|
# simulate register saving.
|
||||||
|
for _ in range(0, self.tick_count):
|
||||||
|
|
||||||
|
if self._vmstate.do_data_anomaly_check:
|
||||||
|
self._vmstate.function_call_lookup['regs'].append(self._vmstate.memory.stack.top_address)
|
||||||
|
|
||||||
|
self._vmstate.memory.stack.push(self._vmstate.memory.address_dimension, copy.deepcopy(registers), 'register')
|
||||||
|
|
||||||
|
# save registers into saving_pool
|
||||||
|
to_save = {'registers': registers, 'registers_input_lookup': registers_input_lookup}
|
||||||
|
self._vmstate.reg_saving_pool.append(to_save)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'SaveRegistersOperation(~{}) [{} ops]'.format(self.target_reg, self.tick_count)
|
||||||
|
|
||||||
|
|
||||||
|
class RestoreRegistersOperation(Instruction):
|
||||||
|
"""
|
||||||
|
Represents an operation that emulates the restoring of registers after a called function returns.
|
||||||
|
It is setup by proiding the corresponding SaveRegistersOperation, from which takes the tick_count and the
|
||||||
|
registers values to be restored.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, save_registers_operation):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.tick_count = save_registers_operation.tick_count
|
||||||
|
self._omit_target = True
|
||||||
|
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
to_restore = self._vmstate.reg_saving_pool.pop()
|
||||||
|
|
||||||
|
# restore each register saved by the SaveRegistersOperation
|
||||||
|
# NB: the target register is not included here by construction of the
|
||||||
|
# SaveRegistersOperation method.
|
||||||
|
for reg in to_restore['registers']:
|
||||||
|
self._vmstate.register_file.write(reg, copy.deepcopy(to_restore['registers'][reg]))
|
||||||
|
|
||||||
|
# restore input lookup information.
|
||||||
|
if self._vmstate.input_lookup_enabled:
|
||||||
|
for reg in to_restore['registers_input_lookup']:
|
||||||
|
self._vmstate.register_file._input_lookup[reg] = copy.deepcopy(to_restore['registers_input_lookup'][reg])
|
||||||
|
|
||||||
|
# simulate registers restoring.
|
||||||
|
for _ in range(0, self.tick_count):
|
||||||
|
stacked_reg_file = self._vmstate.memory.stack.pop(self._vmstate.memory.address_dimension)
|
||||||
|
|
||||||
|
if to_restore['registers'] != stacked_reg_file:
|
||||||
|
raise MemoryException('[RestoreRegistersOperation] Register file saved in stack is anomalous (wrong register values).')
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'RestoreRegistersOperation() [{} ops]'.format(self.tick_count)
|
||||||
48
ScEpTIC/AST/register_allocation/linear_scan/register_pool.py
Normal file
48
ScEpTIC/AST/register_allocation/linear_scan/register_pool.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from ScEpTIC.exceptions import RegAllocException
|
||||||
|
|
||||||
|
class RegisterPool:
|
||||||
|
"""
|
||||||
|
Pool of registers used by register allocation to get available registers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, regs_number, reg_prefix):
|
||||||
|
# list of registers state: True -> free; False -> in use
|
||||||
|
self.registers = [True for x in range(regs_number)]
|
||||||
|
self.regs_number = regs_number
|
||||||
|
self.reg_prefix = reg_prefix
|
||||||
|
|
||||||
|
|
||||||
|
def get_reg(self):
|
||||||
|
"""
|
||||||
|
Returns a free register. If no register is available, it raises a RegAllocException
|
||||||
|
The returned register is marked as busy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for reg_id in range(0, self.regs_number):
|
||||||
|
if self.registers[reg_id]:
|
||||||
|
# set the register to in use
|
||||||
|
self.registers[reg_id] = False
|
||||||
|
return '{}{}'.format(self.reg_prefix, reg_id)
|
||||||
|
|
||||||
|
raise RegAllocException('No register available for register allocation!')
|
||||||
|
|
||||||
|
|
||||||
|
def _get_id_from_reg_name(self, reg_name):
|
||||||
|
"""
|
||||||
|
Returns the id of a register, given its name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if reg_name is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
reg_name = reg_name.replace(self.reg_prefix, '')
|
||||||
|
return int(reg_name)
|
||||||
|
|
||||||
|
|
||||||
|
def free_reg(self, reg_name):
|
||||||
|
"""
|
||||||
|
Marks a given register as free.
|
||||||
|
"""
|
||||||
|
|
||||||
|
reg_id = self._get_id_from_reg_name(reg_name)
|
||||||
|
self.registers[reg_id] = True
|
||||||
21
ScEpTIC/AST/transformations/__init__.py
Normal file
21
ScEpTIC/AST/transformations/__init__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
CONFIG_FUNCTION_NAME = 'get_config'
|
||||||
|
|
||||||
|
def apply_transformation(transformation, functions_ast, vmstate, f_declarations):
|
||||||
|
"""
|
||||||
|
Applies a program transformation.
|
||||||
|
Note that any transformation must be specified as a sub-module of the transformation module.
|
||||||
|
The name of the module represents the name of the transformation, which is passed as argument to this method.
|
||||||
|
Each transformation sub-module need a apply_transformation() function that takes as argument the functions AST.
|
||||||
|
"""
|
||||||
|
logging.info(f"Applying transformation {transformation}")
|
||||||
|
|
||||||
|
# Import custom module
|
||||||
|
module_name = 'ScEpTIC.AST.transformations.{}.main'.format(transformation)
|
||||||
|
function_name = 'apply_transformation'
|
||||||
|
module = __import__(module_name, fromlist=[function_name])
|
||||||
|
transformation_function = getattr(module, function_name)
|
||||||
|
|
||||||
|
# Apply transformation
|
||||||
|
transformation_function(functions_ast, vmstate, f_declarations)
|
||||||
BIN
ScEpTIC/AST/transformations/__pycache__/__init__.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/transformations/__pycache__/__init__.pypy310.pyc
Normal file
Binary file not shown.
0
ScEpTIC/AST/transformations/base/__init__.py
Normal file
0
ScEpTIC/AST/transformations/base/__init__.py
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/transformations/base/__pycache__/config.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/transformations/base/__pycache__/config.pypy310.pyc
Normal file
Binary file not shown.
BIN
ScEpTIC/AST/transformations/base/__pycache__/main.pypy310.pyc
Normal file
BIN
ScEpTIC/AST/transformations/base/__pycache__/main.pypy310.pyc
Normal file
Binary file not shown.
60
ScEpTIC/AST/transformations/base/config.py
Normal file
60
ScEpTIC/AST/transformations/base/config.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
from ScEpTIC.config.base_config import ScEpTICBaseConfig
|
||||||
|
|
||||||
|
class BaseTransformationsConfig(ScEpTICBaseConfig):
|
||||||
|
"""
|
||||||
|
Configuration for default transformations
|
||||||
|
"""
|
||||||
|
|
||||||
|
_config_domain = {
|
||||||
|
'simulate_clock_cycles_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'increment_custom_metric_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'enter_mcu_lpm_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'print_dump_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
|
||||||
|
'interrupts_enable_firing_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'interrupts_disable_firing_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'interrupts_enable_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'interrupts_disable_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
|
||||||
|
'timer_start_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'timer_stop_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'timer_set_period_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
|
||||||
|
'printf_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'custom_log_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'custom_log_dump_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, main_config):
|
||||||
|
super().__init__(main_config)
|
||||||
|
self.__ir_prefix = f"{main_config.program.ir_function_prefix}"
|
||||||
|
self.simulate_clock_cycles_function_name = f"{self.__ir_prefix}sceptic_simulate_cycles"
|
||||||
|
self.increment_custom_metric_function_name = f"{self.__ir_prefix}sceptic_increment_custom_metric"
|
||||||
|
self.enter_mcu_lpm_function_name = f"{self.__ir_prefix}sceptic_mcu_lpm"
|
||||||
|
self.print_dump_function_name = f"{self.__ir_prefix}sceptic_print_dump"
|
||||||
|
|
||||||
|
self.interrupts_enable_firing_function_name = f"{self.__ir_prefix}sceptic_interrupts_enable_firing"
|
||||||
|
self.interrupts_disable_firing_function_name = f"{self.__ir_prefix}sceptic_interrupts_disable_firing"
|
||||||
|
self.interrupts_enable_function_name = f"{self.__ir_prefix}sceptic_interrupts_enable"
|
||||||
|
self.interrupts_disable_function_name = f"{self.__ir_prefix}sceptic_interrupts_disable"
|
||||||
|
|
||||||
|
self.timer_start_function_name = f"{self.__ir_prefix}sceptic_timer_start"
|
||||||
|
self.timer_stop_function_name = f"{self.__ir_prefix}sceptic_timer_stop"
|
||||||
|
self.timer_set_period_function_name = f"{self.__ir_prefix}sceptic_timer_set_period"
|
||||||
|
|
||||||
|
self.printf_function_name = f"{self.__ir_prefix}printf"
|
||||||
|
self.custom_log_function_name = f"{self.__ir_prefix}sceptic_custom_log"
|
||||||
|
self.custom_log_dump_function_name = f"{self.__ir_prefix}sceptic_custom_log_dump"
|
||||||
|
|
||||||
|
for function_name in self._config_domain.keys():
|
||||||
|
self._register_config_callback(function_name, self._append_ir_callback)
|
||||||
|
|
||||||
|
def _append_ir_callback(self, config_name, config_value):
|
||||||
|
if not config_value.startswith("sceptic_"):
|
||||||
|
config_value = f"sceptic_{config_value}"
|
||||||
|
|
||||||
|
setattr(self, config_name, f"{self.__ir_prefix}{config_value}")
|
||||||
|
|
||||||
|
# Returns the configuration
|
||||||
|
def get_config(main_config):
|
||||||
|
return BaseTransformationsConfig(main_config)
|
||||||
52
ScEpTIC/AST/transformations/base/instructions/__init__.py
Normal file
52
ScEpTIC/AST/transformations/base/instructions/__init__.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from ScEpTIC.AST.elements.instruction import Instruction
|
||||||
|
|
||||||
|
class CustomInstruction(Instruction):
|
||||||
|
"""
|
||||||
|
CustomInstruction() base class
|
||||||
|
"""
|
||||||
|
|
||||||
|
_omit_target = True
|
||||||
|
tick_count = 0
|
||||||
|
|
||||||
|
def __init__(self, call_operation):
|
||||||
|
super().__init__()
|
||||||
|
self.basic_block_id = call_operation.basic_block_id
|
||||||
|
self.label = call_operation.label
|
||||||
|
self.preds = call_operation.preds
|
||||||
|
self.metadata = call_operation.metadata
|
||||||
|
self.virtual_memory_target = call_operation.virtual_memory_target
|
||||||
|
|
||||||
|
self.target = None
|
||||||
|
|
||||||
|
self.args = call_operation.function_args
|
||||||
|
self._str_params = None
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = super().__str__()
|
||||||
|
|
||||||
|
if self._str_params is None:
|
||||||
|
params = [self.get_arg(i) for i in range(len(self.args))]
|
||||||
|
else:
|
||||||
|
params = self._str_params
|
||||||
|
|
||||||
|
str_params = ', '.join([f"\"{x}\"" if isinstance(x, str) else f"{x}" for x in params])
|
||||||
|
retstr += f"{self.__class__.__name__}({str_params})"
|
||||||
|
return retstr
|
||||||
|
|
||||||
|
def get_arg(self, id):
|
||||||
|
try:
|
||||||
|
# llvm string
|
||||||
|
if self.args[id].is_llvm_string():
|
||||||
|
address = self.args[id].get_val()
|
||||||
|
return self._vmstate.memory.gst._get_gst_from_address(address).read_string_from_address(address)
|
||||||
|
|
||||||
|
return self.args[id].get_val()
|
||||||
|
|
||||||
|
except BaseException:
|
||||||
|
return self.args[id]
|
||||||
|
|
||||||
|
def argc(self):
|
||||||
|
return len(self.args)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
51
ScEpTIC/AST/transformations/base/instructions/custom_log.py
Normal file
51
ScEpTIC/AST/transformations/base/instructions/custom_log.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from . import CustomInstruction
|
||||||
|
import os
|
||||||
|
|
||||||
|
class CustomLog(CustomInstruction):
|
||||||
|
"""
|
||||||
|
CustomLog() logs data
|
||||||
|
"""
|
||||||
|
|
||||||
|
tick_count = 1
|
||||||
|
handler = None
|
||||||
|
|
||||||
|
def __init__(self, call_operation, no_pointer=True):
|
||||||
|
super(CustomLog, self).__init__(call_operation)
|
||||||
|
|
||||||
|
self.no_pointer = no_pointer
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
if CustomLog.handler is None:
|
||||||
|
os.makedirs(self._vmstate.log_dir, exist_ok=True)
|
||||||
|
CustomLog.handler = open(os.path.join(self._vmstate.log_dir, 'custom_logs.csv'), "w")
|
||||||
|
CustomLog.handler.write("timestamp, program_counter, custom_log_data\n")
|
||||||
|
|
||||||
|
format_str = self.get_arg(0)
|
||||||
|
if self.no_pointer:
|
||||||
|
args = tuple([self.get_arg(i) for i in range(1, self.argc())])
|
||||||
|
#print(f"CustomLog: {args}")
|
||||||
|
else:
|
||||||
|
args = tuple([self._vmstate.memory.read_addr(self.get_arg(i)) for i in range(1, self.argc())])
|
||||||
|
#print(f"CustomLogDump: {args}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
log_line = "{}".format(format_str % args)
|
||||||
|
except TypeError:
|
||||||
|
#log_line = "{} {}".format(format_str, args)
|
||||||
|
log_line = f"{args}"
|
||||||
|
|
||||||
|
if ',' in log_line:
|
||||||
|
log_line = f"\"{log_line}\""
|
||||||
|
|
||||||
|
t = self._vmstate.config.analysis.energy.system_model.get_simulation_time()
|
||||||
|
pc = f"{self._vmstate.register_file.pc}"
|
||||||
|
|
||||||
|
CustomLog.handler.write(f"{t}, {pc}, {log_line}\n")
|
||||||
|
|
||||||
|
super().run(update_program_counter)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomLogDump(CustomLog):
|
||||||
|
|
||||||
|
def __init__(self, call_operation):
|
||||||
|
super(CustomLogDump, self).__init__(call_operation, False)
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
from . import CustomInstruction
|
||||||
|
|
||||||
|
class EnterMCULPM(CustomInstruction):
|
||||||
|
"""
|
||||||
|
EnterMCULPM() sets the MCU into LPM
|
||||||
|
params for the equivalent function call:
|
||||||
|
- wakeup_time
|
||||||
|
"""
|
||||||
|
|
||||||
|
tick_count = 1
|
||||||
|
|
||||||
|
def __init__(self, call_operation):
|
||||||
|
super().__init__(call_operation)
|
||||||
|
self.lpm_time = int(self.get_arg(0)) if len(self.args) > 0 else 0
|
||||||
|
|
||||||
|
def get_lpm_time(self):
|
||||||
|
# us
|
||||||
|
return float(self.lpm_time / 1000000.0)
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
from . import CustomInstruction
|
||||||
|
|
||||||
|
class IncrementCustomMetric(CustomInstruction):
|
||||||
|
"""
|
||||||
|
IncrementCustomMetric() increments a custom metric
|
||||||
|
params for the equivalent function call:
|
||||||
|
- custom metric id
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, call_operation):
|
||||||
|
super().__init__(call_operation)
|
||||||
|
|
||||||
|
self.metric_id = self.get_arg(0)
|
||||||
|
|
||||||
|
if not self._vmstate.custom_metrics.exists(self.metric_id):
|
||||||
|
raise Exception(f"Metric #{self.metric_id} not found")
|
||||||
|
|
||||||
|
self.val = int(self.get_arg(1)) if len(self.args) > 1 else 1
|
||||||
|
|
||||||
|
name = self._vmstate.custom_metrics.get_name(self.metric_id)
|
||||||
|
self._str_params = [name, self.val]
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
super().run(update_program_counter)
|
||||||
|
self._vmstate.custom_metrics.increment(self.metric_id, self.val)
|
||||||
37
ScEpTIC/AST/transformations/base/instructions/interrupts.py
Normal file
37
ScEpTIC/AST/transformations/base/instructions/interrupts.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from . import CustomInstruction
|
||||||
|
|
||||||
|
class InterruptsDisableFiring(CustomInstruction):
|
||||||
|
"""
|
||||||
|
InterruptsDisableFiring() disable interrupts firing
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
self._vmstate.interrupts_manager.disable_firing()
|
||||||
|
super().run(update_program_counter)
|
||||||
|
|
||||||
|
class InterruptsEnableFiring(CustomInstruction):
|
||||||
|
"""
|
||||||
|
InterruptsEnableFiring() enable interrupts firing
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
self._vmstate.interrupts_manager.enable_firing()
|
||||||
|
super().run(update_program_counter)
|
||||||
|
|
||||||
|
class InterruptsDisable(CustomInstruction):
|
||||||
|
"""
|
||||||
|
InterruptsDisable() disable interrupts
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
self._vmstate.interrupts_manager.disable()
|
||||||
|
super().run(update_program_counter)
|
||||||
|
|
||||||
|
class InterruptsEnable(CustomInstruction):
|
||||||
|
"""
|
||||||
|
InterruptsEnable() enable interrupts
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
self._vmstate.interrupts_manager.enable()
|
||||||
|
super().run(update_program_counter)
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
from . import CustomInstruction
|
||||||
|
|
||||||
|
class PrintVMDump(CustomInstruction):
|
||||||
|
"""
|
||||||
|
PrintVMDump() prints ScEpTIC memory dump
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
print("START DUMP >")
|
||||||
|
print()
|
||||||
|
print(self._vmstate.register_file.pc.pc_tree())
|
||||||
|
print("[REGISTER FILE]")
|
||||||
|
print(self._vmstate.register_file.get_visual_dump())
|
||||||
|
print()
|
||||||
|
print("[GST]")
|
||||||
|
print(self._vmstate.memory.gst.get_visual_dump())
|
||||||
|
print()
|
||||||
|
print("[STACK]")
|
||||||
|
print(self._vmstate.memory.stack.get_visual_dump())
|
||||||
|
print()
|
||||||
|
print("[HEAP]")
|
||||||
|
print(self._vmstate.memory.heap.get_visual_dump())
|
||||||
|
print()
|
||||||
|
print("END DUMP <")
|
||||||
|
|
||||||
|
super().run(update_program_counter)
|
||||||
22
ScEpTIC/AST/transformations/base/instructions/printf.py
Normal file
22
ScEpTIC/AST/transformations/base/instructions/printf.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from . import CustomInstruction
|
||||||
|
|
||||||
|
class Printf(CustomInstruction):
|
||||||
|
"""
|
||||||
|
printf() function
|
||||||
|
"""
|
||||||
|
|
||||||
|
tick_count = 0
|
||||||
|
stdout_enabled = True
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
if self.stdout_enabled:
|
||||||
|
format_str = self.get_arg(0)
|
||||||
|
args = tuple([self.get_arg(i) for i in range(1, self.argc())])
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("[PRINTF]: {}".format(format_str % args))
|
||||||
|
except TypeError:
|
||||||
|
print("[PRINTF]: {}, {}".format(format_str, args))
|
||||||
|
|
||||||
|
|
||||||
|
super().run(update_program_counter)
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
from ScEpTIC.emulator.energy.mcu.options import MCUClockCycleAction
|
||||||
|
|
||||||
|
from . import CustomInstruction
|
||||||
|
|
||||||
|
class SimulateClockCycles(CustomInstruction):
|
||||||
|
"""
|
||||||
|
SimulateClockCycles() simulates the execution of a given number of clock cycles
|
||||||
|
params for the equivalent function call:
|
||||||
|
- number of clock cycles to simulate
|
||||||
|
- ratio of register operations
|
||||||
|
- ratio of volatile memory accesses
|
||||||
|
- ratio of non-volatile memory accesses
|
||||||
|
"""
|
||||||
|
entities = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reset(cls):
|
||||||
|
for entity in cls.entities:
|
||||||
|
entity.executed_instructions = 0
|
||||||
|
|
||||||
|
def __init__(self, call_operation):
|
||||||
|
super().__init__(call_operation)
|
||||||
|
|
||||||
|
self.n_cycles = self.get_arg(0)
|
||||||
|
self.ratios = [float(self.get_arg(1)), float(self.get_arg(2)), float(self.get_arg(3))]
|
||||||
|
|
||||||
|
if sum(self.ratios) != 1.0:
|
||||||
|
raise Exception("Operation ratios do not sum up to 1")
|
||||||
|
|
||||||
|
self.t_base, self.schedule = self.__generate_schedule(self.ratios)
|
||||||
|
|
||||||
|
self.executed_instructions = 0
|
||||||
|
self.current_instruction = None
|
||||||
|
|
||||||
|
self.entities.append(self)
|
||||||
|
|
||||||
|
def get_instruction_type(self):
|
||||||
|
"""
|
||||||
|
Returns the instruction to execute
|
||||||
|
"""
|
||||||
|
return self.current_instruction
|
||||||
|
|
||||||
|
def get_simulation_data(self):
|
||||||
|
return {'schedule': self.schedule, 'cycles': self.n_cycles, 'base_period': self.t_base}
|
||||||
|
|
||||||
|
def __generate_schedule(self, ratios):
|
||||||
|
# Possible periods length: possible base periods that lead to integer deadlines
|
||||||
|
t_bases = [10, 20, 50, 100, 200, 500, 1000]
|
||||||
|
ops = [MCUClockCycleAction.NO_MEMORY_ACCESS, MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, MCUClockCycleAction.NON_VOLATILE_MEMORY_ACCESS]
|
||||||
|
|
||||||
|
t_base = 0
|
||||||
|
periods = []
|
||||||
|
int_ratios = []
|
||||||
|
|
||||||
|
for t in t_bases:
|
||||||
|
# Create integer ratios to identify the minimum possible base period
|
||||||
|
int_ratios = [int(x * t) for x in ratios if x * t == int(x * t)]
|
||||||
|
|
||||||
|
# Check if all operations have an integer ratio
|
||||||
|
if len(int_ratios) == len(ratios):
|
||||||
|
t_base = t
|
||||||
|
# Generate periods, accounting for operations never executed (i.e. ratio = 0)
|
||||||
|
periods = [t / x if x != 0 else t_base+1 for x in int_ratios]
|
||||||
|
break
|
||||||
|
|
||||||
|
# If t_base not set -> an operation has a non-integer period -> not supported (max 2 numbers after comma)
|
||||||
|
if t_base == 0:
|
||||||
|
raise Exception("Operation ratios not supported in a period of up to 100ops!")
|
||||||
|
|
||||||
|
schedule = []
|
||||||
|
|
||||||
|
# Initial deadlines
|
||||||
|
deadlines = [x for x in periods]
|
||||||
|
|
||||||
|
# Generate schedule
|
||||||
|
for _ in range(0, t_base):
|
||||||
|
# Element with the lowest deadline
|
||||||
|
op = min(range(len(deadlines)), key=deadlines.__getitem__)
|
||||||
|
|
||||||
|
schedule.append(ops[op])
|
||||||
|
|
||||||
|
# Update deadline
|
||||||
|
deadlines[op] = deadlines[op] + periods[op]
|
||||||
|
|
||||||
|
n = {}
|
||||||
|
error = False
|
||||||
|
# Test schedule
|
||||||
|
for i in range(len(ops)):
|
||||||
|
# Count occurrences of operation ops[i]
|
||||||
|
n[i] = len([x for x in schedule if x == ops[i]])
|
||||||
|
|
||||||
|
if n[i] != int_ratios[i]:
|
||||||
|
error = True
|
||||||
|
if error:
|
||||||
|
raise Exception(f"Wrong schedule: {ops}\nRequested: {int_ratios}\nActual: {n}\n")
|
||||||
|
|
||||||
|
return t_base, schedule
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
# Set current instruction
|
||||||
|
self.current_instruction = self.schedule[self.executed_instructions % self.t_base]
|
||||||
|
|
||||||
|
# Increment executed instructions
|
||||||
|
self.executed_instructions += 1
|
||||||
|
|
||||||
|
# Update program counter if end reached
|
||||||
|
end_reached = self.executed_instructions >= self.n_cycles
|
||||||
|
|
||||||
|
# Reset if end reached
|
||||||
|
if end_reached:
|
||||||
|
self.executed_instructions = 0
|
||||||
|
|
||||||
|
super().run(update_program_counter=end_reached)
|
||||||
41
ScEpTIC/AST/transformations/base/instructions/timers.py
Normal file
41
ScEpTIC/AST/transformations/base/instructions/timers.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from . import CustomInstruction
|
||||||
|
|
||||||
|
class TimerCustomInstruction(CustomInstruction):
|
||||||
|
"""
|
||||||
|
Timer custom instruction
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def timer_name(self):
|
||||||
|
return self.get_arg(0)
|
||||||
|
|
||||||
|
|
||||||
|
class TimerStart(TimerCustomInstruction):
|
||||||
|
"""
|
||||||
|
TimerStart() starts a given timer
|
||||||
|
"""
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
self._vmstate.timers_manager.start_timer(self.timer_name)
|
||||||
|
super().run(update_program_counter)
|
||||||
|
|
||||||
|
|
||||||
|
class TimerStop(TimerCustomInstruction):
|
||||||
|
"""
|
||||||
|
TimerStop() stops a given timer
|
||||||
|
"""
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
self._vmstate.timers_manager.stop_timer(self.timer_name)
|
||||||
|
super().run(update_program_counter)
|
||||||
|
|
||||||
|
|
||||||
|
class TimerSetPeriod(TimerCustomInstruction):
|
||||||
|
"""
|
||||||
|
TimerSetPeriod() stops a given timer
|
||||||
|
"""
|
||||||
|
def __init__(self, call_operation):
|
||||||
|
super().__init__(call_operation)
|
||||||
|
self.period = float(self.get_arg(1))
|
||||||
|
|
||||||
|
def run(self, update_program_counter=True):
|
||||||
|
period = float(self.get_arg(1))
|
||||||
|
self._vmstate.timers_manager.set_timer_period(self.timer_name, self.period)
|
||||||
|
super().run(update_program_counter)
|
||||||
45
ScEpTIC/AST/transformations/base/main.py
Normal file
45
ScEpTIC/AST/transformations/base/main.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from ScEpTIC.AST.elements.instructions.other_operations import CallOperation
|
||||||
|
|
||||||
|
from ScEpTIC.AST.transformations.base.instructions.custom_log import CustomLog, CustomLogDump
|
||||||
|
from ScEpTIC.AST.transformations.base.instructions.printf import Printf
|
||||||
|
from ScEpTIC.AST.transformations.base.instructions.enter_mcu_lpm import EnterMCULPM
|
||||||
|
from ScEpTIC.AST.transformations.base.instructions.increment_custom_metric import IncrementCustomMetric
|
||||||
|
from ScEpTIC.AST.transformations.base.instructions.simulate_clock_cycles import SimulateClockCycles
|
||||||
|
from ScEpTIC.AST.transformations.base.instructions.print_vm_dump import PrintVMDump
|
||||||
|
from ScEpTIC.AST.transformations.base.instructions.interrupts import InterruptsEnable, InterruptsDisable, InterruptsEnableFiring, InterruptsDisableFiring
|
||||||
|
from ScEpTIC.AST.transformations.base.instructions.timers import TimerStart, TimerStop, TimerSetPeriod
|
||||||
|
|
||||||
|
def apply_transformation(functions, vmstate, f_declarations):
|
||||||
|
"""
|
||||||
|
:param functions: ScEpTIC AST functions
|
||||||
|
:param vmstate: vmstate
|
||||||
|
"""
|
||||||
|
config = vmstate.config.ast_transformations.base
|
||||||
|
|
||||||
|
function_map = {
|
||||||
|
config.simulate_clock_cycles_function_name: SimulateClockCycles,
|
||||||
|
config.increment_custom_metric_function_name: IncrementCustomMetric,
|
||||||
|
config.enter_mcu_lpm_function_name: EnterMCULPM,
|
||||||
|
config.print_dump_function_name: PrintVMDump,
|
||||||
|
config.interrupts_enable_firing_function_name: InterruptsEnableFiring,
|
||||||
|
config.interrupts_disable_firing_function_name: InterruptsDisableFiring,
|
||||||
|
config.interrupts_enable_function_name: InterruptsEnable,
|
||||||
|
config.interrupts_disable_function_name: InterruptsDisable,
|
||||||
|
config.timer_start_function_name: TimerStart,
|
||||||
|
config.timer_stop_function_name: TimerStop,
|
||||||
|
config.timer_set_period_function_name: TimerSetPeriod,
|
||||||
|
config.printf_function_name: Printf,
|
||||||
|
config.custom_log_function_name: CustomLog,
|
||||||
|
config.custom_log_dump_function_name: CustomLogDump,
|
||||||
|
}
|
||||||
|
|
||||||
|
for f_name, function in functions.items():
|
||||||
|
for n, instruction in enumerate(function.body):
|
||||||
|
if isinstance(instruction, CallOperation):
|
||||||
|
if instruction.name in function_map.keys():
|
||||||
|
op = function_map[instruction.name](instruction)
|
||||||
|
function.body[n] = op
|
||||||
|
|
||||||
|
for f_name in function_map.keys():
|
||||||
|
if f_name in f_declarations:
|
||||||
|
del f_declarations[f_name]
|
||||||
16
ScEpTIC/AST/transformations/mementos_memory_map/config.py
Normal file
16
ScEpTIC/AST/transformations/mementos_memory_map/config.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from ScEpTIC.config.base_config import ScEpTICBaseConfig
|
||||||
|
|
||||||
|
class MementosMemoryMapConfig(ScEpTICBaseConfig):
|
||||||
|
"""
|
||||||
|
Configuration for the Mementos Memory Map Transformation
|
||||||
|
"""
|
||||||
|
_config_domain = {
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, main_config):
|
||||||
|
super().__init__(main_config)
|
||||||
|
|
||||||
|
|
||||||
|
# Returns the configuration
|
||||||
|
def get_config(main_config):
|
||||||
|
return MementosMemoryMapConfig(main_config)
|
||||||
24
ScEpTIC/AST/transformations/mementos_memory_map/main.py
Normal file
24
ScEpTIC/AST/transformations/mementos_memory_map/main.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import copy
|
||||||
|
|
||||||
|
from ScEpTIC.AST.elements.instructions.other_operations import CallOperation
|
||||||
|
from ScEpTIC.AST.misc.virtual_memory_enum import VirtualMemoryEnum
|
||||||
|
|
||||||
|
|
||||||
|
def apply_transformation(functions, vmstate, f_declarations):
|
||||||
|
"""
|
||||||
|
Sets the correct memory map for Mementos
|
||||||
|
"""
|
||||||
|
checkpoint_function_name = vmstate.config.state_retention.state_save_function_name
|
||||||
|
|
||||||
|
registers_to_save = set([x for x in range(0, vmstate.config.register_file.n_physical_registers)])
|
||||||
|
|
||||||
|
for function in functions.values():
|
||||||
|
for instruction in function.body:
|
||||||
|
if "virtual_memory_target" in dir(instruction):
|
||||||
|
instruction.virtual_memory_target = VirtualMemoryEnum.VOLATILE
|
||||||
|
|
||||||
|
if isinstance(instruction, CallOperation) and instruction.name == checkpoint_function_name:
|
||||||
|
instruction.checkpoint_save_pc = True
|
||||||
|
instruction.checkpoint_save_esp = True
|
||||||
|
instruction.checkpoint_save_ram = True
|
||||||
|
instruction.checkpoint_save_regs = copy.deepcopy(registers_to_save)
|
||||||
16
ScEpTIC/AST/transformations/nv_memory_map/config.py
Normal file
16
ScEpTIC/AST/transformations/nv_memory_map/config.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from ScEpTIC.config.base_config import ScEpTICBaseConfig
|
||||||
|
|
||||||
|
class NVMemoryMapConfig(ScEpTICBaseConfig):
|
||||||
|
"""
|
||||||
|
Configuration for the NV Memory Map Transformation
|
||||||
|
"""
|
||||||
|
_config_domain = {
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, main_config):
|
||||||
|
super().__init__(main_config)
|
||||||
|
|
||||||
|
|
||||||
|
# Returns the configuration
|
||||||
|
def get_config(main_config):
|
||||||
|
return NVMemoryMapConfig(main_config)
|
||||||
31
ScEpTIC/AST/transformations/nv_memory_map/main.py
Normal file
31
ScEpTIC/AST/transformations/nv_memory_map/main.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from ScEpTIC.AST.elements.instructions.memory_operations import StoreOperation
|
||||||
|
from ScEpTIC.AST.misc.virtual_memory_enum import VirtualMemoryEnum
|
||||||
|
from ScEpTIC.AST.transformations import virtual_memory
|
||||||
|
|
||||||
|
def apply_transformation(functions, vmstate, f_declarations):
|
||||||
|
"""
|
||||||
|
Sets all the memory operations to target NVM
|
||||||
|
"""
|
||||||
|
|
||||||
|
virtual_memory.apply_transformation(functions, vmstate)
|
||||||
|
|
||||||
|
checkpoint_function_name = vmstate.config.state_retention.state_save_function_name
|
||||||
|
|
||||||
|
for function in functions.values():
|
||||||
|
to_remove = []
|
||||||
|
|
||||||
|
for instruction in function.body:
|
||||||
|
# Set all to NVM
|
||||||
|
if "virtual_memory_target" in dir(instruction):
|
||||||
|
instruction.virtual_memory_target = VirtualMemoryEnum.NON_VOLATILE
|
||||||
|
|
||||||
|
# Note: dummy writes resolve uncertainty and we need them to apply our versioning technique
|
||||||
|
# Remove volatile copies
|
||||||
|
if isinstance(instruction, StoreOperation):
|
||||||
|
if instruction.is_virtual_memory_copy:
|
||||||
|
to_remove.append(instruction)
|
||||||
|
|
||||||
|
for instruction in to_remove:
|
||||||
|
function.body.remove(instruction)
|
||||||
|
|
||||||
|
function.update_labels()
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
from ScEpTIC.config.base_config import ScEpTICBaseConfig
|
||||||
|
|
||||||
|
class PlaceStaticFrequencyScalingConfig(ScEpTICBaseConfig):
|
||||||
|
"""
|
||||||
|
Configuration for placing static frequency scaling operations
|
||||||
|
"""
|
||||||
|
_config_domain = {
|
||||||
|
'scale_up_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'scale_down_function_name': {'class': str, 'mode': 'set'},
|
||||||
|
'scale_up_frequency': {'class': str, 'mode': 'set'},
|
||||||
|
'scale_down_frequency': {'class': str, 'mode': 'set'},
|
||||||
|
'scale_up_instructions': {'class': int, 'mode': 'set'},
|
||||||
|
'scale_down_instructions': {'class': int, 'mode': 'set'},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, main_config):
|
||||||
|
super().__init__(main_config)
|
||||||
|
self.__ir_prefix = f"{main_config.program.ir_function_prefix}"
|
||||||
|
self.scale_up_function_name = f"{self.__ir_prefix}scale_up"
|
||||||
|
self.scale_down_function_name = f"{self.__ir_prefix}scale_down"
|
||||||
|
|
||||||
|
self.scale_up_frequency = '16MHz'
|
||||||
|
self.scale_down_frequency = '8MHz'
|
||||||
|
|
||||||
|
self.scale_up_instructions = 3
|
||||||
|
self.scale_down_instructions = 3
|
||||||
|
|
||||||
|
self._register_config_callback("scale_up_function_name", self._append_ir_callback)
|
||||||
|
self._register_config_callback("scale_down_function_name", self._append_ir_callback)
|
||||||
|
|
||||||
|
def _append_ir_callback(self, config_name, config_value):
|
||||||
|
setattr(self, config_name, f"{self.__ir_prefix}{config_value}")
|
||||||
|
|
||||||
|
# Returns the configuration
|
||||||
|
def get_config(main_config):
|
||||||
|
return PlaceStaticFrequencyScalingConfig(main_config)
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from ScEpTIC.AST.transformations.place_static_frequency_scaling_ops.scaling_instructions import ScaleUpInstruction, ScaleDownInstruction
|
||||||
|
from ScEpTIC.AST.transformations.virtual_memory.parsers.ast_parser import ASTParser
|
||||||
|
from ScEpTIC.AST.transformations.virtual_memory.parsers.memory_tags_parser import MemoryTagsParser
|
||||||
|
from ScEpTIC.AST.elements.instructions.memory_operations import LoadOperation, StoreOperation
|
||||||
|
from ScEpTIC.AST.elements.instructions.other_operations import CallOperation
|
||||||
|
from ScEpTIC.emulator.energy.system_energy_model import SystemEnergyModel
|
||||||
|
|
||||||
|
def apply_transformation(functions, vmstate, f_declarations):
|
||||||
|
"""
|
||||||
|
:param functions: ScEpTIC AST functions
|
||||||
|
:param vmstate: vmstate
|
||||||
|
"""
|
||||||
|
config = vmstate.config.ast_transformations.place_static_frequency_scaling_ops
|
||||||
|
system_model = vmstate.config.analysis.energy.system_model
|
||||||
|
|
||||||
|
if not isinstance(system_model, SystemEnergyModel):
|
||||||
|
raise ConfigurationException(f"SystemEnergyModel required by place_static_frequency_scaling_ops transformation!")
|
||||||
|
|
||||||
|
for f_name, function in functions.items():
|
||||||
|
for n, instruction in enumerate(function.body):
|
||||||
|
if isinstance(instruction, CallOperation):
|
||||||
|
if instruction.name == config.scale_up_function_name:
|
||||||
|
op = ScaleUpInstruction(instruction)
|
||||||
|
function.body[n] = op
|
||||||
|
elif instruction.name == config.scale_down_function_name:
|
||||||
|
op = ScaleDownInstruction(instruction)
|
||||||
|
function.body[n] = op
|
||||||
|
|
||||||
|
for f_name in [config.scale_up_function_name, config.scale_down_function_name]:
|
||||||
|
if f_name in f_declarations:
|
||||||
|
del f_declarations[f_name]
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
from ScEpTIC.AST.elements.instruction import Instruction
|
||||||
|
from ScEpTIC.emulator.energy.mcu.options import MCUClockCycleAction
|
||||||
|
from ScEpTIC.emulator.energy.options import OpModeName
|
||||||
|
|
||||||
|
class StaticScalingInstruction(Instruction):
|
||||||
|
|
||||||
|
def __init__(self, call_operation):
|
||||||
|
super().__init__()
|
||||||
|
self.basic_block_id = call_operation.basic_block_id
|
||||||
|
self.label = call_operation.label
|
||||||
|
self.preds = call_operation.preds
|
||||||
|
self.metadata = call_operation.metadata
|
||||||
|
self.target = None
|
||||||
|
self._omit_target = True
|
||||||
|
self.virtual_memory_target = call_operation.virtual_memory_target
|
||||||
|
|
||||||
|
|
||||||
|
def get_val(self):
|
||||||
|
#print(self._vmstate.register_file.pc)
|
||||||
|
#print(f"Scaling frequency to {self.frequency} {self.__class__.__name__}")
|
||||||
|
system_model = self._vmstate.config.analysis.energy.system_model
|
||||||
|
|
||||||
|
# Simulate scaling instructions execution
|
||||||
|
for _ in range(self.n_ticks):
|
||||||
|
system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name)
|
||||||
|
|
||||||
|
# Change frequency
|
||||||
|
system_model.mcu.set_frequency(self.frequency)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ScaleUpInstruction(StaticScalingInstruction):
|
||||||
|
def __init__(self, call_operation):
|
||||||
|
super().__init__(call_operation)
|
||||||
|
self.n_ticks = self._vmstate.config.ast_transformations.place_static_frequency_scaling_ops.scale_up_instructions
|
||||||
|
self.frequency = self._vmstate.config.ast_transformations.place_static_frequency_scaling_ops.scale_up_frequency
|
||||||
|
self.op_mode_name = OpModeName.STATIC_SCALE_UP
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = super().__str__()
|
||||||
|
retstr += 'static_scale_up()'
|
||||||
|
return retstr
|
||||||
|
|
||||||
|
|
||||||
|
class ScaleDownInstruction(StaticScalingInstruction):
|
||||||
|
def __init__(self, call_operation):
|
||||||
|
super().__init__(call_operation)
|
||||||
|
self.n_ticks = self._vmstate.config.ast_transformations.place_static_frequency_scaling_ops.scale_down_instructions
|
||||||
|
self.frequency = self._vmstate.config.ast_transformations.place_static_frequency_scaling_ops.scale_down_frequency
|
||||||
|
self.op_mode_name = OpModeName.STATIC_SCALE_DOWN
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
retstr = super().__str__()
|
||||||
|
retstr += 'static_scale_down()'
|
||||||
|
return retstr
|
||||||
0
ScEpTIC/AST/transformations/ratchet/__init__.py
Normal file
0
ScEpTIC/AST/transformations/ratchet/__init__.py
Normal file
362
ScEpTIC/AST/transformations/ratchet/checkpoint_placer.py
Normal file
362
ScEpTIC/AST/transformations/ratchet/checkpoint_placer.py
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
from ScEpTIC.AST.elements.instructions.memory_operations import LoadOperation, StoreOperation
|
||||||
|
from ScEpTIC.AST.elements.instructions.other_operations import CallOperation
|
||||||
|
from ScEpTIC.AST.elements.types import BaseType
|
||||||
|
from ScEpTIC.AST.misc.virtual_memory_enum import VirtualMemoryEnum
|
||||||
|
from ScEpTIC.AST.transformations.ratchet.memory_tags_identifier import MemoryTagsIdentifier
|
||||||
|
|
||||||
|
|
||||||
|
class CheckpointPlacer:
|
||||||
|
|
||||||
|
def __init__(self, functions, functions_with_outside_frame_accesses, checkpoint_function_name):
|
||||||
|
self.functions = functions
|
||||||
|
self.functions_with_outside_frame_accesses = []
|
||||||
|
self.checkpoint_function_name = checkpoint_function_name
|
||||||
|
|
||||||
|
self.function_body_slices = {}
|
||||||
|
|
||||||
|
# adjust for LLVM IR
|
||||||
|
for function_name in functions_with_outside_frame_accesses:
|
||||||
|
name = f"@{function_name}"
|
||||||
|
self.functions_with_outside_frame_accesses.append(name)
|
||||||
|
|
||||||
|
|
||||||
|
def place_checkpoints(self):
|
||||||
|
"""
|
||||||
|
Places checkpoints in each function
|
||||||
|
"""
|
||||||
|
self._identify_memory_tags()
|
||||||
|
self._place_checkpoints_for_function_calls()
|
||||||
|
self._remove_duplicate_checkpoints()
|
||||||
|
self._split_function_bodies()
|
||||||
|
self._place_checkpoints()
|
||||||
|
self._synchronize_slices_to_body()
|
||||||
|
self._remove_duplicate_checkpoints()
|
||||||
|
self._set_nvm_default_memory()
|
||||||
|
|
||||||
|
|
||||||
|
def print_slices(self):
|
||||||
|
"""
|
||||||
|
Prints all the slices
|
||||||
|
"""
|
||||||
|
for name, slices in self.function_body_slices.items():
|
||||||
|
print(name)
|
||||||
|
|
||||||
|
for slice_index, slice in enumerate(slices):
|
||||||
|
print(f" --- Slice {slice_index} ---")
|
||||||
|
for index, instruction in enumerate(slice):
|
||||||
|
print(f" {index}: {instruction}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def _set_nvm_default_memory(self):
|
||||||
|
"""
|
||||||
|
Sets each instruction to target NVM
|
||||||
|
"""
|
||||||
|
|
||||||
|
for function in self.functions.values():
|
||||||
|
for instruction in function.body:
|
||||||
|
if "virtual_memory_target" in dir(instruction):
|
||||||
|
instruction.virtual_memory_target = VirtualMemoryEnum.NON_VOLATILE
|
||||||
|
|
||||||
|
|
||||||
|
def _synchronize_slices_to_body(self):
|
||||||
|
"""
|
||||||
|
Updates the functions body with the modified slices
|
||||||
|
"""
|
||||||
|
|
||||||
|
for name, slices in self.function_body_slices.items():
|
||||||
|
instructions = []
|
||||||
|
|
||||||
|
for slice in slices:
|
||||||
|
for instruction in slice:
|
||||||
|
instructions.append(instruction)
|
||||||
|
|
||||||
|
self.functions[name].update_body(instructions)
|
||||||
|
|
||||||
|
|
||||||
|
def _place_checkpoints(self):
|
||||||
|
"""
|
||||||
|
Places checkpoints in function slices
|
||||||
|
"""
|
||||||
|
for slices in self.function_body_slices.values():
|
||||||
|
for slice in slices:
|
||||||
|
self._place_checkpoints_in_slice(slice)
|
||||||
|
|
||||||
|
|
||||||
|
def _identify_memory_tags(self):
|
||||||
|
"""
|
||||||
|
Identifies the memory tag of each instruction
|
||||||
|
"""
|
||||||
|
memory_tags = MemoryTagsIdentifier(self.functions)
|
||||||
|
memory_tags.resolve_memory_tags()
|
||||||
|
|
||||||
|
|
||||||
|
def _split_function_bodies(self):
|
||||||
|
"""
|
||||||
|
Splits the function body in slices
|
||||||
|
"""
|
||||||
|
|
||||||
|
for name, function in self.functions.items():
|
||||||
|
|
||||||
|
# Skip builtins processing
|
||||||
|
if function.is_builtin or function.is_input:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.function_body_slices[name] = []
|
||||||
|
|
||||||
|
slice = []
|
||||||
|
|
||||||
|
for instruction in function.body:
|
||||||
|
slice.append(instruction)
|
||||||
|
|
||||||
|
# slice end reached -> add to slices and start a new slice
|
||||||
|
if isinstance(instruction, CallOperation) and instruction.name == self.checkpoint_function_name:
|
||||||
|
self.function_body_slices[name].append(slice)
|
||||||
|
slice = []
|
||||||
|
|
||||||
|
# account for last slice
|
||||||
|
if len(slice) > 0:
|
||||||
|
self.function_body_slices[name].append(slice)
|
||||||
|
|
||||||
|
|
||||||
|
def _identify_possible_checkpoints(self, slice):
|
||||||
|
"""
|
||||||
|
Scans a slice and identifies all the pairs of store/load, which makes idempotent sections.
|
||||||
|
|
||||||
|
1. LOAD
|
||||||
|
2. LOAD
|
||||||
|
3. STORE
|
||||||
|
4. STORE
|
||||||
|
5. LOAD
|
||||||
|
Lines 3-4 makes an idempotent section, so we need a checkpoint between line 2 and 3
|
||||||
|
"""
|
||||||
|
checkpoint_ranges = {}
|
||||||
|
|
||||||
|
reads = {}
|
||||||
|
writes = {}
|
||||||
|
|
||||||
|
# scan in reverse order
|
||||||
|
for index, instruction in reversed(list(enumerate(slice))):
|
||||||
|
|
||||||
|
if isinstance(instruction, LoadOperation):
|
||||||
|
memory_tag = instruction.memory_tag
|
||||||
|
|
||||||
|
# if there is a write after the read -> checkpoint needed
|
||||||
|
if memory_tag in writes:
|
||||||
|
write = writes[memory_tag]
|
||||||
|
del writes[memory_tag]
|
||||||
|
|
||||||
|
if memory_tag not in checkpoint_ranges:
|
||||||
|
checkpoint_ranges[memory_tag] = []
|
||||||
|
|
||||||
|
data = {"locations": set(range(index, write)), "memory_tag": memory_tag, "covered": False}
|
||||||
|
|
||||||
|
checkpoint_ranges[memory_tag].append(data)
|
||||||
|
|
||||||
|
if isinstance(instruction, StoreOperation):
|
||||||
|
memory_tag = instruction.memory_tag
|
||||||
|
# only last identified store needed
|
||||||
|
writes[memory_tag] = index
|
||||||
|
|
||||||
|
return checkpoint_ranges
|
||||||
|
|
||||||
|
|
||||||
|
def _identify_ordered_possible_checkpoints(self, slice):
|
||||||
|
"""
|
||||||
|
Returns an ordered list of all the possible checkpoint locations
|
||||||
|
"""
|
||||||
|
possible_checkpoints = self._identify_possible_checkpoints(slice)
|
||||||
|
|
||||||
|
checkpoint_locations = {}
|
||||||
|
|
||||||
|
for data in possible_checkpoints.values():
|
||||||
|
for element in data:
|
||||||
|
index = len(element["locations"])
|
||||||
|
|
||||||
|
if index not in checkpoint_locations:
|
||||||
|
checkpoint_locations[index] = []
|
||||||
|
|
||||||
|
checkpoint_locations[index].append(element)
|
||||||
|
|
||||||
|
ordered_locations = []
|
||||||
|
|
||||||
|
for _, possible_locations in sorted(checkpoint_locations.items()):
|
||||||
|
for possible_location in possible_locations:
|
||||||
|
ordered_locations.append(possible_location)
|
||||||
|
|
||||||
|
return ordered_locations
|
||||||
|
|
||||||
|
|
||||||
|
def _place_checkpoints_in_slice(self, slice):
|
||||||
|
"""
|
||||||
|
Places checkpoints to create idempotent code sections in the provided slice of code
|
||||||
|
"""
|
||||||
|
checkpoints = self._identify_checkpoints(slice)
|
||||||
|
|
||||||
|
placed_checkpoints = 0
|
||||||
|
|
||||||
|
for checkpoint_index in checkpoints:
|
||||||
|
index = checkpoint_index + placed_checkpoints
|
||||||
|
|
||||||
|
# instruction at index
|
||||||
|
instruction = slice[index]
|
||||||
|
|
||||||
|
# build checkpoint
|
||||||
|
new_checkpoint = self._create_checkpoint()
|
||||||
|
new_checkpoint.basic_block_id = instruction.basic_block_id
|
||||||
|
|
||||||
|
# update labels
|
||||||
|
new_checkpoint.label = instruction.label
|
||||||
|
instruction.label = None
|
||||||
|
|
||||||
|
slice.insert(index, new_checkpoint)
|
||||||
|
|
||||||
|
placed_checkpoints += 1
|
||||||
|
|
||||||
|
|
||||||
|
def _identify_checkpoints(self, slice):
|
||||||
|
"""
|
||||||
|
Returns a list that contains the indexes where to place checkpoints.
|
||||||
|
The list is ordered.
|
||||||
|
|
||||||
|
Approximation: uses a map colouring-like algorithm.
|
||||||
|
Assumption valid for our benchmarks, as non-idempotent sections cannot be avoided inside loops
|
||||||
|
"""
|
||||||
|
ordered_locations = self._identify_ordered_possible_checkpoints(slice)
|
||||||
|
|
||||||
|
checkpoints = []
|
||||||
|
|
||||||
|
for location in ordered_locations:
|
||||||
|
# skip if already covered
|
||||||
|
if location["covered"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
candidates = {}
|
||||||
|
|
||||||
|
# tries each possible checkpoint placement
|
||||||
|
for candidate_checkpoint in location["locations"]:
|
||||||
|
candidates[candidate_checkpoint] = []
|
||||||
|
|
||||||
|
# check which locations the current candidate covers
|
||||||
|
for next_location in ordered_locations:
|
||||||
|
# skip previous locations and already covered locations
|
||||||
|
# Note: the current location is instead considered
|
||||||
|
if next_location["covered"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if candidate_checkpoint in next_location["locations"]:
|
||||||
|
candidates[candidate_checkpoint].append(next_location)
|
||||||
|
|
||||||
|
# order possible checkpoint placements by covered locations
|
||||||
|
ordered_candidates = {}
|
||||||
|
# parse in order of checkpoint location
|
||||||
|
for candidate_checkpoint, data in sorted(candidates.items()):
|
||||||
|
index = len(data)
|
||||||
|
|
||||||
|
if index not in ordered_candidates:
|
||||||
|
ordered_candidates[index] = []
|
||||||
|
|
||||||
|
ordered_candidates[index].append(candidate_checkpoint)
|
||||||
|
|
||||||
|
# get the elements that cover the highest number of locations
|
||||||
|
max_covered_locations = max(ordered_candidates.keys())
|
||||||
|
# get the lowest location (instruction index), that is, where a lower number of live registers are (LOAD)
|
||||||
|
# Note: placing a checkpoint before a STORE requires to save two registers: target and value
|
||||||
|
target_checkpoint = ordered_candidates[max_covered_locations][0]
|
||||||
|
|
||||||
|
checkpoints.append(target_checkpoint)
|
||||||
|
|
||||||
|
# mark as covered the covered locations
|
||||||
|
for location in ordered_locations:
|
||||||
|
if target_checkpoint in location["locations"]:
|
||||||
|
location["covered"] = True
|
||||||
|
|
||||||
|
return sorted(checkpoints)
|
||||||
|
|
||||||
|
|
||||||
|
def _place_checkpoints_for_function_calls(self):
|
||||||
|
"""
|
||||||
|
Places a checkpoint before and after function calls that have outside frame accesses
|
||||||
|
"""
|
||||||
|
|
||||||
|
for function in self.functions.values():
|
||||||
|
|
||||||
|
# Skip builtins processing
|
||||||
|
if function.is_builtin or function.is_input:
|
||||||
|
continue
|
||||||
|
|
||||||
|
target_calls = []
|
||||||
|
|
||||||
|
for index, instruction in enumerate(function.body):
|
||||||
|
if isinstance(instruction, CallOperation) and instruction.name in self.functions_with_outside_frame_accesses:
|
||||||
|
target_calls.append(index)
|
||||||
|
|
||||||
|
new_checkpoints = 0
|
||||||
|
|
||||||
|
for call_index in sorted(target_calls):
|
||||||
|
index = call_index + new_checkpoints
|
||||||
|
target_call = function.body[index]
|
||||||
|
|
||||||
|
# create checkpoints
|
||||||
|
checkpoint_after = self._create_checkpoint()
|
||||||
|
checkpoint_before = self._create_checkpoint()
|
||||||
|
|
||||||
|
# set basic block id
|
||||||
|
checkpoint_after.basic_block_id = target_call.basic_block_id
|
||||||
|
checkpoint_before.basic_block_id = target_call.basic_block_id
|
||||||
|
|
||||||
|
# adjust label
|
||||||
|
checkpoint_before.label = target_call.label
|
||||||
|
target_call.label = None
|
||||||
|
|
||||||
|
# add checkpoint
|
||||||
|
function.body.insert(index, checkpoint_before)
|
||||||
|
# new index = index+1 -> add after -> index + 2
|
||||||
|
function.body.insert(index+2, checkpoint_after)
|
||||||
|
|
||||||
|
new_checkpoints += 2
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_duplicate_checkpoints(self):
|
||||||
|
"""
|
||||||
|
Removes duplicate checkpoints
|
||||||
|
"""
|
||||||
|
for function in self.functions.values():
|
||||||
|
|
||||||
|
# Skip builtins processing
|
||||||
|
if function.is_builtin or function.is_input:
|
||||||
|
continue
|
||||||
|
|
||||||
|
to_remove = []
|
||||||
|
|
||||||
|
for index, instruction in enumerate(function.body[:-1]):
|
||||||
|
if isinstance(instruction, CallOperation) and instruction.name == self.checkpoint_function_name:
|
||||||
|
next_instruction = function.body[index+1]
|
||||||
|
|
||||||
|
if isinstance(next_instruction, CallOperation) and next_instruction.name == self.checkpoint_function_name:
|
||||||
|
to_remove.append(index)
|
||||||
|
|
||||||
|
removed = 0
|
||||||
|
|
||||||
|
for checkpoint_index in sorted(to_remove):
|
||||||
|
index = checkpoint_index - removed
|
||||||
|
|
||||||
|
checkpoint = function.body[index]
|
||||||
|
|
||||||
|
# sets the index on the next checkpoint
|
||||||
|
if checkpoint.label is not None:
|
||||||
|
function.body[index+1].label = checkpoint.label
|
||||||
|
|
||||||
|
|
||||||
|
del function.body[index]
|
||||||
|
removed += 1
|
||||||
|
|
||||||
|
|
||||||
|
def _create_checkpoint(self):
|
||||||
|
"""
|
||||||
|
Creates and returns a custom checkpoint function call
|
||||||
|
"""
|
||||||
|
return CallOperation(None, self.checkpoint_function_name, BaseType("int", 32), [], [], {})
|
||||||
@ -0,0 +1,205 @@
|
|||||||
|
from ScEpTIC.AST.elements.instructions.memory_operations import AllocaOperation
|
||||||
|
from ScEpTIC.AST.elements.instructions.other_operations import CallOperation
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterSavingOptimization:
|
||||||
|
"""
|
||||||
|
Identifies the minimum set of registes that each checkpoint need to save
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, checkpoint_placer):
|
||||||
|
self.checkpoint_placer = checkpoint_placer
|
||||||
|
|
||||||
|
|
||||||
|
def set_checkpoints_registers(self):
|
||||||
|
"""
|
||||||
|
Identifies and sets the registers that each checkpoints need to save
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.checkpoint_placer._split_function_bodies()
|
||||||
|
|
||||||
|
for name in self.checkpoint_placer.function_body_slices.keys():
|
||||||
|
self._reset_registers_info(name)
|
||||||
|
self._set_checkpoints_general_purpose_regs(name)
|
||||||
|
self._set_checkpoints_esp_reg(name)
|
||||||
|
|
||||||
|
self.checkpoint_placer._synchronize_slices_to_body()
|
||||||
|
|
||||||
|
|
||||||
|
def _reset_registers_info(self, name):
|
||||||
|
"""
|
||||||
|
Resets the register-saving information associated to each checkpoint
|
||||||
|
"""
|
||||||
|
slices = self.checkpoint_placer.function_body_slices[name]
|
||||||
|
|
||||||
|
for slice in slices:
|
||||||
|
checkpoints = self._get_checkpoints(slice)
|
||||||
|
|
||||||
|
for checkpoint in checkpoints:
|
||||||
|
checkpoint.checkpoint_save_pc = True
|
||||||
|
checkpoint.checkpoint_save_esp = False
|
||||||
|
checkpoint.checkpoint_save_regs = set()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_checkpoints(self, slice):
|
||||||
|
"""
|
||||||
|
Returns a list of all the checkpoints included in the slices
|
||||||
|
"""
|
||||||
|
instructions = []
|
||||||
|
|
||||||
|
for instruction in slice:
|
||||||
|
if isinstance(instruction, CallOperation) and instruction.name == self.checkpoint_placer.checkpoint_function_name:
|
||||||
|
instructions.append(instruction)
|
||||||
|
|
||||||
|
return instructions
|
||||||
|
|
||||||
|
|
||||||
|
def _set_checkpoints_general_purpose_regs(self, name):
|
||||||
|
"""
|
||||||
|
Sets the general purpose registers that each checkpoint in the function need to save
|
||||||
|
"""
|
||||||
|
|
||||||
|
# account for function's argument registers
|
||||||
|
n_args = len(self.checkpoint_placer.functions[name].arguments)
|
||||||
|
|
||||||
|
slices = self.checkpoint_placer.function_body_slices[name]
|
||||||
|
alloca_regs = self._get_alloca_regs(slices, n_args)
|
||||||
|
|
||||||
|
alive_regs = set()
|
||||||
|
|
||||||
|
# analyze slices in reverse order to identify general purpose registers to save
|
||||||
|
for slice in reversed(slices):
|
||||||
|
to_save = set()
|
||||||
|
|
||||||
|
# retrieve regs infos
|
||||||
|
regs_external_uses, regs_defs = self._get_regs_first_use_def(slice, alloca_regs)
|
||||||
|
|
||||||
|
# add live set to registers to save
|
||||||
|
for reg in alive_regs:
|
||||||
|
to_save.add(reg)
|
||||||
|
|
||||||
|
# if a defined reg is in alive_regs -> remove it from alive_regs
|
||||||
|
for reg in regs_defs:
|
||||||
|
if reg in alive_regs:
|
||||||
|
alive_regs.remove(reg)
|
||||||
|
|
||||||
|
# add to alive_regs the used regs
|
||||||
|
for reg in regs_external_uses:
|
||||||
|
alive_regs.add(reg)
|
||||||
|
|
||||||
|
self._set_checkpoints_save_regs(slice, to_save)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_alloca_regs(self, slices, n_args):
|
||||||
|
"""
|
||||||
|
Returns the registers that are target of alloca instructions
|
||||||
|
"""
|
||||||
|
alloca_regs = set()
|
||||||
|
|
||||||
|
# accounts for registers that contain arguments
|
||||||
|
for i in range(n_args):
|
||||||
|
alloca_regs.add(f"%{i}")
|
||||||
|
|
||||||
|
# order does not matter
|
||||||
|
for slice in slices:
|
||||||
|
for instruction in slice:
|
||||||
|
if isinstance(instruction, AllocaOperation):
|
||||||
|
alloca_regs.add(instruction.target.value)
|
||||||
|
|
||||||
|
return alloca_regs
|
||||||
|
|
||||||
|
|
||||||
|
def _get_regs_first_use_def(self, slice, alloca_regs):
|
||||||
|
"""
|
||||||
|
Returns two lists:
|
||||||
|
- registers whose use happens before their defs
|
||||||
|
- registers defined
|
||||||
|
"""
|
||||||
|
reg_first_uses = set()
|
||||||
|
reg_defs = set()
|
||||||
|
|
||||||
|
for instruction in slice:
|
||||||
|
for reg in instruction.get_uses():
|
||||||
|
# reg not defined + ignore alloca regs
|
||||||
|
if reg not in reg_defs and reg not in alloca_regs:
|
||||||
|
reg_first_uses.add(reg)
|
||||||
|
|
||||||
|
for reg in instruction.get_defs():
|
||||||
|
# ignore alloca regs
|
||||||
|
if reg not in alloca_regs:
|
||||||
|
reg_defs.add(reg)
|
||||||
|
|
||||||
|
return reg_first_uses, reg_defs
|
||||||
|
|
||||||
|
|
||||||
|
def _set_checkpoints_save_regs(self, slice, to_save):
|
||||||
|
"""
|
||||||
|
Sets the checkpoints contained in the slice to save the given registers
|
||||||
|
|
||||||
|
Assumption (safe) -> no reg is used after a function call
|
||||||
|
This is a safe assumption for the benchmarks we are considering, as we consider LLVM IR virtual regs
|
||||||
|
"""
|
||||||
|
checkpoints = self._get_checkpoints(slice)
|
||||||
|
|
||||||
|
for checkpoint in checkpoints:
|
||||||
|
checkpoint.checkpoint_save_pc = True
|
||||||
|
|
||||||
|
for reg in to_save:
|
||||||
|
checkpoint.checkpoint_save_regs.add(reg)
|
||||||
|
|
||||||
|
if len(to_save) > 0:
|
||||||
|
checkpoint.checkpoint_save_esp = True
|
||||||
|
|
||||||
|
|
||||||
|
def _set_checkpoints_esp_reg(self, name):
|
||||||
|
"""
|
||||||
|
Sets which checkpoint in the function need to save the stack pointer
|
||||||
|
"""
|
||||||
|
slices = self.checkpoint_placer.function_body_slices[name]
|
||||||
|
|
||||||
|
# all checkpoints need to save stack pointer
|
||||||
|
#for slice in slices:
|
||||||
|
# self._set_checkpoint_save_esp(slice)
|
||||||
|
|
||||||
|
# first slice always need to save stack pointer
|
||||||
|
self._set_checkpoint_save_esp(slices[0])
|
||||||
|
|
||||||
|
# a checkpoint at the end of a slice need to save the stack pointer only if the slice calls a function with a checkpoint
|
||||||
|
for slice in slices[1:]:
|
||||||
|
for instruction in slice:
|
||||||
|
# call to a function that is not a checkpoint
|
||||||
|
if isinstance(instruction, CallOperation) and instruction.name != self.checkpoint_placer.checkpoint_function_name:
|
||||||
|
# the function contains a checkpoint, that is, saves the stack pointer
|
||||||
|
if self._function_has_checkpoint(instruction.name):
|
||||||
|
self._set_checkpoint_save_esp(slice)
|
||||||
|
# go to next slice
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def _set_checkpoint_save_esp(self, slice):
|
||||||
|
"""
|
||||||
|
Sets the checkpoints contained in the given slice to save the stack pointer
|
||||||
|
"""
|
||||||
|
checkpoints = self._get_checkpoints(slice)
|
||||||
|
|
||||||
|
for checkpoint in checkpoints:
|
||||||
|
checkpoint.checkpoint_save_esp = True
|
||||||
|
|
||||||
|
|
||||||
|
def _function_has_checkpoint(self, name):
|
||||||
|
"""
|
||||||
|
Returns if a given function contains a checkpoint
|
||||||
|
"""
|
||||||
|
|
||||||
|
# builtin or input function
|
||||||
|
if name not in self.checkpoint_placer.function_body_slices:
|
||||||
|
return False
|
||||||
|
|
||||||
|
slices = self.checkpoint_placer.function_body_slices[name]
|
||||||
|
|
||||||
|
for slice in slices:
|
||||||
|
for instruction in slice:
|
||||||
|
if isinstance(instruction, CallOperation) and instruction.name == self.checkpoint_placer.checkpoint_function_name:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
20
ScEpTIC/AST/transformations/ratchet/config.py
Normal file
20
ScEpTIC/AST/transformations/ratchet/config.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from ScEpTIC.config.base_config import ScEpTICBaseConfig
|
||||||
|
|
||||||
|
class RatchetMemoryConfig(ScEpTICBaseConfig):
|
||||||
|
"""
|
||||||
|
Configuration for the Virtual Memory Transformation
|
||||||
|
"""
|
||||||
|
_config_domain = {
|
||||||
|
'functions_with_outside_frame_accesses': {'class': str, 'mode': 'add'},
|
||||||
|
'optimize_saved_registers': {'class': bool, 'mode': 'set'},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, main_config):
|
||||||
|
super().__init__(main_config)
|
||||||
|
self.functions_with_outside_frame_accesses = []
|
||||||
|
self.optimize_saved_registers = True
|
||||||
|
|
||||||
|
|
||||||
|
# Returns the configuration
|
||||||
|
def get_config(main_config):
|
||||||
|
return RatchetMemoryConfig(main_config)
|
||||||
21
ScEpTIC/AST/transformations/ratchet/main.py
Normal file
21
ScEpTIC/AST/transformations/ratchet/main.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from ScEpTIC.AST.transformations.ratchet.checkpoint_placer import CheckpointPlacer
|
||||||
|
from ScEpTIC.AST.transformations.ratchet.checkpoint_registers_optimization import RegisterSavingOptimization
|
||||||
|
|
||||||
|
|
||||||
|
def apply_transformation(functions, vmstate, f_declarations):
|
||||||
|
"""
|
||||||
|
Apply ratchet transformation
|
||||||
|
:param functions: ScEpTIC AST functions
|
||||||
|
:param vmstate: vmstate
|
||||||
|
"""
|
||||||
|
config = vmstate.config.ast_transformations.ratchet
|
||||||
|
|
||||||
|
functions_with_outside_frame_accesses = config.functions_with_outside_frame_accesses
|
||||||
|
checkpoint_function_name = vmstate.config.state_retention.state_save_function_name
|
||||||
|
|
||||||
|
checkpoint_placer = CheckpointPlacer(functions, functions_with_outside_frame_accesses, checkpoint_function_name)
|
||||||
|
checkpoint_placer.place_checkpoints()
|
||||||
|
|
||||||
|
if config.optimize_saved_registers:
|
||||||
|
checkpoint_register_optimization = RegisterSavingOptimization(checkpoint_placer)
|
||||||
|
checkpoint_register_optimization.set_checkpoints_registers()
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
from ScEpTIC.AST.elements.instructions.memory_operations import AllocaOperation, StoreOperation, LoadOperation
|
||||||
|
|
||||||
|
class MemoryTagsIdentifier:
|
||||||
|
"""
|
||||||
|
A memory tag is a virtual representation of an accessed memory location, which captures also the access pattern.
|
||||||
|
A memory tag is associated to each LoadOperation and StoreOperation.
|
||||||
|
This class contains a collection of methods for parsing the AST and identifying the memory tag of each operation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, functions):
|
||||||
|
self.functions = functions
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_memory_tags(self):
|
||||||
|
"""
|
||||||
|
Populates the memory tags of the instructions in each function
|
||||||
|
"""
|
||||||
|
for name, function in self.functions.items():
|
||||||
|
|
||||||
|
# Skip builtins processing
|
||||||
|
if function.is_builtin or function.is_input:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._resolve_memory_tags(function.body)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_memory_tags(self, function_body):
|
||||||
|
"""
|
||||||
|
Populates the memory tags of the instructions of a given function
|
||||||
|
"""
|
||||||
|
elements = {}
|
||||||
|
first_alloca = True
|
||||||
|
|
||||||
|
for instruction in function_body:
|
||||||
|
# If instruction has a target virtual register, keep track of it to resolve memory tags
|
||||||
|
if 'target' in dir(instruction) and not instruction.target is None and not instruction._omit_target:
|
||||||
|
target_reg = instruction.target.value
|
||||||
|
elements[target_reg] = instruction
|
||||||
|
|
||||||
|
# Fix for LLVM 6/7/8 first alloca
|
||||||
|
if isinstance(instruction, AllocaOperation):
|
||||||
|
# Set first Alloca
|
||||||
|
instruction.is_first = first_alloca
|
||||||
|
first_alloca = False
|
||||||
|
|
||||||
|
# Resolve the memory tag of each LoadOperation and StoreOperation
|
||||||
|
if isinstance(instruction, LoadOperation) or isinstance(instruction, StoreOperation):
|
||||||
|
instruction.resolve_memory_tag(elements)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user