| /* | |
| * Copyright (c) 2000-2005 The Regents of The University of Michigan | |
| * Copyright (c) 2013 Advanced Micro Devices, Inc. | |
| * Copyright (c) 2013 Mark D. Hill and David A. Wood | |
| * 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. | |
| */ | |
| /* @file | |
| * EventQueue interfaces | |
| */ | |
| #ifndef __SIM_EVENTQ_HH__ | |
| #define __SIM_EVENTQ_HH__ | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <climits> | |
| #include <functional> | |
| #include <iosfwd> | |
| #include <list> | |
| #include <memory> | |
| #include <string> | |
| #include "base/debug.hh" | |
| #include "base/flags.hh" | |
| #include "base/named.hh" | |
| #include "base/trace.hh" | |
| #include "base/type_traits.hh" | |
| #include "base/types.hh" | |
| #include "base/uncontended_mutex.hh" | |
| #include "debug/Event.hh" | |
| #include "sim/cur_tick.hh" | |
| #include "sim/serialize.hh" | |
| namespace gem5 | |
| { | |
| class EventQueue; // forward declaration | |
| class BaseGlobalEvent; | |
| //! Simulation Quantum for multiple eventq simulation. | |
| //! The quantum value is the period length after which the queues | |
| //! synchronize themselves with each other. This means that any | |
| //! event to scheduled on Queue A which is generated by an event on | |
| //! Queue B should be at least simQuantum ticks away in future. | |
| extern Tick simQuantum; | |
| //! Current number of allocated main event queues. | |
| extern uint32_t numMainEventQueues; | |
| //! Array for main event queues. | |
| extern std::vector<EventQueue *> mainEventQueue; | |
| //! The current event queue for the running thread. Access to this queue | |
| //! does not require any locking from the thread. | |
| extern __thread EventQueue *_curEventQueue; | |
| //! Current mode of execution: parallel / serial | |
| extern bool inParallelMode; | |
| //! Function for returning eventq queue for the provided | |
| //! index. The function allocates a new queue in case one | |
| //! does not exist for the index, provided that the index | |
| //! is with in bounds. | |
| EventQueue *getEventQueue(uint32_t index); | |
| inline EventQueue *curEventQueue() { return _curEventQueue; } | |
| inline void curEventQueue(EventQueue *q); | |
| /** | |
| * Common base class for Event and GlobalEvent, so they can share flag | |
| * and priority definitions and accessor functions. This class should | |
| * not be used directly. | |
| */ | |
| class EventBase | |
| { | |
| protected: | |
| typedef unsigned short FlagsType; | |
| typedef ::gem5::Flags<FlagsType> Flags; | |
| static const FlagsType PublicRead = 0x003f; // public readable flags | |
| static const FlagsType PublicWrite = 0x001d; // public writable flags | |
| static const FlagsType Squashed = 0x0001; // has been squashed | |
| static const FlagsType Scheduled = 0x0002; // has been scheduled | |
| static const FlagsType Managed = 0x0004; // Use life cycle manager | |
| static const FlagsType AutoDelete = Managed; // delete after dispatch | |
| /** | |
| * This used to be AutoSerialize. This value can't be reused | |
| * without changing the checkpoint version since the flag field | |
| * gets serialized. | |
| */ | |
| static const FlagsType Reserved0 = 0x0008; | |
| static const FlagsType IsExitEvent = 0x0010; // special exit event | |
| static const FlagsType IsMainQueue = 0x0020; // on main event queue | |
| static const FlagsType Initialized = 0x7a40; // somewhat random bits | |
| static const FlagsType InitMask = 0xffc0; // mask for init bits | |
| public: | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| typedef int8_t Priority; | |
| /// Event priorities, to provide tie-breakers for events scheduled | |
| /// at the same cycle. Most events are scheduled at the default | |
| /// priority; these values are used to control events that need to | |
| /// be ordered within a cycle. | |
| /** | |
| * Minimum priority | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Minimum_Pri = SCHAR_MIN; | |
| /** | |
| * If we enable tracing on a particular cycle, do that as the | |
| * very first thing so we don't miss any of the events on | |
| * that cycle (even if we enter the debugger). | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Debug_Enable_Pri = -101; | |
| /** | |
| * Breakpoints should happen before anything else (except | |
| * enabling trace output), so we don't miss any action when | |
| * debugging. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Debug_Break_Pri = -100; | |
| /** | |
| * CPU switches schedule the new CPU's tick event for the | |
| * same cycle (after unscheduling the old CPU's tick event). | |
| * The switch needs to come before any tick events to make | |
| * sure we don't tick both CPUs in the same cycle. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority CPU_Switch_Pri = -31; | |
| /** | |
| * For some reason "delayed" inter-cluster writebacks are | |
| * scheduled before regular writebacks (which have default | |
| * priority). Steve? | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Delayed_Writeback_Pri = -1; | |
| /** | |
| * Default is zero for historical reasons. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Default_Pri = 0; | |
| /** | |
| * DVFS update event leads to stats dump therefore given a lower priority | |
| * to ensure all relevant states have been updated | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority DVFS_Update_Pri = 31; | |
| /** | |
| * Serailization needs to occur before tick events also, so | |
| * that a serialize/unserialize is identical to an on-line | |
| * CPU switch. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Serialize_Pri = 32; | |
| /** | |
| * CPU ticks must come after other associated CPU events | |
| * (such as writebacks). | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority CPU_Tick_Pri = 50; | |
| /** | |
| * If we want to exit a thread in a CPU, it comes after CPU_Tick_Pri | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority CPU_Exit_Pri = 64; | |
| /** | |
| * Statistics events (dump, reset, etc.) come after | |
| * everything else, but before exit. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Stat_Event_Pri = 90; | |
| /** | |
| * Progress events come at the end. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Progress_Event_Pri = 95; | |
| /** | |
| * If we want to exit on this cycle, it's the very last thing | |
| * we do. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Sim_Exit_Pri = 100; | |
| /** | |
| * Maximum priority | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| static const Priority Maximum_Pri = SCHAR_MAX; | |
| }; | |
| /* | |
| * An item on an event queue. The action caused by a given | |
| * event is specified by deriving a subclass and overriding the | |
| * process() member function. | |
| * | |
| * Caution, the order of members is chosen to maximize data packing. | |
| */ | |
| class Event : public EventBase, public Serializable | |
| { | |
| friend class EventQueue; | |
| private: | |
| // The event queue is now a linked list of linked lists. The | |
| // 'nextBin' pointer is to find the bin, where a bin is defined as | |
| // when+priority. All events in the same bin will be stored in a | |
| // second linked list (a stack) maintained by the 'nextInBin' | |
| // pointer. The list will be accessed in LIFO order. The end | |
| // result is that the insert/removal in 'nextBin' is | |
| // linear/constant, and the lookup/removal in 'nextInBin' is | |
| // constant/constant. Hopefully this is a significant improvement | |
| // over the current fully linear insertion. | |
| Event *nextBin; | |
| Event *nextInBin; | |
| static Event *insertBefore(Event *event, Event *curr); | |
| static Event *removeItem(Event *event, Event *last); | |
| Tick _when; //!< timestamp when event should be processed | |
| Priority _priority; //!< event priority | |
| Flags flags; | |
| #ifndef NDEBUG | |
| /// Global counter to generate unique IDs for Event instances | |
| static Counter instanceCounter; | |
| /// This event's unique ID. We can also use pointer values for | |
| /// this but they're not consistent across runs making debugging | |
| /// more difficult. Thus we use a global counter value when | |
| /// debugging. | |
| Counter instance; | |
| /// queue to which this event belongs (though it may or may not be | |
| /// scheduled on this queue yet) | |
| EventQueue *queue; | |
| #endif | |
| #ifdef EVENTQ_DEBUG | |
| Tick whenCreated; //!< time created | |
| Tick whenScheduled; //!< time scheduled | |
| #endif | |
| void | |
| setWhen(Tick when, EventQueue *q) | |
| { | |
| _when = when; | |
| #ifndef NDEBUG | |
| queue = q; | |
| #endif | |
| #ifdef EVENTQ_DEBUG | |
| whenScheduled = curTick(); | |
| #endif | |
| } | |
| bool | |
| initialized() const | |
| { | |
| return (flags & InitMask) == Initialized; | |
| } | |
| protected: | |
| Flags | |
| getFlags() const | |
| { | |
| return flags & PublicRead; | |
| } | |
| bool | |
| isFlagSet(Flags _flags) const | |
| { | |
| assert(_flags.noneSet(~PublicRead)); | |
| return flags.isSet(_flags); | |
| } | |
| void | |
| setFlags(Flags _flags) | |
| { | |
| assert(_flags.noneSet(~PublicWrite)); | |
| flags.set(_flags); | |
| } | |
| void | |
| clearFlags(Flags _flags) | |
| { | |
| assert(_flags.noneSet(~PublicWrite)); | |
| flags.clear(_flags); | |
| } | |
| void | |
| clearFlags() | |
| { | |
| flags.clear(PublicWrite); | |
| } | |
| /** | |
| * This function isn't really useful if TRACING_ON is not defined | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| virtual void trace(const char *action); //!< trace event activity | |
| /// Return the instance number as a string. | |
| const std::string instanceString() const; | |
| protected: /* Memory management */ | |
| /** | |
| * @{ | |
| * Memory management hooks for events that have the Managed flag set | |
| * | |
| * Events can use automatic memory management by setting the | |
| * Managed flag. The default implementation automatically deletes | |
| * events once they have been removed from the event queue. This | |
| * typically happens when events are descheduled or have been | |
| * triggered and not rescheduled. | |
| * | |
| * The methods below may be overridden by events that need custom | |
| * memory management. For example, events exported to Python need | |
| * to impement reference counting to ensure that the Python | |
| * implementation of the event is kept alive while it lives in the | |
| * event queue. | |
| * | |
| * @note Memory managers are responsible for implementing | |
| * reference counting (by overriding both acquireImpl() and | |
| * releaseImpl()) or checking if an event is no longer scheduled | |
| * in releaseImpl() before deallocating it. | |
| */ | |
| /** | |
| * Managed event scheduled and being held in the event queue. | |
| */ | |
| void acquire(); | |
| /** | |
| * Managed event removed from the event queue. | |
| */ | |
| void release(); | |
| virtual void acquireImpl(); | |
| virtual void releaseImpl(); | |
| /** @} */ | |
| public: | |
| /* | |
| * Event constructor | |
| * @param queue that the event gets scheduled on | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| Event(Priority p = Default_Pri, Flags f = 0) | |
| : nextBin(nullptr), nextInBin(nullptr), _when(0), _priority(p), | |
| flags(Initialized | f) | |
| { | |
| assert(f.noneSet(~PublicWrite)); | |
| #ifndef NDEBUG | |
| instance = ++instanceCounter; | |
| queue = NULL; | |
| #endif | |
| #ifdef EVENTQ_DEBUG | |
| whenCreated = curTick(); | |
| whenScheduled = 0; | |
| #endif | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| * @{ | |
| */ | |
| virtual ~Event(); | |
| virtual const std::string name() const; | |
| /// Return a C string describing the event. This string should | |
| /// *not* be dynamically allocated; just a const char array | |
| /// describing the event class. | |
| virtual const char *description() const; | |
| /// Dump the current event data | |
| void dump() const; | |
| /** @}*/ //end of api group | |
| public: | |
| /* | |
| * This member function is invoked when the event is processed | |
| * (occurs). There is no default implementation; each subclass | |
| * must provide its own implementation. The event is not | |
| * automatically deleted after it is processed (to allow for | |
| * statically allocated event objects). | |
| * | |
| * If the AutoDestroy flag is set, the object is deleted once it | |
| * is processed. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| virtual void process() = 0; | |
| /** | |
| * Determine if the current event is scheduled | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| bool scheduled() const { return flags.isSet(Scheduled); } | |
| /** | |
| * Squash the current event | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| void squash() { flags.set(Squashed); } | |
| /** | |
| * Check whether the event is squashed | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| bool squashed() const { return flags.isSet(Squashed); } | |
| /** | |
| * See if this is a SimExitEvent (without resorting to RTTI) | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| bool isExitEvent() const { return flags.isSet(IsExitEvent); } | |
| /** | |
| * Check whether this event will auto-delete | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| bool isManaged() const { return flags.isSet(Managed); } | |
| /** | |
| * The function returns true if the object is automatically | |
| * deleted after the event is processed. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| bool isAutoDelete() const { return isManaged(); } | |
| /** | |
| * Get the time that the event is scheduled | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| Tick when() const { return _when; } | |
| /** | |
| * Get the event priority | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| Priority priority() const { return _priority; } | |
| //! If this is part of a GlobalEvent, return the pointer to the | |
| //! Global Event. By default, there is no GlobalEvent, so return | |
| //! NULL. (Overridden in GlobalEvent::BarrierEvent.) | |
| virtual BaseGlobalEvent *globalEvent() { return NULL; } | |
| void serialize(CheckpointOut &cp) const override; | |
| void unserialize(CheckpointIn &cp) override; | |
| }; | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| inline bool | |
| operator<(const Event &l, const Event &r) | |
| { | |
| return l.when() < r.when() || | |
| (l.when() == r.when() && l.priority() < r.priority()); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| inline bool | |
| operator>(const Event &l, const Event &r) | |
| { | |
| return l.when() > r.when() || | |
| (l.when() == r.when() && l.priority() > r.priority()); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| inline bool | |
| operator<=(const Event &l, const Event &r) | |
| { | |
| return l.when() < r.when() || | |
| (l.when() == r.when() && l.priority() <= r.priority()); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| inline bool | |
| operator>=(const Event &l, const Event &r) | |
| { | |
| return l.when() > r.when() || | |
| (l.when() == r.when() && l.priority() >= r.priority()); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| inline bool | |
| operator==(const Event &l, const Event &r) | |
| { | |
| return l.when() == r.when() && l.priority() == r.priority(); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| inline bool | |
| operator!=(const Event &l, const Event &r) | |
| { | |
| return l.when() != r.when() || l.priority() != r.priority(); | |
| } | |
| /** | |
| * Queue of events sorted in time order | |
| * | |
| * Events are scheduled (inserted into the event queue) using the | |
| * schedule() method. This method either inserts a <i>synchronous</i> | |
| * or <i>asynchronous</i> event. | |
| * | |
| * Synchronous events are scheduled using schedule() method with the | |
| * argument 'global' set to false (default). This should only be done | |
| * from a thread holding the event queue lock | |
| * (EventQueue::service_mutex). The lock is always held when an event | |
| * handler is called, it can therefore always insert events into its | |
| * own event queue unless it voluntarily releases the lock. | |
| * | |
| * Events can be scheduled across thread (and event queue borders) by | |
| * either scheduling asynchronous events or taking the target event | |
| * queue's lock. However, the lock should <i>never</i> be taken | |
| * directly since this is likely to cause deadlocks. Instead, code | |
| * that needs to schedule events in other event queues should | |
| * temporarily release its own queue and lock the new queue. This | |
| * prevents deadlocks since a single thread never owns more than one | |
| * event queue lock. This functionality is provided by the | |
| * ScopedMigration helper class. Note that temporarily migrating | |
| * between event queues can make the simulation non-deterministic, it | |
| * should therefore be limited to cases where that can be tolerated | |
| * (e.g., handling asynchronous IO or fast-forwarding in KVM). | |
| * | |
| * Asynchronous events can also be scheduled using the normal | |
| * schedule() method with the 'global' parameter set to true. Unlike | |
| * the previous queue migration strategy, this strategy is fully | |
| * deterministic. This causes the event to be inserted in a separate | |
| * queue of asynchronous events (async_queue), which is merged main | |
| * event queue at the end of each simulation quantum (by calling the | |
| * handleAsyncInsertions() method). Note that this implies that such | |
| * events must happen at least one simulation quantum into the future, | |
| * otherwise they risk being scheduled in the past by | |
| * handleAsyncInsertions(). | |
| */ | |
| class EventQueue | |
| { | |
| private: | |
| friend void curEventQueue(EventQueue *); | |
| std::string objName; | |
| Event *head; | |
| Tick _curTick; | |
| //! Mutex to protect async queue. | |
| UncontendedMutex async_queue_mutex; | |
| //! List of events added by other threads to this event queue. | |
| std::list<Event*> async_queue; | |
| /** | |
| * Lock protecting event handling. | |
| * | |
| * This lock is always taken when servicing events. It is assumed | |
| * that the thread scheduling new events (not asynchronous events | |
| * though) have taken this lock. This is normally done by | |
| * serviceOne() since new events are typically scheduled as a | |
| * response to an earlier event. | |
| * | |
| * This lock is intended to be used to temporarily steal an event | |
| * queue to support inter-thread communication when some | |
| * deterministic timing can be sacrificed for speed. For example, | |
| * the KVM CPU can use this support to access devices running in a | |
| * different thread. | |
| * | |
| * @see EventQueue::ScopedMigration. | |
| * @see EventQueue::ScopedRelease | |
| * @see EventQueue::lock() | |
| * @see EventQueue::unlock() | |
| */ | |
| UncontendedMutex service_mutex; | |
| //! Insert / remove event from the queue. Should only be called | |
| //! by thread operating this queue. | |
| void insert(Event *event); | |
| void remove(Event *event); | |
| //! Function for adding events to the async queue. The added events | |
| //! are added to main event queue later. Threads, other than the | |
| //! owning thread, should call this function instead of insert(). | |
| void asyncInsert(Event *event); | |
| EventQueue(const EventQueue &); | |
| public: | |
| class ScopedMigration | |
| { | |
| public: | |
| /** | |
| * Temporarily migrate execution to a different event queue. | |
| * | |
| * An instance of this class temporarily migrates execution to | |
| * different event queue by releasing the current queue, locking | |
| * the new queue, and updating curEventQueue(). This can, for | |
| * example, be useful when performing IO across thread event | |
| * queues when timing is not crucial (e.g., during fast | |
| * forwarding). | |
| * | |
| * ScopedMigration does nothing if both eqs are the same | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| ScopedMigration(EventQueue *_new_eq, bool _doMigrate = true) | |
| :new_eq(*_new_eq), old_eq(*curEventQueue()), | |
| doMigrate((&new_eq != &old_eq)&&_doMigrate) | |
| { | |
| if (doMigrate){ | |
| old_eq.unlock(); | |
| new_eq.lock(); | |
| curEventQueue(&new_eq); | |
| } | |
| } | |
| ~ScopedMigration() | |
| { | |
| if (doMigrate){ | |
| new_eq.unlock(); | |
| old_eq.lock(); | |
| curEventQueue(&old_eq); | |
| } | |
| } | |
| private: | |
| EventQueue &new_eq; | |
| EventQueue &old_eq; | |
| bool doMigrate; | |
| }; | |
| class ScopedRelease | |
| { | |
| public: | |
| /** | |
| * Temporarily release the event queue service lock. | |
| * | |
| * There are cases where it is desirable to temporarily release | |
| * the event queue lock to prevent deadlocks. For example, when | |
| * waiting on the global barrier, we need to release the lock to | |
| * prevent deadlocks from happening when another thread tries to | |
| * temporarily take over the event queue waiting on the barrier. | |
| * | |
| * @group api_eventq | |
| */ | |
| ScopedRelease(EventQueue *_eq) | |
| : eq(*_eq) | |
| { | |
| eq.unlock(); | |
| } | |
| ~ScopedRelease() | |
| { | |
| eq.lock(); | |
| } | |
| private: | |
| EventQueue &eq; | |
| }; | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| EventQueue(const std::string &n); | |
| /** | |
| * @ingroup api_eventq | |
| * @{ | |
| */ | |
| virtual const std::string name() const { return objName; } | |
| void name(const std::string &st) { objName = st; } | |
| /** @}*/ //end of api_eventq group | |
| /** | |
| * Schedule the given event on this queue. Safe to call from any thread. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| schedule(Event *event, Tick when, bool global=false) | |
| { | |
| assert(when >= getCurTick()); | |
| assert(!event->scheduled()); | |
| assert(event->initialized()); | |
| event->setWhen(when, this); | |
| // The check below is to make sure of two things | |
| // a. A thread schedules local events on other queues through the | |
| // asyncq. | |
| // b. A thread schedules global events on the asyncq, whether or not | |
| // this event belongs to this eventq. This is required to maintain | |
| // a total order amongst the global events. See global_event.{cc,hh} | |
| // for more explanation. | |
| if (inParallelMode && (this != curEventQueue() || global)) { | |
| asyncInsert(event); | |
| } else { | |
| insert(event); | |
| } | |
| event->flags.set(Event::Scheduled); | |
| event->acquire(); | |
| if (debug::Event) | |
| event->trace("scheduled"); | |
| } | |
| /** | |
| * Deschedule the specified event. Should be called only from the owning | |
| * thread. | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| deschedule(Event *event) | |
| { | |
| assert(event->scheduled()); | |
| assert(event->initialized()); | |
| assert(!inParallelMode || this == curEventQueue()); | |
| remove(event); | |
| event->flags.clear(Event::Squashed); | |
| event->flags.clear(Event::Scheduled); | |
| if (debug::Event) | |
| event->trace("descheduled"); | |
| event->release(); | |
| } | |
| /** | |
| * Reschedule the specified event. Should be called only from the owning | |
| * thread. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| reschedule(Event *event, Tick when, bool always=false) | |
| { | |
| assert(when >= getCurTick()); | |
| assert(always || event->scheduled()); | |
| assert(event->initialized()); | |
| assert(!inParallelMode || this == curEventQueue()); | |
| if (event->scheduled()) { | |
| remove(event); | |
| } else { | |
| event->acquire(); | |
| } | |
| event->setWhen(when, this); | |
| insert(event); | |
| event->flags.clear(Event::Squashed); | |
| event->flags.set(Event::Scheduled); | |
| if (debug::Event) | |
| event->trace("rescheduled"); | |
| } | |
| Tick nextTick() const { return head->when(); } | |
| void setCurTick(Tick newVal) { _curTick = newVal; } | |
| /** | |
| * While curTick() is useful for any object assigned to this event queue, | |
| * if an object that is assigned to another event queue (or a non-event | |
| * object) need to access the current tick of this event queue, this | |
| * function is used. | |
| * | |
| * Tick is the unit of time used in gem5. | |
| * | |
| * @return Tick The current tick of this event queue. | |
| * @ingroup api_eventq | |
| */ | |
| Tick getCurTick() const { return _curTick; } | |
| Event *getHead() const { return head; } | |
| Event *serviceOne(); | |
| /** | |
| * process all events up to the given timestamp. we inline a quick test | |
| * to see if there are any events to process; if so, call the internal | |
| * out-of-line version to process them all. | |
| * | |
| * Notes: | |
| * - This is only used for "instruction" event queues. Instead of counting | |
| * ticks, this is actually counting instructions. | |
| * - This updates the current tick value to the value of the entry at the | |
| * head of the queue. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| serviceEvents(Tick when) | |
| { | |
| while (!empty()) { | |
| if (nextTick() > when) | |
| break; | |
| /** | |
| * @todo this assert is a good bug catcher. I need to | |
| * make it true again. | |
| */ | |
| //assert(head->when() >= when && "event scheduled in the past"); | |
| serviceOne(); | |
| } | |
| setCurTick(when); | |
| } | |
| /** | |
| * Returns true if no events are queued | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| bool empty() const { return head == NULL; } | |
| /** | |
| * This is a debugging function which will print everything on the event | |
| * queue. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| void dump() const; | |
| bool debugVerify() const; | |
| /** | |
| * Function for moving events from the async_queue to the main queue. | |
| */ | |
| void handleAsyncInsertions(); | |
| /** | |
| * Function to signal that the event loop should be woken up because | |
| * an event has been scheduled by an agent outside the gem5 event | |
| * loop(s) whose event insertion may not have been noticed by gem5. | |
| * This function isn't needed by the usual gem5 event loop but may | |
| * be necessary in derived EventQueues which host gem5 onto other | |
| * schedulers. | |
| * | |
| * @param when Time of a delayed wakeup (if known). This parameter | |
| * can be used by an implementation to schedule a wakeup in the | |
| * future if it is sure it will remain active until then. | |
| * Or it can be ignored and the event queue can be woken up now. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| virtual void wakeup(Tick when = (Tick)-1) { } | |
| /** | |
| * function for replacing the head of the event queue, so that a | |
| * different set of events can run without disturbing events that have | |
| * already been scheduled. Already scheduled events can be processed | |
| * by replacing the original head back. | |
| * USING THIS FUNCTION CAN BE DANGEROUS TO THE HEALTH OF THE SIMULATOR. | |
| * NOT RECOMMENDED FOR USE. | |
| */ | |
| Event* replaceHead(Event* s); | |
| /**@{*/ | |
| /** | |
| * Provide an interface for locking/unlocking the event queue. | |
| * | |
| * @warn Do NOT use these methods directly unless you really know | |
| * what you are doing. Incorrect use can easily lead to simulator | |
| * deadlocks. | |
| * | |
| * @see EventQueue::ScopedMigration. | |
| * @see EventQueue::ScopedRelease | |
| * @see EventQueue | |
| */ | |
| void lock() { service_mutex.lock(); } | |
| void unlock() { service_mutex.unlock(); } | |
| /**@}*/ | |
| /** | |
| * Reschedule an event after a checkpoint. | |
| * | |
| * Since events don't know which event queue they belong to, | |
| * parent objects need to reschedule events themselves. This | |
| * method conditionally schedules an event that has the Scheduled | |
| * flag set. It should be called by parent objects after | |
| * unserializing an object. | |
| * | |
| * @warn Only use this method after unserializing an Event. | |
| */ | |
| void checkpointReschedule(Event *event); | |
| virtual ~EventQueue() | |
| { | |
| while (!empty()) | |
| deschedule(getHead()); | |
| } | |
| }; | |
| inline void | |
| curEventQueue(EventQueue *q) | |
| { | |
| _curEventQueue = q; | |
| Gem5Internal::_curTickPtr = (q == nullptr) ? nullptr : &q->_curTick; | |
| } | |
| void dumpMainQueue(); | |
| class EventManager | |
| { | |
| protected: | |
| /** A pointer to this object's event queue */ | |
| EventQueue *eventq; | |
| public: | |
| /** | |
| * Event manger manages events in the event queue. Where | |
| * you can schedule and deschedule different events. | |
| * | |
| * @ingroup api_eventq | |
| * @{ | |
| */ | |
| EventManager(EventManager &em) : eventq(em.eventq) {} | |
| EventManager(EventManager *em) : eventq(em->eventq) {} | |
| EventManager(EventQueue *eq) : eventq(eq) {} | |
| /** @}*/ //end of api_eventq group | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| EventQueue * | |
| eventQueue() const | |
| { | |
| return eventq; | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| schedule(Event &event, Tick when) | |
| { | |
| eventq->schedule(&event, when); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| deschedule(Event &event) | |
| { | |
| eventq->deschedule(&event); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| reschedule(Event &event, Tick when, bool always = false) | |
| { | |
| eventq->reschedule(&event, when, always); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| schedule(Event *event, Tick when) | |
| { | |
| eventq->schedule(event, when); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| deschedule(Event *event) | |
| { | |
| eventq->deschedule(event); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| void | |
| reschedule(Event *event, Tick when, bool always = false) | |
| { | |
| eventq->reschedule(event, when, always); | |
| } | |
| /** | |
| * This function is not needed by the usual gem5 event loop | |
| * but may be necessary in derived EventQueues which host gem5 | |
| * on other schedulers. | |
| * @ingroup api_eventq | |
| */ | |
| void wakeupEventQueue(Tick when = (Tick)-1) | |
| { | |
| eventq->wakeup(when); | |
| } | |
| void setCurTick(Tick newVal) { eventq->setCurTick(newVal); } | |
| }; | |
| /** | |
| * @brief Wrap a member function inside MemberEventWrapper to use it as an | |
| * event callback. This wrapper should be prefered over EventFunctionWrapper | |
| * for better performance and type safety. | |
| * | |
| * Wrapping a function *process* member of a class *klass* can be done by | |
| * adding a member variable of the following type: | |
| * MemberEventWrapper<&klass::process>. | |
| * | |
| * It is required that klass::process takes no explicit argument and returns no | |
| * value as these could not be handled by the event scheduler. | |
| * | |
| * @tparam F Pointer to the member function wrapped in this event. | |
| */ | |
| template <auto F> | |
| class MemberEventWrapper final: public Event, public Named | |
| { | |
| using CLASS = MemberFunctionClass_t<F>; | |
| static_assert(std::is_same_v<void, MemberFunctionReturn_t<F>>); | |
| static_assert(std::is_same_v<MemberFunctionArgsTuple_t<F>, std::tuple<>>); | |
| public: | |
| [[deprecated("Use reference version of this constructor instead")]] | |
| MemberEventWrapper(CLASS *object, | |
| bool del = false, | |
| Priority p = Default_Pri): | |
| MemberEventWrapper{*object, del, p} | |
| {} | |
| /** | |
| * @brief Construct a new MemberEventWrapper object | |
| * | |
| * @param object instance of the object to call the wrapped member func on | |
| * @param del if true, flag this event as AutoDelete | |
| * @param p priority of this event | |
| */ | |
| MemberEventWrapper(CLASS &object, | |
| bool del = false, | |
| Priority p = Default_Pri): | |
| Event(p), | |
| Named(object.name() + ".wrapped_event"), | |
| mObject(&object) | |
| { | |
| if (del) setFlags(AutoDelete); | |
| gem5_assert(mObject); | |
| } | |
| void process() override { | |
| (mObject->*F)(); | |
| } | |
| const char *description() const override { return "EventWrapped"; } | |
| private: | |
| CLASS *mObject; | |
| }; | |
| template <class T, void (T::* F)()> | |
| using EventWrapper [[deprecated("Use MemberEventWrapper instead")]] | |
| = MemberEventWrapper<F>; | |
| class EventFunctionWrapper : public Event | |
| { | |
| private: | |
| std::function<void(void)> callback; | |
| std::string _name; | |
| public: | |
| /** | |
| * This function wraps a function into an event, to be | |
| * executed later. | |
| * | |
| * @ingroup api_eventq | |
| */ | |
| EventFunctionWrapper(const std::function<void(void)> &callback, | |
| const std::string &name, | |
| bool del = false, | |
| Priority p = Default_Pri) | |
| : Event(p), callback(callback), _name(name) | |
| { | |
| if (del) | |
| setFlags(AutoDelete); | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| void process() { callback(); } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| const std::string | |
| name() const | |
| { | |
| return _name + ".wrapped_function_event"; | |
| } | |
| /** | |
| * @ingroup api_eventq | |
| */ | |
| const char *description() const { return "EventFunctionWrapped"; } | |
| }; | |
| /** | |
| * \def SERIALIZE_EVENT(event) | |
| * | |
| * @ingroup api_serialize | |
| */ | |
| #define SERIALIZE_EVENT(event) event.serializeSection(cp, #event); | |
| /** | |
| * \def UNSERIALIZE_EVENT(event) | |
| * | |
| * @ingroup api_serialize | |
| */ | |
| #define UNSERIALIZE_EVENT(event) \ | |
| do { \ | |
| event.unserializeSection(cp, #event); \ | |
| eventQueue()->checkpointReschedule(&event); \ | |
| } while (0) | |
| } // namespace gem5 | |
| #endif // __SIM_EVENTQ_HH__ |