From c3995d6cdaafab55c055e0d12e6bd92ff4d8f599 Mon Sep 17 00:00:00 2001 From: "Bobby R. Bruce" Date: Thu, 8 Feb 2024 15:46:36 -0800 Subject: [PATCH] stdlib: Add SimLoopExit event WIP Change-Id: Id1ff08273f90a5b6cbba55f652a70b3756c72981 --- src/python/gem5/simulate/sim_loop_exit.py | 322 ++++++++++++++++++ src/python/gem5/simulate/simulator.py | 68 +--- .../configs/simulator_exit_event_run.py | 4 +- 3 files changed, 338 insertions(+), 56 deletions(-) create mode 100644 src/python/gem5/simulate/sim_loop_exit.py diff --git a/src/python/gem5/simulate/sim_loop_exit.py b/src/python/gem5/simulate/sim_loop_exit.py new file mode 100644 index 00000000000..c585846a2c1 --- /dev/null +++ b/src/python/gem5/simulate/sim_loop_exit.py @@ -0,0 +1,322 @@ +# Copyright (c) 2024 The Regents of the University of California +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import m5 +from m5.util import warn + +from _m5.event import GlobalSimLoopExitEvent + +from gem5.components.boards.abstract_board import AbstractBoard +from gem5.components.processors.switchable_processor import SwitchableProcessor + +from .exit_event import ExitEvent + + +def translate_exit_event(event: GlobalSimLoopExitEvent) -> "SimLoopExitEvent": + """This function translates the GlobalSimLoopExitEvent into a a + SimLoopExitEvent object. Right now this works through a process of + the SimLoopExitEvent subclass via the GlobalSimLoopExitEvent's `clause` + property. + + :param event: The GlobalSimLoopExitEvent to translate. + """ + + exit_string = event.getClause() + + if ( + exit_string == "m5_workbegin instruction encountered" + or exit_string == "workbegin" + ): + return WorkBeginExit(event) + elif ( + exit_string == "m5_workend instruction encountered" + or exit_string == "workend" + ): + return WorkEndExit(event) + elif exit_string == "simulate() limit reached": + return MaxTickExit(event) + elif exit_string == "Tick exit reached": + return ScheduledTickExit(event) + elif exit_string == "checkpoint": + return CheckpointExit + elif exit_string == "user interupt received": + return UserInterruptExit(event) + elif exit_string == "switchcpu": + return SwitchCpuExit(event) + elif exit_string == "simpoint starting point found": + return SimpointBeginExit(event) + elif exit_string == "a thread reached the max instruction count": + return MaxInstructionsExit(event) + + else: + # By default, we return a SimLoopExit object. + return SimLoopExit(event) + + +class SimLoopExit: + """The base class for all SimLoopExit objects. This is the most basic + implementation of the SimLoopExitEvent object. It is a simple wrapper. It + exits the simulator by default and does nothing else. + """ + + # TODO: It would be good if there was an `info` called when each exit event + # was handled. This would be useful for debugging and logging purposes. + + def __init__(self, event: GlobalSimLoopExitEvent): + """The constructor for the SimLoopExit object. The + GlobalSimLoopExitEvent returned by the CPP Simuluator loop is passed + here and stored in the object for reference. + + :param event: The GlobalSimLoopExitEvent that was returned by the + simulator loop. + """ + self._event = event + + def get_event(self): + """Returns the GlobalSimLoopExitEvent that was passed to the object.""" + return self._event + + def get_exit_event_enum(self) -> ExitEvent: + """Returns the ExitEvent enum that corresponds to the exit event + that was passed to the object. By default this is ExitEvent.Exit but + may be overridden by subclasses. + """ + return ExitEvent.Exit + + def default_exit_simulator(self) -> bool: + """The default behavior for whether the simulator should exit back to + the Simulation configuration script. By default this is True (ergo, the + simulator will exit back to the Simulation configuration script). This + may be overridden by subclasses. + """ + return True + + def get_trigger_string(self) -> str: + """Returns a string explaining the trigger for the event. Used + primarily for debugging and logging purposes. + """ + return "" # TODO: Implement this method and in the subclasses. + + def default_behavior(self, board: AbstractBoard) -> None: + """The default behavior for the SimLoopExitEvent. By default this does + nothing but may be overridden by subclasses. + """ + pass + + +class WorkBeginExit(SimLoopExit): + """An Exit that is triggerd by a ROI of a workload being entered. This, + by default, the simulator module will not exit and the statistics will be + reset, to be dumped with the next WorkEndExit event. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.WORKBEGIN + + def default_exit_simulator(self) -> bool: + return False + + def default_behavior(self, board: AbstractBoard) -> None: + m5.stats.reset() + + +class WorkEndExit(SimLoopExit): + """An Exit that is triggerd by a ROI of a workload exiting. This, + by default, the simulator module will exit and the statistics will be + dumped. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.WORKEND + + def default_exit_simulator(self) -> bool: + return True + + def default_behavior(self, board: AbstractBoard) -> None: + m5.stats.dump() + + +class CheckpointExit(SimLoopExit): + """An exit that is triggered by the simulated system with the intent of + creating a checkpoint of the system state. This, by default, will dump a + checkpoint and not exit the simulator. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.CHECKPOINT + + def default_exit_simulator(self) -> bool: + return False + + def default_behavior(self, board: AbstractBoard) -> None: + m5.checkpoint(m5.output_dir + "/cpt." + str(m5.curTick())) + + +class SwitchCpuExit(SimLoopExit): + """An exit that is triggered to switch the CPUs in the system. The default + behavior is to switch the CPUs in the board (if the board is using a + switchable) processor. By default the Simulator will not be exit. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.SWITCHCPU + + def default_exit_simulator(self) -> bool: + return False + + def default_behavior(self, board: AbstractBoard) -> None: + if isinstance(board.processor(), SwitchableProcessor): + board.processor().switch() + else: + warn( + "The board was not setup with a switchable processor. " + f"Ignoring switch event at tick {m5.curTick()}." + ) + + +class UserInterruptExit(SimLoopExit): + """An exit that is triggered by a user interrupt. The default behavior is + to exit the simulation entirely by throwing an exception + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.USER_INTERRUPT + + def default_exit_simulator(self) -> bool: + return True + + def default_behavior(self, board: AbstractBoard) -> None: + raise KeyboardInterrupt( + "User interrupt received at tick {m5.curTick()}." + ) + + +class ScheduledTickExit(SimLoopExit): + """An exit that is triggered by the simulated system reaching a scheduled + tick count. The default behavior is to exit the simulation loop. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.SCHEDULED_TICK + + def default_exit_simulator(self) -> bool: + return True + + +class MaxTickExit(ScheduledTickExit): + """An exit that is triggered by the simulated system reaching the maximum + tick count. The default behavior is the same as a ScheduledTickExit. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.MAX_TICK + + +class SimpointBeginExit(SimLoopExit): + """An exit that is triggered by the simulated system reaching the beginning + of a simpoint. The default behavior is to exit the simulation loop. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.SIMPOINT_BEGIN + + def default_exit_simulator(self) -> bool: + return True + + +class MaxInstructionsExit(SimLoopExit): + """An exit that is triggered by the simulated system reaching the maximum + instruction count. The default behavior is to exit the simulation loop. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.MAX_INSTS + + def default_exit_simulator(self) -> bool: + return True + + +class FailExit(SimLoopExit): + """An exit that is triggered by a simulation failure. The behavior is the + same as the default SimLoopExit event. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.FAIL + + +class KernelPanicExit(FailExit): + """An exit that is triggered by the simulated system experiencing a kernel + panic. The default behavior is the same as the default FailExit. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.KERNEL_PANIC + + +class KernelOopsExit(FailExit): + """An exit that is triggered by the simulated system experiencing a kernel + oops. The default behavior is the same as the default FailExit. + """ + + def __init__(self, event: GlobalSimLoopExitEvent): + super().__init__(event) + + def get_exit_event_enum(self) -> ExitEvent: + return ExitEvent.KERNEL_OOPS diff --git a/src/python/gem5/simulate/simulator.py b/src/python/gem5/simulate/simulator.py index dabcb668de0..9316c0053b2 100644 --- a/src/python/gem5/simulate/simulator.py +++ b/src/python/gem5/simulate/simulator.py @@ -57,6 +57,10 @@ switch_generator, warn_default_decorator, ) +from .sim_loop_exit import ( + SimLoopExit, + translate_to_sim_loop_exit, +) class Simulator: @@ -272,50 +276,8 @@ def print_hello() -> bool: """ - # We specify a dictionary here outlining the default behavior for each - # exit event. Each exit event is mapped to a generator. - self._default_on_exit_dict = { - ExitEvent.EXIT: exit_generator(), - ExitEvent.CHECKPOINT: warn_default_decorator( - save_checkpoint_generator, - "checkpoint", - "creating a checkpoint and continuing", - )(), - ExitEvent.FAIL: exit_generator(), - ExitEvent.SWITCHCPU: warn_default_decorator( - switch_generator, - "switch CPU", - "switching the CPU type of the processor and continuing", - )(processor=board.get_processor()), - ExitEvent.WORKBEGIN: warn_default_decorator( - reset_stats_generator, - "work begin", - "resetting the stats and continuing", - )(), - ExitEvent.WORKEND: warn_default_decorator( - dump_stats_generator, - "work end", - "dumping the stats and continuing", - )(), - ExitEvent.USER_INTERRUPT: exit_generator(), - ExitEvent.MAX_TICK: exit_generator(), - ExitEvent.SCHEDULED_TICK: exit_generator(), - ExitEvent.SIMPOINT_BEGIN: warn_default_decorator( - reset_stats_generator, - "simpoint begin", - "resetting the stats and continuing", - )(), - ExitEvent.MAX_INSTS: warn_default_decorator( - exit_generator, - "max instructions", - "exiting the simulation", - )(), - ExitEvent.KERNEL_PANIC: exit_generator(), - ExitEvent.KERNEL_OOPS: exit_generator(), - } - + self._on_exit_event = {} if on_exit_event: - self._on_exit_event = {} for key, value in on_exit_event.items(): if isinstance(value, Generator): self._on_exit_event[key] = value @@ -337,8 +299,6 @@ def function_generator(func: Callable): f"`on_exit_event` for '{key.value}' event is " "not a Generator or List[Callable]." ) - else: - self._on_exit_event = self._default_on_exit_dict self._instantiated = False self._board = board @@ -559,7 +519,7 @@ def _instantiate(self) -> None: # any final things. self._board._post_instantiate() - def run(self, max_ticks: int = m5.MaxTick) -> GlobalSimLoopExitEvent: + def run(self, max_ticks: int = m5.MaxTick) -> SimLoopExit: """ This function will start or continue the simulator run and handle exit events accordingly. If the the exit event is handled by returning to @@ -589,10 +549,11 @@ def run(self, max_ticks: int = m5.MaxTick) -> GlobalSimLoopExitEvent: while True: self._last_exit_event = m5.simulate(max_ticks) - # Translate the exit event cause to the exit event enum. - exit_enum = ExitEvent.translate_exit_status( - self.get_last_exit_event_cause() - ) + # Translate the exit event into a SimLoopExit object. + sim_loop_exit = translate_to_sim_loop_exit(self._last_exit_event) + + # Get the exit event enum that corresponds to the exit event. + exit_enum = sim_loop_exit.get_exit_event_enum() # Check to see the run is corresponding to the expected execution # order (assuming this check is demanded by the user). @@ -627,9 +588,8 @@ def run(self, max_ticks: int = m5.MaxTick) -> GlobalSimLoopExitEvent: except KeyError: # If the user has not specified their own generator for this # exit event, use the default. - exit_on_completion = next( - self._default_on_exit_dict[exit_enum] - ) + exit_on_completion = sim_loop_exit.default_exit_simulator() + sim_loop_exit.default_behavior(board=self._board) self._exit_event_count += 1 @@ -637,7 +597,7 @@ def run(self, max_ticks: int = m5.MaxTick) -> GlobalSimLoopExitEvent: # run loop with the ExitEvent. (In the case of a function, instead # of a generator, : if it also returned True). if exit_on_completion: - return self._last_exit_event + return sim_loop_exit def save_checkpoint(self, checkpoint_dir: Path) -> None: """ diff --git a/tests/gem5/stdlib/configs/simulator_exit_event_run.py b/tests/gem5/stdlib/configs/simulator_exit_event_run.py index 84a3cd5ed8e..77403c535e8 100644 --- a/tests/gem5/stdlib/configs/simulator_exit_event_run.py +++ b/tests/gem5/stdlib/configs/simulator_exit_event_run.py @@ -163,5 +163,5 @@ def lone_function() -> bool: # termination of the `run` method. Its state is printed below. exit_event_obj = simulator.run() -print(f"Exit event object cause: '{exit_event_obj.getCause()}'") -print(f"Exit event code '{exit_event_obj.getCode()}'") +print(f"Exit event object cause: '{exit_event_obj.get_event().getCause()}'") +print(f"Exit event code '{exit_event_obj.get_event().getCode()}'")