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

97 lines
4.3 KiB
Python

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']