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

177 lines
7.2 KiB
Python

from collections import defaultdict
import copy
import logging
from ScEpTIC.AST.elements.instructions.other_operations import CallOperation
from ScEpTIC.AST.elements.instructions.termination_instructions import ReturnOperation
from ScEpTIC.emulator.intermittent_executor.interruption_managers.base import InterruptionManager
from ScEpTIC.emulator.intermittent_executor.profiling import ProfilingReset, RunInfo, ProfilingLog
from ScEpTIC.emulator.io.input import InputManager, InputSkeleton
from ScEpTIC.emulator.io.output import OutputManager, OutputSkeleton
from ScEpTIC.exceptions import RuntimeException, StopException, MemoryException
class ProfilingInterruptionManager(InterruptionManager):
"""
Interruption manager for profiling intermittent executions
"""
requires_static_checkpoint = False
def __init__(self, vmstate, state_retention_manager):
super().__init__(vmstate, state_retention_manager)
self.output_functions = []
self.input_functions = []
self.is_dynamic = not self.state_retention.static_placement
for key in OutputSkeleton.output_functions:
function_name = '{}{}'.format(self.vmstate.ir_function_prefix, OutputSkeleton.output_functions[key])
self.output_functions.append(function_name)
for key in InputSkeleton.input_functions:
function_name = '{}{}'.format(self.vmstate.ir_function_prefix, InputSkeleton.input_functions[key])
self.input_functions.append(function_name)
def _execution_condition(self):
"""
Checks if we are inside the current checkpoint interval
"""
if self.vmstate.program_end_reached:
return False
current_instruction = self.vmstate.current_instruction
if isinstance(current_instruction, CallOperation) and current_instruction.name == self.state_retention.routine_names['save']:
return False
return True
def intermittent_execution_required(self):
return True
def _is_call_to_output(self):
current_instruction = self.vmstate.current_instruction
return isinstance(current_instruction, CallOperation) and current_instruction.name in self.output_functions
def run_with_intermittent_execution(self):
logging.info('[ProfilingInterruptionManager] Running {} in intermittent execution scenario.'.format(self.vmstate.register_file.pc))
# could be also the first instruction of the program
checkpoint_pc = copy.deepcopy(self.vmstate.register_file.pc)
checkpoint_clock = self.vmstate.global_clock
self.vmstate.state_save_clock_pc_maps[checkpoint_clock] = copy.deepcopy(checkpoint_pc)
# Only after first instruction of the program
if self.vmstate.global_clock > 0:
current_instruction = self.vmstate.current_instruction
if not isinstance(current_instruction, CallOperation) or current_instruction.name != self.state_retention.routine_names['save']:
raise RuntimeException('[ProfilingInterruptionManager] Checkpoint operation required. {} given.'.format(current_instruction.__class__.__name__))
# PC -> checkpoint. skip PC, since the simulator executes it.
self.vmstate.register_file.pc.increment_pc()
self.vmstate.global_clock += 1
# do checkpoint
self.state_retention.execute_state_save()
self.state_retention.save_dump()
force_stop = False
power_failures = 0
tracking = defaultdict(list)
ProfilingLog.tracking = tracking
ProfilingLog.power_failures = power_failures
while self._execution_condition():
current_instruction = self.vmstate.current_instruction
# if it is a call operation, can be an input/output operation
if isinstance(current_instruction, CallOperation):
io_type = None
if current_instruction.name in self.output_functions:
io_type = 'OUTPUT'
elif current_instruction.name in self.input_functions:
io_type = 'INPUT'
# if is an actual I/O operation, track it.
if io_type is not None:
pc = copy.deepcopy(self.vmstate.register_file.pc)
global_clock = self.vmstate.global_clock
# run i/o function
while not isinstance(self.vmstate.current_instruction, ReturnOperation):
self.vmstate.run_step()
# return from i/o function
self.vmstate.run_step()
function_name = current_instruction.name.replace(self.vmstate.ir_function_prefix, '')
if io_type == 'OUTPUT':
io_name = OutputSkeleton.output_names[function_name]
io_value = OutputManager.output_table[io_name]
else:
io_name = InputSkeleton.input_names[function_name]
io_value = InputManager.input_table[io_name]
# create tracking information
runinfo = RunInfo(io_type, pc, global_clock, power_failures, function_name, io_name, io_value)
# append tracking information
tracking[power_failures].append(runinfo)
# update variable
current_instruction = self.vmstate.current_instruction
# if it is a profiling reset
if isinstance(current_instruction, ProfilingReset) and current_instruction.evaluate_reset_condition(power_failures):
power_failures += 1
# Support for dynamic checkpoint mechanisms / Just In Time
# Custom routine can be emulated using execute_before / execute_after functions
if self.is_dynamic:
self.state_retention.execute_state_save()
self.state_retention.save_dump()
ProfilingLog.power_failures = power_failures
self.state_retention.execute_state_restore()
continue
# run the operation. If an exception occurs, sign it and stop tracking
try:
self.vmstate.run_step()
# Skip to next checkpoint interval if a stack activation record anomaly was found
if self.has_stack_activation_record_anomalies(checkpoint_pc, False):
tracking[power_failures].append('Unable to continue the analysis due to a stack anomaly.')
self.state_retention.restore_dump()
force_stop = True
break
except MemoryException as e:
tracking[power_failures].append('Unable to continue the analysis due to a MemoryException: {}'.format(e))
self.state_retention.restore_dump()
force_stop = True
break
# append to global tracking info
self.vmstate.profiling[checkpoint_clock] = tracking
if force_stop:
raise StopException()