diff --git a/documentation.md b/documentation.md new file mode 100644 index 0000000000..2e1d2e9dcb --- /dev/null +++ b/documentation.md @@ -0,0 +1,315 @@ +# LoopPoint Documentation + + +1. [Take Checkpoint](#take-checkpoint) + - [LoopPointCheckpoint Module](#looppointcheckpoint-module) + - [looppoint_save_checkpoint_generator](#looppoint_save_checkpoint_generator) + - [Example Script](#take-checkpoint-example-script) +2. [Restore Checkpoint](#restore-checkpoint) + - [Example Script](#restore-checkpoint-example-script) +3. [Json File](#json-file) + - [structure](#structure) + - [components](#components) + - [Example](#example) +4. [Others](#others) + - [PcCountPair](#pccountpair) + - [Debug Flag](#degub-flags) + + +The main methodology of performing LoopPoint sampling in the gem5 simulator is taking and restoring checkpoints for LoopPoint significant regions. + +This can be done easily with the standard library LoopPoint modules. + +## Take Checkpoint +In the gem5 standard library, there is a `LoopPointCheckpoint` module designed specifically to take checkpoints for LoopPoint with the `set_se_looppoint_workload` module and the `looppoint_save_checkpoint_generator` generator. + + +### LoopPointCheckpoint module +The `LoopPointCheckpoint` module requires two parameters: `looppoint_file` and `if_csv`. + +`looppoint_file` is a Path object that contains the path of the LoopPoint data file. The data file can be the csv file generated by Pin, or the json file generated by gem5. + +`if_csv` is a boolean object that is True if the file is a csv file, or False if the file is a json file. + +With these two inputs, the `LoopPointCheckpoint` module is able to generate and store information needed for the LoopPoint workload. + +Outputting the information by calling the `output_json_file` in the LoopPoint module in the Python script is strongly recommended and the json file is needed to restore the checkpoint later with the `LoopPointRestore` module. + +After creating a `LoopPointCheckpoint` object with the required inputs, this object can be passed into the `set_se_looppoint_workload`. + +If the LoopPoint information is correctly passed into the cores, there will be `SIMPOINT_END` exit events generated when the target Program Counter address has been executed the number of times the LoopPoint data specifies. + +When the exit event is raised, the `looppoint_save_checkpoint_generator` can evaluate the (PC,count) pair and determine if it is going to take a checkpoint at it or not. + +### looppoint_save_checkpoint_generator +The `looppoint_save_checkpoint_generator` requires three parameters: `checkpoint_dir`, `looppoint`, and `update_relatives`. + +`checkpoint_dir` is a Path object that contains the path to the directory the checkpoints should be stored at. + +`looppoint` is the LoopPoint object(the `LoopPointCheckPoint` object created above) that will be used to identify the current (PC, count) pair the simulation reaches, the corresponding region id of this pair, and update the relative counts in the region if needed. + +`update_relatives` is a boolean object that is True if the relative count in the region should be updated, otherwise, it should be False. If you do not have relative counts in your LoopPoint data file, then it should be True. Relative count information is needed in the json file to restore checkpoints for LoopPoint. It is default as True. + +When the simulation encountered all the (PC, count) pairs in the LoopPoint data file, then it will raises an `PCCOUNTTRACK_END` exit event. With the standard library feature, the simulation can exit the simulation loop when this exit event is encountered. + + +### Take Checkpoint Example Script +``` Python +from gem5.simulate.exit_event import ExitEvent +from gem5.simulate.simulator import Simulator +from gem5.resources.resource import Resource +from gem5.components.cachehierarchies.classic.no_cache import NoCache +from gem5.components.boards.simple_board import SimpleBoard +from gem5.components.memory.single_channel import SingleChannelDDR3_1600 +from gem5.components.processors.simple_processor import SimpleProcessor +from gem5.components.processors.cpu_types import CPUTypes +from gem5.isas import ISA +from pathlib import Path +# LoopPoint related modules +from gem5.utils.looppoint import LoopPointCheckpoint +from gem5.simulate.exit_event_generators import looppoint_save_checkpoint_generator, exit_generator + +# When taking checkpoints, we can use simpler configurations, for example NoCache for cache_hierarchy and ATOMIC CPUs. +# Most of the configurations can be changed in the restore script. +# There are some exceptions, for example the num_cores and the size of memory must match in both the taking checkpoint script and the restore script. +cache_hierarchy = NoCache() + +memory = SingleChannelDDR3_1600(size="2GB") + +processor = SimpleProcessor( + cpu_type=CPUTypes.ATOMIC, + isa=ISA.X86, + num_cores=9, +) + +board = SimpleBoard( + clk_freq="3GHz", + processor=processor, + memory=memory, + cache_hierarchy=cache_hierarchy, +) + +dir = Path("LoopPoint-CheckPoints/") +dir.mkdir(exist_ok=True) + +# ---------------LoopPoint part begins--------------- +# LoopPoint object created +looppoint = LoopPointCheckpoint( + looppoint_file=Path("LoopPoint.csv"), + if_csv=True +) + +board.set_se_looppoint_workload( + binary= Resource("example-binary"), + looppoint = looppoint +) + +simulator = Simulator( + board=board, + on_exit_event ={ + # setup exit event generator + ExitEvent.SIMPOINT_BEGIN : + looppoint_save_checkpoint_generator( + checkpoint_dir=dir, + looppoint=looppoint, + update_relatives=True + ), + # exit the simulation loop when PCCOUNTRACK_END + # is encountered + ExitEvent.PCCOUNTTRACK_END : exit_generator() + } +) + +simulator.run() +# output json file +looppoint.output_json_file() +# ---------------LoopPoint part ends--------------- +``` + +## Restore Checkpoint +Checkpoints taken with the methodology above can be restored fairly straight forward with the `LoopPointRestore` and `set_se_looppoint_workload` module similar to how the checkpoints are taken. + +### LoopPointRestore module +The `LoopPointRestor` module requires two parameters: the `looppoint_file` and `checkpoint_path`. + +`looppoint_file` is a Path object that contains the path to the LoopPoint json file that is generated with the checkpoint taking script. + +`checkpoint_path` is the Path that contains the path to the checkpoint directory. + +With these two inputs, the module is able to get the information needed to restore checkpoints taken for LoopPoint. + +After passing the `LoopPointRestore` object into `set_se_looppoint_workload` module, the simulation will raise `SIMPOINT_BEGIN` exit event when it encounters the (PC, count) pair. You can setup an exit event generator with the `Simulator` module in the standard library to perform different action to the exit event. + +### Restore Checkpoint Example Script +``` Python +from gem5.simulate.exit_event import ExitEvent +from gem5.simulate.simulator import Simulator +from gem5.components.cachehierarchies.classic.\ + private_l1_private_l2_cache_hierarchy import ( + PrivateL1PrivateL2CacheHierarchy, +) +from gem5.components.boards.simple_board import SimpleBoard +from gem5.components.memory import DualChannelDDR4_2400 +from gem5.components.processors.simple_processor import SimpleProcessor +from gem5.components.processors.cpu_types import CPUTypes +from gem5.isas import ISA +from gem5.resources.resource import Resource +from pathlib import Path +from gem5.simulate.exit_event_generators import dump_reset_generator +# LoopPoint related module +from gem5.utils.looppoint import LoopPointRestore + +# Using a more complex cache hierarchy than the one +# used in the taking checkpoint script +cache_hierarchy = PrivateL1PrivateL2CacheHierarchy( + l1d_size="32kB", + l1i_size="32kB", + l2_size="256kB", +) + +memory = DualChannelDDR4_2400(size="2GB") + +processor = SimpleProcessor( + cpu_type=CPUTypes.O3, + isa=ISA.X86, + num_cores=9, +) + +board = SimpleBoard( + clk_freq="3GHz", + processor=processor, + memory=memory, + cache_hierarchy=cache_hierarchy, +) + +# ---------------LoopPoint part begins--------------- +# LoopPoint object created +looppoint = LoopPointRestore( + looppoint_file=Path("LoopPoint.json"), + checkpoint_path=Path("LoopPoint-CheckPoints/cpt.Region1") +) + +board.set_se_looppoint_workload( + binary= Resource("example-binary"), + looppoint=looppoint +) + +simulator = Simulator( + board=board, + checkpoint_path=args.checkpoint, + on_exit_event ={ + # setup generator to perform a statistic dump and reset + # when the SIMPOINT_BEGIN exit event is encountered + ExitEvent.SIMPOINT_BEGIN : dump_reset_generator(), + # exit the simulation loop when PCCOUNTRACK_END + # is encountered + ExitEvent.PCCOUNTTRACK_END: exit_generator() + } +) + +simulator.run() +# ---------------LoopPoint part ends--------------- +``` + +## Json File + + +### structure + +Below is the structure of the json file +``` +region id + |___ simulation + | |___ start + | | |___ pc + | | |___ global + | | |___ relative + | |___ end + | |___ pc + | |___ global + | |___ relative + |___ warmup # optional to region + | |___ start + | | |___ pc + | | |___ global + | | |___ relative + | |___ end + | |___ pc + | |___ global + | |___ relative + |___ multiplier + +``` + +### components + +`region id` represent the specific region that is selected by the LoopPoint sampling methodology. Each of this specific region has a simulation region, but it might or might not has a warmup region. + +`simulation` is the region that should be simulate in detail. + +`warmup` is the warmup region that should be used to warmup before performing detail simulation on the simulation region. + +`pc` is the Program Counter address. + +`global` is the global count that starts counting from the beginning of the workload. + +`relative` is the relative count that starts counting from the beginning of the (PC, count) pair where the checkpoint is taken. + +`multiplier` is the weight that should be applied on the data for this region to calculate the runtime with the LoopPoint sampling methodology. + +### Example + +``` Json + "1": { + "simulation": { + "start": { + "pc": 4221392, + "global": 211076617, + "relative": 15326617 + }, + "end": { + "pc": 4221392, + "global": 219060252, + "relative": 23310252 + } + }, + "multiplier": 4.0, + "warmup": { + "start": { + "pc": 4221056, + "count": 23520614 + }, + "end": { + "pc": 4221392, + "count": 211076617 + } + } + }, + "2": { + "simulation": { + "start": { + "pc": 4206672, + "global": 1 + }, + "end": { + "pc": 4221392, + "global": 6861604, + "relative": 6861604 + } + }, + "multiplier": 1.0 + } +``` + +## Others + +### PcCountPair + +`PcCountPair` is a special parameter for storing a Program Counter address (`PcCountPair::pc`) and an integer value of count(`PcCountPair::count`). The `PcCountPair` object can communicate between Python and C++, and can be used in the same way. However, the hashing functions between Python and C++ of this parameter are different. It is known that the C++ `PcCountPair` object can not be used as the key to find a value in the Python hash table. + +### Debug Flag + +`PcCountTracker` is a debug flag for the `PcCountTrackerManager` SimObject, which is the underneath object that is responsible for tracking the Program Counter address and the number of times they have been executed. It can be enabled in the command line. For example: + + build/X86/gem5.opt --debug-flags=PcCountTracker config_script.py + diff --git a/src/base/types.hh b/src/base/types.hh index 913455fca0..c7d90e8cdb 100644 --- a/src/base/types.hh +++ b/src/base/types.hh @@ -138,6 +138,66 @@ class Cycles friend std::ostream& operator<<(std::ostream &out, const Cycles & cycles); }; +class PcCountPair +{ + + private: + + /** The Program Counter address*/ + uint64_t pc; + /** The count of the Program Counter address*/ + int count; + + public: + + /** Explicit constructor assigning the pc and count values*/ + explicit constexpr PcCountPair(uint64_t _pc, int _count) : + pc(_pc), count(_count) {} + + /** Default constructor for parameter classes*/ + PcCountPair() : pc(0), count(0) {} + + /** Returns the Program Counter address*/ + constexpr uint64_t getPC() const { return pc; } + /** Returns the count of the Program*/ + constexpr int getCount() const { return count; } + + /** Greater than comparison*/ + constexpr bool + operator>(const PcCountPair& cc) const + { + return count > cc.getCount(); + } + + /** Equal comparison*/ + constexpr bool + operator==(const PcCountPair& cc) const + { + return (pc == cc.getPC() && count == cc.getCount()); + } + + /** String format*/ + std::string + to_string() const + { + std::string s = "(" + std::to_string(pc) + + ", " + std::to_string(count) + ")"; + return s; + } + + /** Enable hashing for this parameter*/ + struct HashFunction + { + size_t operator()(const PcCountPair& item) const + { + size_t xHash = std::hash()(item.pc); + size_t yHash = std::hash()(item.count) << 1; + return xHash ^ yHash; + } + }; + +}; + /** * Address type * This will probably be moved somewhere else in the near future. diff --git a/src/cpu/probes/PcCountTracker.py b/src/cpu/probes/PcCountTracker.py new file mode 100644 index 0000000000..78c0855cad --- /dev/null +++ b/src/cpu/probes/PcCountTracker.py @@ -0,0 +1,63 @@ +# Copyright (c) 2023 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. + +from m5.params import * +from m5.util.pybind import * +from m5.objects.Probe import ProbeListenerObject +from m5.objects import SimObject + + +class PcCountTrackerManager(SimObject): + """This class manages global PC-count pair tracking. + + + """ + + type = "PcCountTrackerManager" + cxx_header = "cpu/probes/pc_count_tracker_manager.hh" + cxx_class = "gem5::PcCountTrackerManager" + + cxx_exports = [ + PyBindMethod("get_pc_count"), + PyBindMethod("get_current_pc_count_pair"), + ] + + targets = VectorParam.PcCountPair("the target PC Count pairs") + + +class PcCountTracker(ProbeListenerObject): + """This probe listener tracks the number of times a particular pc has been + executed. It needs to be connected to a manager to track the global + information. + """ + + type = "PcCountTracker" + cxx_header = "cpu/probes/pc_count_tracker.hh" + cxx_class = "gem5::PcCountTracker" + + targets = VectorParam.PcCountPair("the target PC Count pairs") + core = Param.BaseCPU("the connected cpu") + ptmanager = Param.PcCountTrackerManager("the PcCountTracker manager") diff --git a/src/cpu/probes/SConscript b/src/cpu/probes/SConscript new file mode 100644 index 0000000000..c96ca78a0c --- /dev/null +++ b/src/cpu/probes/SConscript @@ -0,0 +1,37 @@ +# Copyright (c) 2022 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("*") + +if not env["CONF"]["USE_NULL_ISA"]: + SimObject( + "PcCountTracker.py", + sim_objects=["PcCountTracker", "PcCountTrackerManager"], + ) + Source("pc_count_tracker.cc") + Source("pc_count_tracker_manager.cc") + + DebugFlag("PcCountTracker") diff --git a/src/cpu/probes/pc_count_tracker.cc b/src/cpu/probes/pc_count_tracker.cc new file mode 100644 index 0000000000..d17c79af9a --- /dev/null +++ b/src/cpu/probes/pc_count_tracker.cc @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 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. + */ + +#include "cpu/probes/pc_count_tracker.hh" + + +namespace gem5 +{ + +PcCountTracker::PcCountTracker(const PcCountTrackerParams &p) + : ProbeListenerObject(p), + cpuptr(p.core), + manager(p.ptmanager) +{ + if (!cpuptr || !manager) { + fatal("%s is NULL", !cpuptr ? "CPU": "PcCountTrackerManager"); + } + for (int i = 0; i < p.targets.size(); i++) { + // initialize the set of targeting Program Counter addresses + targetPC.insert(p.targets[i].getPC()); + } +} + +void +PcCountTracker::regProbeListeners() +{ + // connect the probe listener with the probe "RetriedInstsPC" in the + // corresponding core. + // when "RetiredInstsPC" notifies the probe listener, then the function + // 'check_pc' is automatically called + typedef ProbeListenerArg PcCountTrackerListener; + listeners.push_back(new PcCountTrackerListener(this, "RetiredInstsPC", + &PcCountTracker::checkPc)); +} + +void +PcCountTracker::checkPc(const Addr& pc) { + if (targetPC.find(pc) != targetPC.end()) { + // if the PC is one of the target PCs, then notify the + // PcCounterTrackerManager by calling its `check_count` function + manager->check_count(pc); + } +} + +} // namespace gem5 diff --git a/src/cpu/probes/pc_count_tracker.hh b/src/cpu/probes/pc_count_tracker.hh new file mode 100644 index 0000000000..b2527f8a01 --- /dev/null +++ b/src/cpu/probes/pc_count_tracker.hh @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 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. + */ + +#ifndef __CPU_PROBES_PC_COUNT_TRACKER_HH__ +#define __CPU_PROBES_PC_COUNT_TRACKER_HH__ + +#include + +#include "cpu/base.hh" +#include "cpu/probes/pc_count_tracker_manager.hh" +#include "params/PcCountTracker.hh" +#include "sim/probe/probe.hh" + +namespace gem5 +{ + +class PcCountTracker : public ProbeListenerObject +{ + public: + PcCountTracker(const PcCountTrackerParams ¶ms); + + // setup the probelistener + virtual void regProbeListeners(); + + // this function is called when the probelistener receives signal + // from the probe + void checkPc(const Addr& pc); + + private: + // a set of Program Counter addresses that should notify the + // PcCounterTrackerManager for + std::unordered_set targetPC; + + // the core this PcCountTracker is tracking at + BaseCPU *cpuptr; + + // the PcCounterTrackerManager + PcCountTrackerManager *manager; +}; +} + +#endif // __CPU_PROBES_PC_COUNT_TRACKER_HH__ diff --git a/src/cpu/probes/pc_count_tracker_manager.cc b/src/cpu/probes/pc_count_tracker_manager.cc new file mode 100644 index 0000000000..efbea378e5 --- /dev/null +++ b/src/cpu/probes/pc_count_tracker_manager.cc @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 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. + */ + +#include "cpu/probes/pc_count_tracker_manager.hh" + +namespace gem5 +{ + PcCountTrackerManager::PcCountTrackerManager( + const PcCountTrackerManagerParams &p) + : SimObject(p) + { + currentPair = PcCountPair(0,0); + ifListNotEmpty = true; + + for (int i = 0 ; i < p.targets.size() ; i++) { + // initialize the counter for the inputted PC Count pair + // unordered_map does not allow duplicate, so counter won't + // have duplicates + counter.insert(std::make_pair(p.targets[i].getPC(),0)); + // store all the PC Count pair into the targetPair set + targetPair.insert(p.targets[i]); + } + DPRINTF(PcCountTracker, + "total %i PCs in counter\n", counter.size()); + DPRINTF(PcCountTracker, + "all targets: \n%s", print_all_targets()); + } + + void + PcCountTrackerManager::check_count(Addr pc) { + + if(ifListNotEmpty) { + int count = ++counter.find(pc)->second; + // increment the counter of the encountered PC address by 1 + + if(targetPair.empty()) { + // if all target PC Count pairs are encountered + DPRINTF(PcCountTracker, + "all targets are encountered.\n"); + ifListNotEmpty = false; + } + + currentPair = PcCountPair(pc,count); + // update the current PC Count pair + if(targetPair.find(currentPair) != targetPair.end()) { + // if the current PC Count pair is one of the target pairs + DPRINTF(PcCountTracker, + "pc:%s encountered\n", currentPair.to_string()); + + exitSimLoopNow("simpoint starting point found"); + // raise the SIMPOINT_BEGIN exit event + + targetPair.erase(currentPair); + // erase the encountered PC Count pair from the target pairs + DPRINTF(PcCountTracker, + "There are %i targets remained\n", targetPair.size()); + } + } + } + +} diff --git a/src/cpu/probes/pc_count_tracker_manager.hh b/src/cpu/probes/pc_count_tracker_manager.hh new file mode 100644 index 0000000000..04912e145e --- /dev/null +++ b/src/cpu/probes/pc_count_tracker_manager.hh @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2022 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. + */ + +#ifndef __CPU_PROBES_PC_COUNT_TRACKER_MANAGER_HH__ +#define __CPU_PROBES_PC_COUNT_TRACKER_MANAGER_HH__ + +#include +#include + +#include "cpu/base.hh" +#include "params/PcCountTrackerManager.hh" +#include "sim/sim_exit.hh" +#include "debug/PcCountTracker.hh" + +namespace gem5 +{ + + + class PcCountTrackerManager : public SimObject { + public: + PcCountTrackerManager(const PcCountTrackerManagerParams ¶ms); + + void check_count(Addr pc); + // this function is called when PcCountTrackerProbeListener finds + // a target PC + + private: + std::unordered_map counter; + // a counter that stores all the target PC addresses and the number + // of times the target PC has been executed + + std::unordered_set targetPair; + // a set that stores all the PC Count pairs that should raise an + // exit event at + + PcCountPair currentPair; + // the current PC Count pair. + + bool ifListNotEmpty; + // when all the PC Count pairs in the `targetPair` are encountered, + // and the PCCOUNTTRACK_END exit event is raised, this boolean + // variable becomes false and is used to stop the `check_count` + // from functioning. This is default as true. + + public: + int + get_pc_count(Addr pc) const + { + if (counter.find(pc) != counter.end()) { + return counter.find(pc)->second; + } + return -1; + } + // this function returns the corresponding value of count for the + // inputted Program Counter address. If the PC address does not + // exist in the counter, then it returns a -1. + + PcCountPair + get_current_pc_count_pair() const + { + return currentPair; + } + // this function returns the current PC Count pair + + std::string + print_all_targets() const + { + std::string s; + for(auto itr = targetPair.begin(); + itr != targetPair.end(); + ++itr) { + s += itr->to_string(); + s += "\n"; + } + return s; + } + // this function print all targets + }; + +} + +#endif // __CPU_PROBES_PC_COUNT_TRACKER_MANAGER_HH__ diff --git a/src/python/SConscript b/src/python/SConscript index aeeb8925a3..68b5e1d926 100644 --- a/src/python/SConscript +++ b/src/python/SConscript @@ -240,6 +240,7 @@ PySource('gem5.components.processors', PySource('gem5.components.processors', 'gem5/components/processors/switchable_processor.py') PySource('gem5.utils', 'gem5/utils/simpoint.py') +PySource('gem5.utils', 'gem5/utils/looppoint.py') PySource('gem5.components.processors', 'gem5/components/processors/traffic_generator_core.py') PySource('gem5.components.processors', diff --git a/src/python/gem5/components/boards/se_binary_workload.py b/src/python/gem5/components/boards/se_binary_workload.py index 8ec112ee13..253e796ad2 100644 --- a/src/python/gem5/components/boards/se_binary_workload.py +++ b/src/python/gem5/components/boards/se_binary_workload.py @@ -27,6 +27,7 @@ from .abstract_board import AbstractBoard from ...resources.resource import AbstractResource from gem5.utils.simpoint import SimPoint +from gem5.utils.looppoint import LoopPoint from m5.objects import SEWorkload, Process @@ -169,3 +170,47 @@ def get_simpoint(self) -> SimPoint: if getattr(self, "_simpoint_object", None): return self._simpoint_object raise Exception("This board does not have a simpoint set.") + + def set_se_looppoint_workload( + self, + binary: AbstractResource, + arguments: List[str] = [], + looppoint: Optional[Union[AbstractResource, LoopPoint]] = None, + checkpoint: Optional[Union[Path, AbstractResource]] = None, + ) -> None: + """Set up the system to run a LoopPoint workload. + + **Limitations** + * Dynamically linked executables are partially supported when the host + ISA and the simulated ISA are the same. + + :param binary: The resource encapsulating the binary to be run. + :param arguments: The input arguments for the binary + :param looppoint: The LoopPoint object that contain all the information + gather from the LoopPoint files and a LoopPointManager that will raise + exit events for LoopPoints + """ + + if isinstance(looppoint, AbstractResource): + self._looppoint_object = LoopPoint(looppoint) + else: + assert isinstance(looppoint, LoopPoint) + self._looppoint_object = looppoint + + self._looppoint_object.setup_processor(self.get_processor()) + + # Call set_se_binary_workload after LoopPoint setup is complete + self.set_se_binary_workload( + binary=binary, + arguments=arguments, + checkpoint=checkpoint, + ) + + def get_looppoint(self) -> LoopPoint: + """ + Returns the LoopPoint object set. If no LoopPoint object has been set + an exception is thrown. + """ + if getattr(self, "_looppoint_object", None): + return self._looppoint_object + raise Exception("This board does not have a looppoint set.") diff --git a/src/python/gem5/components/processors/abstract_core.py b/src/python/gem5/components/processors/abstract_core.py index 58296bca3b..8259df8a8b 100644 --- a/src/python/gem5/components/processors/abstract_core.py +++ b/src/python/gem5/components/processors/abstract_core.py @@ -29,7 +29,8 @@ from ...isas import ISA -from m5.objects import BaseMMU, Port, SubSystem +from m5.objects import BaseMMU, Port, SubSystem, PcCountTrackerManager +from m5.params import PcCountPair class AbstractCore(SubSystem): @@ -155,3 +156,9 @@ def _set_inst_stop_any_thread( instruction stop is setup differently dependent on this. """ raise NotImplementedError("This core type does not support MAX_INSTS") + + @abstractmethod + def add_pc_tracker_probe( + self, target_pair: List[PcCountPair], manager: PcCountTrackerManager + ) -> None: + raise NotImplementedError diff --git a/src/python/gem5/components/processors/base_cpu_core.py b/src/python/gem5/components/processors/base_cpu_core.py index 631fd0ad0e..c75c0029cf 100644 --- a/src/python/gem5/components/processors/base_cpu_core.py +++ b/src/python/gem5/components/processors/base_cpu_core.py @@ -33,7 +33,15 @@ from ...utils.override import overrides from ...utils.requires import requires -from m5.objects import BaseMMU, Port, BaseCPU, Process +from m5.objects import ( + BaseMMU, + Port, + BaseCPU, + Process, + PcCountTracker, + PcCountTrackerManager, +) +from m5.params import PcCountPair class BaseCPUCore(AbstractCore): @@ -169,3 +177,13 @@ def _set_inst_stop_any_thread( self.core.scheduleInstStopAnyThread(inst) else: self.core.max_insts_any_thread = inst + + @overrides(AbstractCore) + def add_pc_tracker_probe( + self, target_pair: List[PcCountPair], manager: PcCountTrackerManager + ) -> None: + pair_tracker = PcCountTracker() + pair_tracker.targets = target_pair + pair_tracker.core = self.core + pair_tracker.ptmanager = manager + self.core.probeListener = pair_tracker diff --git a/src/python/gem5/simulate/exit_event_generators.py b/src/python/gem5/simulate/exit_event_generators.py index d6732bb49d..294f457189 100644 --- a/src/python/gem5/simulate/exit_event_generators.py +++ b/src/python/gem5/simulate/exit_event_generators.py @@ -29,6 +29,7 @@ from ..components.processors.abstract_processor import AbstractProcessor from ..components.processors.switchable_processor import SwitchableProcessor from ..utils.simpoint import SimPoint +from gem5.utils.looppoint import LoopPoint from m5.util import warn from pathlib import Path @@ -167,3 +168,46 @@ def simpoints_save_checkpoint_generator( yield False else: yield True + + +def looppoint_save_checkpoint_generator( + checkpoint_dir: Path, + looppoint: LoopPoint, + update_relatives: bool = True, + exit_when_empty: bool = True, +): + """ + A generator for taking a checkpoint for LoopPoint. It will save the + checkpoints in the checkpoint_dir path with the Region id. + (i.e. "cpt.Region10) It only takes a checkpoint if the current PC Count + pair is a significant PC Count Pair. This is determined in the LoopPoint + module. The simulation loop continues after exiting this generator. + :param checkpoint_dir: where to save the checkpoints + :param loopoint: the looppoint object used in the configuration script + :param update_relative: if the generator should update the relative count + information in the output json file, then it should be True. It is default + as True. + :param exit_when_empty: if the generator should exit the simulation loop if + all PC paris have been discovered, then it should be True. It is default as + True. + """ + if exit_when_empty: + total_pairs = len(looppoint.get_targets()) + else: + total_pairs = -1 + # it will never equal to 0 if exit_when_empty is false + + while total_pairs != 0: + region = looppoint.get_current_region() + # if it is a significant PC Count pair, then the get_current_region() + # will return an integer greater than 0. By significant PC Count pair, + # it means the PC Count pair that indicates where to take the + # checkpoint at. This is determined in the LoopPoint module. + if region != -1: + if update_relatives: + looppoint.update_relatives() + m5.checkpoint((checkpoint_dir / f"cpt.Region{region}").as_posix()) + total_pairs -= 1 + yield False + + yield True diff --git a/src/python/gem5/utils/looppoint.py b/src/python/gem5/utils/looppoint.py new file mode 100644 index 0000000000..8933306b42 --- /dev/null +++ b/src/python/gem5/utils/looppoint.py @@ -0,0 +1,403 @@ +# Copyright (c) 2022 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. + + +from m5.util import fatal +from m5.params import PcCountPair +from pathlib import Path +from typing import List, Dict +from gem5.components.processors.abstract_processor import AbstractProcessor +from m5.objects import PcCountTrackerManager +import csv +import re +import json + + +class LoopPoint: + """ + This LoopPoint class is used to manage the information needed for LoopPoint + in workload + """ + + def __init__( + self, + targets: List[PcCountPair], + regions: Dict[PcCountPair, int], + json_file: Dict[int, Dict], + ) -> None: + """ + :param targets: a list of PcCountPair that are used to generate exit + event at when the PcCountTrackerManager encounter this PcCountPair in + execution + :param regions: a dictionary used to find the corresponding region id + for the significant PcCountPair. This is mainly used to ensure + checkpoints are taken in the correct PcCountPair or relative counts are + updated at the correct count + :param json_file: all the LoopPoint data including relative counts and + multiplier are stored in this parameter. It can be outputted as a json + file. + """ + + self._manager = PcCountTrackerManager() + self._manager.targets = targets + self._targets = targets + self._regions = regions + self._json_file = json_file + + def setup_processor( + self, + processor: AbstractProcessor, + ) -> None: + """ + This function is used to setup a PC tracker in all the cores and + connect all the tracker to the PC tracker manager to perform + multithread PC tracking + :param processor: the processor used in the simulation configuration + """ + for core in processor.get_cores(): + core.add_pc_tracker_probe(self._targets, self._manager) + + def update_relatives(self) -> None: + """ + This function is used to update the relative count for restore used. + The new relative count will be stored in the _json_file and can be + outputted into a json file by calling the output_json_file function. + """ + current_pair = self._manager.get_current_pc_count_pair() + temp_pair = PcCountPair(current_pair.getPC(), current_pair.getCount()) + if temp_pair in self._regions: + rid = self._regions[temp_pair] + region = self._json_file[rid]["simulation"] + if "warmup" in self._json_file[rid]: + # if this region has a warmup interval, + # then update the relative count for the + # start of the simulation region + start = region["start"]["pc"] + temp = region["start"]["global"] - self._manager.get_pc_count( + start + ) + self._json_file[rid]["simulation"]["start"]["relative"] = int( + temp + ) + end = region["end"]["pc"] + temp = region["end"]["global"] - self._manager.get_pc_count(end) + self._json_file[rid]["simulation"]["end"]["relative"] = int(temp) + + def output_json_file( + self, input_indent: int = 4, filename: str = "LoopPoint.json" + ) -> Dict[int, Dict]: + """ + This function is used to output the _json_file into a json file + :param input_indent: the indent value of the json file + :param filename: the name of the output file + """ + with open(filename, "w") as file: + json.dump(self._json_file, file, indent=input_indent) + + def get_current_region(self) -> int: + """ + This function returns the region id if the current PC Count pair is + significant(e.x. beginning of the checkpoint), otherwise, it returns + a '-1' to indicate the current PC Count pair is not significant + """ + current_pair = self._manager.get_current_pc_count_pair() + temp_pair = PcCountPair(current_pair.getPC(), current_pair.getCount()) + if temp_pair in self._regions: + return self._regions[temp_pair] + return -1 + + def get_current_pair(self) -> PcCountPair: + """ + This function returns the current PC Count pair + """ + return self._manager.get_current_pc_count_pair() + + def get_regions(self) -> Dict[PcCountPair, int]: + """ + This function returns the complete dictionary of _regions + """ + return self._regions + + def get_targets(self) -> List[PcCountPair]: + """ + This function returns the complete list of _targets + """ + return self._targets + + +class LoopPointCheckpoint(LoopPoint): + def __init__(self, looppoint_file: Path, if_csv: bool) -> None: + """ + This class is specifically designed to take in the LoopPoint data file + and generate the information needed to take checkpoints for LoopPoint + regions(warmup region+simulation region) + :param looppoint_file: the director of the LoopPoint data file + :param if_csv: if the file is a csv file, then it is True. If the file + is a json file, then it is False + """ + + _json_file = {} + _targets = [] + _region_id = {} + + if if_csv: + self.profile_csv(looppoint_file, _targets, _json_file, _region_id) + else: + self.profile_json(looppoint_file, _targets, _json_file, _region_id) + + super().__init__( + _targets, + _region_id, + _json_file, + ) + + def profile_csv( + self, + looppoint_file_path: Path, + targets: List[PcCountPair], + json_file: Dict[int, Dict], + region_id: Dict[PcCountPair, int], + ) -> None: + """ + This function profiles the csv LoopPoint data file into three variables + to take correct checkpoints for LoopPoint + :param looppoint_file_path: the director of the LoopPoint data file + :param targets: a list of PcCountPair + :param json_file: a dictionary for all the LoopPoint data + :param region_id: a dictionary for all the significant PcCountPair and + its corresponding region id + """ + + # This section is hard-coded to parse the data in the csv file. + # The csv file is assumed to have a constant format. + with open(looppoint_file_path, newline="") as csvfile: + spamreader = csv.reader(csvfile, delimiter=" ", quotechar="|") + for row in spamreader: + if len(row) > 1: + if row[0] == "cluster": + # if it is a simulation region + line = row[4].split(",") + start = PcCountPair(int(line[3], 16), int(line[6])) + end = PcCountPair(int(line[7], 16), int(line[10])) + if int(line[2]) in json_file: + # if this region was created in the json_file + json_file[int(line[2])]["simulation"] = { + "start": {"pc": int(line[3], 16)} + } + else: + json_file[int(line[2])] = { + "simulation": { + "start": {"pc": int(line[3], 16)} + } + } + json_file[int(line[2])]["simulation"]["start"][ + "global" + ] = int(line[6]) + json_file[int(line[2])]["simulation"]["end"] = { + "pc": int(line[7], 16) + } + json_file[int(line[2])]["simulation"]["end"][ + "global" + ] = int(line[10]) + json_file[int(line[2])]["multiplier"] = float(line[14]) + targets.append(start) + targets.append(end) + # store all the PC Count pairs from the file to the + # targets list + elif row[0] == "Warmup": + line = row[3].split(",") + start = PcCountPair(int(line[3], 16), int(line[6])) + end = PcCountPair(int(line[7], 16), int(line[10])) + if int(line[0]) in json_file: + json_file[int(line[0])]["warmup"] = { + "start": {"pc": int(line[3], 16)} + } + else: + json_file[int(line[0])] = { + "warmup": {"start": {"pc": int(line[3], 16)}} + } + json_file[int(line[0])]["warmup"]["start"][ + "count" + ] = int(line[6]) + json_file[int(line[0])]["warmup"]["end"] = { + "pc": int(line[7], 16) + } + json_file[int(line[0])]["warmup"]["end"][ + "count" + ] = int(line[10]) + targets.append(start) + targets.append(end) + # store all the PC Count pairs from the file to the + # targets list + + for rid, region in json_file.items(): + # this loop iterates all the regions and find the significant PC + # Count pair for the region + if "warmup" in region: + # if the region has a warmup interval, then the checkpoint + # should be taken at the start of the warmup interval + start = PcCountPair( + region["warmup"]["start"]["pc"], + region["warmup"]["start"]["count"], + ) + else: + # if the region does not have a warmup interval, then the + # checkpoint should be taken at the start of the simulation + # region + start = PcCountPair( + region["simulation"]["start"]["pc"], + region["simulation"]["start"]["global"], + ) + region_id[start] = rid + + def profile_json( + self, + looppoint_file_path: Path, + targets: List[PcCountPair], + json_file: Dict[int, Dict], + region_id: Dict[PcCountPair, int], + ) -> None: + """ + This function profiles the json LoopPoint data file into three + variables to take correct checkpoints for LoopPoint + :param looppoint_file_path: the director of the LoopPoint data file + :param targets: a list of PcCountPair + :param json_file: a dictionary for all the LoopPoint data + :param region_id: a dictionary for all the significant PcCountPair and + its corresponding region id + """ + + with open(looppoint_file_path) as file: + json_file = json.load(file) + # load all json information into the json_file variable + for rid, region in json_file.items(): + # iterates all regions + sim_start = PcCountPair( + region["simulation"]["start"]["pc"], + region["simulation"]["start"]["global"], + ) + targets.append(sim_start) + # store all PC Count pairs in the file into targets list + end = PcCountPair( + region["simulation"]["end"]["pc"], + region["simulation"]["end"]["global"], + ) + targets.append(end) + if "warmup" in region: + # if there is a warmup in the region, then the checkpoint + # should be taken at the start of the warmup interval + start = PcCountPair( + region["warmup"]["start"]["pc"], + region["warmup"]["start"]["count"], + ) + targets.append(start) + end = PcCountPair( + region["warmup"]["end"]["pc"], + region["warmup"]["end"]["count"], + ) + targets.append(end) + else: + # if there is not a warmup interval in the region, then the + # checkpoint should be taken at the start of the simulation + # region + start = sim_start + region_id[start] = rid + + +class LoopPointRestore(LoopPoint): + def __init__(self, looppoint_file: Path, checkpoint_path: Path) -> None: + """ + This class is specifically designed to take in the LoopPoint data file and + generator information needed to restore a checkpoint taken by the + LoopPointCheckPoint. + :param looppoint_file: a json file generated by gem5 that has all the + LoopPoint data information + :param checkpoint_path: the director of the checkpoint taken by the gem5 + standard library looppoint_save_checkpoint_generator + + """ + + _json_file = {} + _targets = [] + _region_id = {} + + self.profile_restore( + looppoint_file, checkpoint_path, _targets, _json_file, _region_id + ) + + super().__init__( + _targets, + _region_id, + _json_file, + ) + + def profile_restore( + self, + looppoint_file_path: Path, + checkpoint_dir: Path, + targets: List[PcCountPair], + json_file: Dict[int, Dict], + region_id: Dict[PcCountPair, int], + ) -> None: + """ + This function is used to profile data from the LoopPoint data file to + information needed to restore the LoopPoint checkpoint + :param looppoint_file_path: the director of the LoopPoint data file + :param targets: a list of PcCountPair + :param json_file: a dictionary for all the LoopPoint data + :param region_id: a dictionary for all the significant PcCountPair and + its corresponding region id + """ + regex = re.compile(r"cpt.Region([0-9]+)") + rid = regex.findall(checkpoint_dir.as_posix())[0] + # finds out the region id from the directory name + with open(looppoint_file_path) as file: + json_file = json.load(file) + if rid not in json_file: + # if the region id does not exist in the LoopPoint data file + # raise a fatal message + fatal(f"{rid} is not a valid region\n") + region = json_file[rid] + if "warmup" in region: + if "relative" not in region["simulation"]["start"]: + # if there are not relative counts for the PC Count pair + # then it means there is not enough information to restore + # this checkpoint + fatal(f"region {rid} doesn't have relative count info\n") + start = PcCountPair( + region["simulation"]["start"]["pc"], + region["simulation"]["start"]["relative"], + ) + region_id[start] = rid + targets.append(start) + if "relative" not in region["simulation"]["end"]: + fatal(f"region {rid} doesn't have relative count info\n") + end = PcCountPair( + region["simulation"]["end"]["pc"], + region["simulation"]["end"]["relative"], + ) + region_id[end] = rid + targets.append(end) diff --git a/src/python/m5/params.py b/src/python/m5/params.py index e76380bc40..a25bec42ea 100644 --- a/src/python/m5/params.py +++ b/src/python/m5/params.py @@ -854,6 +854,37 @@ def pretty_print(self, value): return "0x%x" % int(val) +class PcCountPair(ParamValue): + # This parameter stores a Program Counter address and the a count value for + # the Program Counter address + cxx_type = "PcCountPair" + cmd_line_settable = True + + def __init__(self, _pc, _count): + self.pc = _pc + self.count = _count + + def getPC(self): + return self.pc + + def getCount(self): + return self.count + + def getValue(self): + from _m5.pc import PcCountPair + + return PcCountPair(self.pc, self.count) + + def __str__(self): + return "(%i,%i)" % (self.pc, self.count) + + def __eq__(self, other): + return self.pc == other.pc and self.count == other.count + + def __hash__(self): + return hash((int(self.pc), int(self.count))) + + class AddrRange(ParamValue): cxx_type = "AddrRange" @@ -2426,4 +2457,5 @@ def clear(): "VectorMasterPort", "VectorSlavePort", "DeprecatedParam", + "PcCountPair", ] diff --git a/src/python/pybind11/core.cc b/src/python/pybind11/core.cc index 89466750d0..98a859664b 100644 --- a/src/python/pybind11/core.cc +++ b/src/python/pybind11/core.cc @@ -163,6 +163,24 @@ init_range(py::module_ &m_native) m.def("RangeSize", &RangeSize); } +static void +init_pc(py::module_ &m_native) +{ + py::module_ m = m_native.def_submodule("pc"); + py::class_(m, "PcCountPair") + .def(py::init<>()) + .def(py::init()) + .def("__eq__", &PcCountPair::operator==) + .def("__str__", &PcCountPair::to_string) + // TODO: add __hash__ that matches the python __hash__ to enable + // hashing from the C++ class to the Python class. + // Currently the C++ class and the Python class use different hashing + // functions + .def("getPC", &PcCountPair::getPC) + .def("getCount", &PcCountPair::getCount) + ; +} + static void init_net(py::module_ &m_native) { @@ -307,6 +325,7 @@ pybind_init_core(py::module_ &m_native) init_range(m_native); init_net(m_native); init_loader(m_native); + init_pc(m_native); } } // namespace gem5