eventq.hh revision 11800:54436a1784dc
1/*
2 * Copyright (c) 2000-2005 The Regents of The University of Michigan
3 * Copyright (c) 2013 Advanced Micro Devices, Inc.
4 * Copyright (c) 2013 Mark D. Hill and David A. Wood
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met: redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer;
11 * redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution;
14 * neither the name of the copyright holders nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 * Authors: Steve Reinhardt
31 *          Nathan Binkert
32 */
33
34/* @file
35 * EventQueue interfaces
36 */
37
38#ifndef __SIM_EVENTQ_HH__
39#define __SIM_EVENTQ_HH__
40
41#include <algorithm>
42#include <cassert>
43#include <climits>
44#include <iosfwd>
45#include <memory>
46#include <mutex>
47#include <string>
48
49#include "base/flags.hh"
50#include "base/types.hh"
51#include "debug/Event.hh"
52#include "sim/serialize.hh"
53
54class EventQueue;       // forward declaration
55class BaseGlobalEvent;
56
57//! Simulation Quantum for multiple eventq simulation.
58//! The quantum value is the period length after which the queues
59//! synchronize themselves with each other. This means that any
60//! event to scheduled on Queue A which is generated by an event on
61//! Queue B should be at least simQuantum ticks away in future.
62extern Tick simQuantum;
63
64//! Current number of allocated main event queues.
65extern uint32_t numMainEventQueues;
66
67//! Array for main event queues.
68extern std::vector<EventQueue *> mainEventQueue;
69
70#ifndef SWIG
71//! The current event queue for the running thread. Access to this queue
72//! does not require any locking from the thread.
73
74extern __thread EventQueue *_curEventQueue;
75
76#endif
77
78//! Current mode of execution: parallel / serial
79extern bool inParallelMode;
80
81//! Function for returning eventq queue for the provided
82//! index. The function allocates a new queue in case one
83//! does not exist for the index, provided that the index
84//! is with in bounds.
85EventQueue *getEventQueue(uint32_t index);
86
87inline EventQueue *curEventQueue() { return _curEventQueue; }
88inline void curEventQueue(EventQueue *q) { _curEventQueue = q; }
89
90/**
91 * Common base class for Event and GlobalEvent, so they can share flag
92 * and priority definitions and accessor functions.  This class should
93 * not be used directly.
94 */
95class EventBase
96{
97  protected:
98    typedef unsigned short FlagsType;
99    typedef ::Flags<FlagsType> Flags;
100
101    static const FlagsType PublicRead    = 0x003f; // public readable flags
102    static const FlagsType PublicWrite   = 0x001d; // public writable flags
103    static const FlagsType Squashed      = 0x0001; // has been squashed
104    static const FlagsType Scheduled     = 0x0002; // has been scheduled
105    static const FlagsType AutoDelete    = 0x0004; // delete after dispatch
106    /**
107     * This used to be AutoSerialize. This value can't be reused
108     * without changing the checkpoint version since the flag field
109     * gets serialized.
110     */
111    static const FlagsType Reserved0     = 0x0008;
112    static const FlagsType IsExitEvent   = 0x0010; // special exit event
113    static const FlagsType IsMainQueue   = 0x0020; // on main event queue
114    static const FlagsType Initialized   = 0x7a40; // somewhat random bits
115    static const FlagsType InitMask      = 0xffc0; // mask for init bits
116
117  public:
118    typedef int8_t Priority;
119
120    /// Event priorities, to provide tie-breakers for events scheduled
121    /// at the same cycle.  Most events are scheduled at the default
122    /// priority; these values are used to control events that need to
123    /// be ordered within a cycle.
124
125    /// Minimum priority
126    static const Priority Minimum_Pri =          SCHAR_MIN;
127
128    /// If we enable tracing on a particular cycle, do that as the
129    /// very first thing so we don't miss any of the events on
130    /// that cycle (even if we enter the debugger).
131    static const Priority Debug_Enable_Pri =          -101;
132
133    /// Breakpoints should happen before anything else (except
134    /// enabling trace output), so we don't miss any action when
135    /// debugging.
136    static const Priority Debug_Break_Pri =           -100;
137
138    /// CPU switches schedule the new CPU's tick event for the
139    /// same cycle (after unscheduling the old CPU's tick event).
140    /// The switch needs to come before any tick events to make
141    /// sure we don't tick both CPUs in the same cycle.
142    static const Priority CPU_Switch_Pri =             -31;
143
144    /// For some reason "delayed" inter-cluster writebacks are
145    /// scheduled before regular writebacks (which have default
146    /// priority).  Steve?
147    static const Priority Delayed_Writeback_Pri =       -1;
148
149    /// Default is zero for historical reasons.
150    static const Priority Default_Pri =                  0;
151
152    /// DVFS update event leads to stats dump therefore given a lower priority
153    /// to ensure all relevant states have been updated
154    static const Priority DVFS_Update_Pri =             31;
155
156    /// Serailization needs to occur before tick events also, so
157    /// that a serialize/unserialize is identical to an on-line
158    /// CPU switch.
159    static const Priority Serialize_Pri =               32;
160
161    /// CPU ticks must come after other associated CPU events
162    /// (such as writebacks).
163    static const Priority CPU_Tick_Pri =                50;
164
165    /// Statistics events (dump, reset, etc.) come after
166    /// everything else, but before exit.
167    static const Priority Stat_Event_Pri =              90;
168
169    /// Progress events come at the end.
170    static const Priority Progress_Event_Pri =          95;
171
172    /// If we want to exit on this cycle, it's the very last thing
173    /// we do.
174    static const Priority Sim_Exit_Pri =               100;
175
176    /// Maximum priority
177    static const Priority Maximum_Pri =          SCHAR_MAX;
178};
179
180/*
181 * An item on an event queue.  The action caused by a given
182 * event is specified by deriving a subclass and overriding the
183 * process() member function.
184 *
185 * Caution, the order of members is chosen to maximize data packing.
186 */
187class Event : public EventBase, public Serializable
188{
189    friend class EventQueue;
190
191  private:
192    // The event queue is now a linked list of linked lists.  The
193    // 'nextBin' pointer is to find the bin, where a bin is defined as
194    // when+priority.  All events in the same bin will be stored in a
195    // second linked list (a stack) maintained by the 'nextInBin'
196    // pointer.  The list will be accessed in LIFO order.  The end
197    // result is that the insert/removal in 'nextBin' is
198    // linear/constant, and the lookup/removal in 'nextInBin' is
199    // constant/constant.  Hopefully this is a significant improvement
200    // over the current fully linear insertion.
201    Event *nextBin;
202    Event *nextInBin;
203
204    static Event *insertBefore(Event *event, Event *curr);
205    static Event *removeItem(Event *event, Event *last);
206
207    Tick _when;         //!< timestamp when event should be processed
208    Priority _priority; //!< event priority
209    Flags flags;
210
211#ifndef NDEBUG
212    /// Global counter to generate unique IDs for Event instances
213    static Counter instanceCounter;
214
215    /// This event's unique ID.  We can also use pointer values for
216    /// this but they're not consistent across runs making debugging
217    /// more difficult.  Thus we use a global counter value when
218    /// debugging.
219    Counter instance;
220
221    /// queue to which this event belongs (though it may or may not be
222    /// scheduled on this queue yet)
223    EventQueue *queue;
224#endif
225
226#ifdef EVENTQ_DEBUG
227    Tick whenCreated;   //!< time created
228    Tick whenScheduled; //!< time scheduled
229#endif
230
231    void
232    setWhen(Tick when, EventQueue *q)
233    {
234        _when = when;
235#ifndef NDEBUG
236        queue = q;
237#endif
238#ifdef EVENTQ_DEBUG
239        whenScheduled = curTick();
240#endif
241    }
242
243    bool
244    initialized() const
245    {
246        return (flags & InitMask) == Initialized;
247    }
248
249  protected:
250    /// Accessor for flags.
251    Flags
252    getFlags() const
253    {
254        return flags & PublicRead;
255    }
256
257    bool
258    isFlagSet(Flags _flags) const
259    {
260        assert(_flags.noneSet(~PublicRead));
261        return flags.isSet(_flags);
262    }
263
264    /// Accessor for flags.
265    void
266    setFlags(Flags _flags)
267    {
268        assert(_flags.noneSet(~PublicWrite));
269        flags.set(_flags);
270    }
271
272    void
273    clearFlags(Flags _flags)
274    {
275        assert(_flags.noneSet(~PublicWrite));
276        flags.clear(_flags);
277    }
278
279    void
280    clearFlags()
281    {
282        flags.clear(PublicWrite);
283    }
284
285    // This function isn't really useful if TRACING_ON is not defined
286    virtual void trace(const char *action);     //!< trace event activity
287
288  public:
289
290    /*
291     * Event constructor
292     * @param queue that the event gets scheduled on
293     */
294    Event(Priority p = Default_Pri, Flags f = 0)
295        : nextBin(nullptr), nextInBin(nullptr), _when(0), _priority(p),
296          flags(Initialized | f)
297    {
298        assert(f.noneSet(~PublicWrite));
299#ifndef NDEBUG
300        instance = ++instanceCounter;
301        queue = NULL;
302#endif
303#ifdef EVENTQ_DEBUG
304        whenCreated = curTick();
305        whenScheduled = 0;
306#endif
307    }
308
309    virtual ~Event();
310    virtual const std::string name() const;
311
312    /// Return a C string describing the event.  This string should
313    /// *not* be dynamically allocated; just a const char array
314    /// describing the event class.
315    virtual const char *description() const;
316
317    /// Dump the current event data
318    void dump() const;
319
320  public:
321    /*
322     * This member function is invoked when the event is processed
323     * (occurs).  There is no default implementation; each subclass
324     * must provide its own implementation.  The event is not
325     * automatically deleted after it is processed (to allow for
326     * statically allocated event objects).
327     *
328     * If the AutoDestroy flag is set, the object is deleted once it
329     * is processed.
330     */
331    virtual void process() = 0;
332
333    /// Determine if the current event is scheduled
334    bool scheduled() const { return flags.isSet(Scheduled); }
335
336    /// Squash the current event
337    void squash() { flags.set(Squashed); }
338
339    /// Check whether the event is squashed
340    bool squashed() const { return flags.isSet(Squashed); }
341
342    /// See if this is a SimExitEvent (without resorting to RTTI)
343    bool isExitEvent() const { return flags.isSet(IsExitEvent); }
344
345    /// Check whether this event will auto-delete
346    bool isAutoDelete() const { return flags.isSet(AutoDelete); }
347
348    /// Get the time that the event is scheduled
349    Tick when() const { return _when; }
350
351    /// Get the event priority
352    Priority priority() const { return _priority; }
353
354    //! If this is part of a GlobalEvent, return the pointer to the
355    //! Global Event.  By default, there is no GlobalEvent, so return
356    //! NULL.  (Overridden in GlobalEvent::BarrierEvent.)
357    virtual BaseGlobalEvent *globalEvent() { return NULL; }
358
359#ifndef SWIG
360    void serialize(CheckpointOut &cp) const override;
361    void unserialize(CheckpointIn &cp) override;
362#endif
363};
364
365#ifndef SWIG
366inline bool
367operator<(const Event &l, const Event &r)
368{
369    return l.when() < r.when() ||
370        (l.when() == r.when() && l.priority() < r.priority());
371}
372
373inline bool
374operator>(const Event &l, const Event &r)
375{
376    return l.when() > r.when() ||
377        (l.when() == r.when() && l.priority() > r.priority());
378}
379
380inline bool
381operator<=(const Event &l, const Event &r)
382{
383    return l.when() < r.when() ||
384        (l.when() == r.when() && l.priority() <= r.priority());
385}
386inline bool
387operator>=(const Event &l, const Event &r)
388{
389    return l.when() > r.when() ||
390        (l.when() == r.when() && l.priority() >= r.priority());
391}
392
393inline bool
394operator==(const Event &l, const Event &r)
395{
396    return l.when() == r.when() && l.priority() == r.priority();
397}
398
399inline bool
400operator!=(const Event &l, const Event &r)
401{
402    return l.when() != r.when() || l.priority() != r.priority();
403}
404#endif
405
406/**
407 * Queue of events sorted in time order
408 *
409 * Events are scheduled (inserted into the event queue) using the
410 * schedule() method. This method either inserts a <i>synchronous</i>
411 * or <i>asynchronous</i> event.
412 *
413 * Synchronous events are scheduled using schedule() method with the
414 * argument 'global' set to false (default). This should only be done
415 * from a thread holding the event queue lock
416 * (EventQueue::service_mutex). The lock is always held when an event
417 * handler is called, it can therefore always insert events into its
418 * own event queue unless it voluntarily releases the lock.
419 *
420 * Events can be scheduled across thread (and event queue borders) by
421 * either scheduling asynchronous events or taking the target event
422 * queue's lock. However, the lock should <i>never</i> be taken
423 * directly since this is likely to cause deadlocks. Instead, code
424 * that needs to schedule events in other event queues should
425 * temporarily release its own queue and lock the new queue. This
426 * prevents deadlocks since a single thread never owns more than one
427 * event queue lock. This functionality is provided by the
428 * ScopedMigration helper class. Note that temporarily migrating
429 * between event queues can make the simulation non-deterministic, it
430 * should therefore be limited to cases where that can be tolerated
431 * (e.g., handling asynchronous IO or fast-forwarding in KVM).
432 *
433 * Asynchronous events can also be scheduled using the normal
434 * schedule() method with the 'global' parameter set to true. Unlike
435 * the previous queue migration strategy, this strategy is fully
436 * deterministic. This causes the event to be inserted in a separate
437 * queue of asynchronous events (async_queue), which is merged main
438 * event queue at the end of each simulation quantum (by calling the
439 * handleAsyncInsertions() method). Note that this implies that such
440 * events must happen at least one simulation quantum into the future,
441 * otherwise they risk being scheduled in the past by
442 * handleAsyncInsertions().
443 */
444class EventQueue
445{
446  private:
447    std::string objName;
448    Event *head;
449    Tick _curTick;
450
451    //! Mutex to protect async queue.
452    std::mutex async_queue_mutex;
453
454    //! List of events added by other threads to this event queue.
455    std::list<Event*> async_queue;
456
457    /**
458     * Lock protecting event handling.
459     *
460     * This lock is always taken when servicing events. It is assumed
461     * that the thread scheduling new events (not asynchronous events
462     * though) have taken this lock. This is normally done by
463     * serviceOne() since new events are typically scheduled as a
464     * response to an earlier event.
465     *
466     * This lock is intended to be used to temporarily steal an event
467     * queue to support inter-thread communication when some
468     * deterministic timing can be sacrificed for speed. For example,
469     * the KVM CPU can use this support to access devices running in a
470     * different thread.
471     *
472     * @see EventQueue::ScopedMigration.
473     * @see EventQueue::ScopedRelease
474     * @see EventQueue::lock()
475     * @see EventQueue::unlock()
476     */
477    std::mutex service_mutex;
478
479    //! Insert / remove event from the queue. Should only be called
480    //! by thread operating this queue.
481    void insert(Event *event);
482    void remove(Event *event);
483
484    //! Function for adding events to the async queue. The added events
485    //! are added to main event queue later. Threads, other than the
486    //! owning thread, should call this function instead of insert().
487    void asyncInsert(Event *event);
488
489    EventQueue(const EventQueue &);
490
491  public:
492#ifndef SWIG
493    /**
494     * Temporarily migrate execution to a different event queue.
495     *
496     * An instance of this class temporarily migrates execution to a
497     * different event queue by releasing the current queue, locking
498     * the new queue, and updating curEventQueue(). This can, for
499     * example, be useful when performing IO across thread event
500     * queues when timing is not crucial (e.g., during fast
501     * forwarding).
502     */
503    class ScopedMigration
504    {
505      public:
506        ScopedMigration(EventQueue *_new_eq)
507            :  new_eq(*_new_eq), old_eq(*curEventQueue())
508        {
509            old_eq.unlock();
510            new_eq.lock();
511            curEventQueue(&new_eq);
512        }
513
514        ~ScopedMigration()
515        {
516            new_eq.unlock();
517            old_eq.lock();
518            curEventQueue(&old_eq);
519        }
520
521      private:
522        EventQueue &new_eq;
523        EventQueue &old_eq;
524    };
525
526    /**
527     * Temporarily release the event queue service lock.
528     *
529     * There are cases where it is desirable to temporarily release
530     * the event queue lock to prevent deadlocks. For example, when
531     * waiting on the global barrier, we need to release the lock to
532     * prevent deadlocks from happening when another thread tries to
533     * temporarily take over the event queue waiting on the barrier.
534     */
535    class ScopedRelease
536    {
537      public:
538        ScopedRelease(EventQueue *_eq)
539            :  eq(*_eq)
540        {
541            eq.unlock();
542        }
543
544        ~ScopedRelease()
545        {
546            eq.lock();
547        }
548
549      private:
550        EventQueue &eq;
551    };
552#endif
553
554    EventQueue(const std::string &n);
555
556    virtual const std::string name() const { return objName; }
557    void name(const std::string &st) { objName = st; }
558
559    //! Schedule the given event on this queue. Safe to call from any
560    //! thread.
561    void schedule(Event *event, Tick when, bool global = false);
562
563    //! Deschedule the specified event. Should be called only from the
564    //! owning thread.
565    void deschedule(Event *event);
566
567    //! Reschedule the specified event. Should be called only from
568    //! the owning thread.
569    void reschedule(Event *event, Tick when, bool always = false);
570
571    Tick nextTick() const { return head->when(); }
572    void setCurTick(Tick newVal) { _curTick = newVal; }
573    Tick getCurTick() const { return _curTick; }
574    Event *getHead() const { return head; }
575
576    Event *serviceOne();
577
578    // process all events up to the given timestamp.  we inline a
579    // quick test to see if there are any events to process; if so,
580    // call the internal out-of-line version to process them all.
581    void
582    serviceEvents(Tick when)
583    {
584        while (!empty()) {
585            if (nextTick() > when)
586                break;
587
588            /**
589             * @todo this assert is a good bug catcher.  I need to
590             * make it true again.
591             */
592            //assert(head->when() >= when && "event scheduled in the past");
593            serviceOne();
594        }
595
596        setCurTick(when);
597    }
598
599    // return true if no events are queued
600    bool empty() const { return head == NULL; }
601
602    void dump() const;
603
604    bool debugVerify() const;
605
606    //! Function for moving events from the async_queue to the main queue.
607    void handleAsyncInsertions();
608
609    /**
610     *  Function to signal that the event loop should be woken up because
611     *  an event has been scheduled by an agent outside the gem5 event
612     *  loop(s) whose event insertion may not have been noticed by gem5.
613     *  This function isn't needed by the usual gem5 event loop but may
614     *  be necessary in derived EventQueues which host gem5 onto other
615     *  schedulers.
616     *
617     *  @param when Time of a delayed wakeup (if known). This parameter
618     *  can be used by an implementation to schedule a wakeup in the
619     *  future if it is sure it will remain active until then.
620     *  Or it can be ignored and the event queue can be woken up now.
621     */
622    virtual void wakeup(Tick when = (Tick)-1) { }
623
624    /**
625     *  function for replacing the head of the event queue, so that a
626     *  different set of events can run without disturbing events that have
627     *  already been scheduled. Already scheduled events can be processed
628     *  by replacing the original head back.
629     *  USING THIS FUNCTION CAN BE DANGEROUS TO THE HEALTH OF THE SIMULATOR.
630     *  NOT RECOMMENDED FOR USE.
631     */
632    Event* replaceHead(Event* s);
633
634    /**@{*/
635    /**
636     * Provide an interface for locking/unlocking the event queue.
637     *
638     * @warn Do NOT use these methods directly unless you really know
639     * what you are doing. Incorrect use can easily lead to simulator
640     * deadlocks.
641     *
642     * @see EventQueue::ScopedMigration.
643     * @see EventQueue::ScopedRelease
644     * @see EventQueue
645     */
646    void lock() { service_mutex.lock(); }
647    void unlock() { service_mutex.unlock(); }
648    /**@}*/
649
650    /**
651     * Reschedule an event after a checkpoint.
652     *
653     * Since events don't know which event queue they belong to,
654     * parent objects need to reschedule events themselves. This
655     * method conditionally schedules an event that has the Scheduled
656     * flag set. It should be called by parent objects after
657     * unserializing an object.
658     *
659     * @warn Only use this method after unserializing an Event.
660     */
661    void checkpointReschedule(Event *event);
662
663    virtual ~EventQueue() { }
664};
665
666void dumpMainQueue();
667
668#ifndef SWIG
669class EventManager
670{
671  protected:
672    /** A pointer to this object's event queue */
673    EventQueue *eventq;
674
675  public:
676    EventManager(EventManager &em) : eventq(em.eventq) {}
677    EventManager(EventManager *em) : eventq(em->eventq) {}
678    EventManager(EventQueue *eq) : eventq(eq) {}
679
680    EventQueue *
681    eventQueue() const
682    {
683        return eventq;
684    }
685
686    void
687    schedule(Event &event, Tick when)
688    {
689        eventq->schedule(&event, when);
690    }
691
692    void
693    deschedule(Event &event)
694    {
695        eventq->deschedule(&event);
696    }
697
698    void
699    reschedule(Event &event, Tick when, bool always = false)
700    {
701        eventq->reschedule(&event, when, always);
702    }
703
704    void
705    schedule(Event *event, Tick when)
706    {
707        eventq->schedule(event, when);
708    }
709
710    void
711    deschedule(Event *event)
712    {
713        eventq->deschedule(event);
714    }
715
716    void
717    reschedule(Event *event, Tick when, bool always = false)
718    {
719        eventq->reschedule(event, when, always);
720    }
721
722    void wakeupEventQueue(Tick when = (Tick)-1)
723    {
724        eventq->wakeup(when);
725    }
726
727    void setCurTick(Tick newVal) { eventq->setCurTick(newVal); }
728};
729
730template <class T, void (T::* F)()>
731void
732DelayFunction(EventQueue *eventq, Tick when, T *object)
733{
734    class DelayEvent : public Event
735    {
736      private:
737        T *object;
738
739      public:
740        DelayEvent(T *o)
741            : Event(Default_Pri, AutoDelete), object(o)
742        { }
743        void process() { (object->*F)(); }
744        const char *description() const { return "delay"; }
745    };
746
747    eventq->schedule(new DelayEvent(object), when);
748}
749
750template <class T, void (T::* F)()>
751class EventWrapper : public Event
752{
753  private:
754    T *object;
755
756  public:
757    EventWrapper(T *obj, bool del = false, Priority p = Default_Pri)
758        : Event(p), object(obj)
759    {
760        if (del)
761            setFlags(AutoDelete);
762    }
763
764    EventWrapper(T &obj, bool del = false, Priority p = Default_Pri)
765        : Event(p), object(&obj)
766    {
767        if (del)
768            setFlags(AutoDelete);
769    }
770
771    void process() { (object->*F)(); }
772
773    const std::string
774    name() const
775    {
776        return object->name() + ".wrapped_event";
777    }
778
779    const char *description() const { return "EventWrapped"; }
780};
781#endif
782
783#endif // __SIM_EVENTQ_HH__
784