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

250 lines
7.9 KiB
Python

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)