dev: Add m5 op to toggle synchronization for dist-gem5.
authorMichael LeBeane <michael.lebeane@amd.com>
Thu, 27 Oct 2016 02:48:40 +0000 (22:48 -0400)
committerMichael LeBeane <michael.lebeane@amd.com>
Thu, 27 Oct 2016 02:48:40 +0000 (22:48 -0400)
This patch adds the ability for an application to request dist-gem5 to begin/
end synchronization using an m5 op. When toggling on sync, all nodes agree
on the next sync point based on the maximum of all nodes' ticks. CPUs are
suspended until the sync point to avoid sending network messages until sync has
been enabled. Toggling off sync acts like a global execution barrier, where
all CPUs are disabled until every node reaches the toggle off point. This
avoids tricky situations such as one node hitting a toggle off followed by a
toggle on before the other nodes hit the first toggle off.

14 files changed:
configs/common/Options.py
src/arch/x86/isa/decoder/two_byte_opcodes.isa
src/dev/net/Ethernet.py
src/dev/net/dist_etherlink.cc
src/dev/net/dist_iface.cc
src/dev/net/dist_iface.hh
src/dev/net/dist_packet.hh
src/dev/net/tcp_iface.cc
src/dev/net/tcp_iface.hh
src/sim/pseudo_inst.cc
src/sim/pseudo_inst.hh
util/m5/m5op.h
util/m5/m5op_x86.S
util/m5/m5ops.h

index 16d817f599b71bce35b5d5675a330822e987a277..a3335c7ef02a6651b78fc0f4d1de54979d0dc1ea 100644 (file)
@@ -169,6 +169,8 @@ def addCommonOptions(parser):
     # dist-gem5 options
     parser.add_option("--dist", action="store_true",
                       help="Parallel distributed gem5 simulation.")
+    parser.add_option("--dist-sync-on-pseudo-op", action="store_true",
+                      help="Use a pseudo-op to start dist-gem5 synchronization.")
     parser.add_option("--is-switch", action="store_true",
                       help="Select the network switch simulator process for a"\
                       "distributed gem5 run")
index 01e8e9b0ce88b584c213b204411de993e4c09c8d..772177d427acd5c1692c2f5fb94c787363dd09f3 100644 (file)
                     0x5b: m5_work_end({{
                         PseudoInst::workend(xc->tcBase(), Rdi, Rsi);
                     }}, IsNonSpeculative);
+                    0x62: m5togglesync({{
+                        PseudoInst::togglesync(xc->tcBase());
+                    }}, IsNonSpeculative, IsQuiesce);
                     default: Inst::UD2();
                 }
             }
index 981a19223985d5b321954a59dea16986ba0bf458..d79aa138a9623fd83d39317a8f55a9ca76ee7f93 100644 (file)
@@ -73,6 +73,7 @@ class DistEtherLink(EtherObject):
     server_name = Param.String('localhost', "Message server name")
     server_port = Param.UInt32('2200', "Message server port")
     is_switch = Param.Bool(False, "true if this a link in etherswitch")
+    dist_sync_on_pseudo_op = Param.Bool(False, "Start sync with pseudo_op")
     num_nodes = Param.UInt32('2', "Number of simulate nodes")
 
 class EtherBus(EtherObject):
index a1cdc01b747a5aa903d9d8253ddd3d6d45b4e62c..01f21d1365b8a3bc30a20f535f29a1998b39c3c7 100644 (file)
@@ -94,7 +94,8 @@ DistEtherLink::DistEtherLink(const Params *p)
     // create the dist (TCP) interface to talk to the peer gem5 processes.
     distIface = new TCPIface(p->server_name, p->server_port,
                              p->dist_rank, p->dist_size,
-                             p->sync_start, sync_repeat, this, p->is_switch,
+                             p->sync_start, sync_repeat, this,
+                             p->dist_sync_on_pseudo_op, p->is_switch,
                              p->num_nodes);
 
     localIface = new LocalIface(name() + ".int0", txLink, rxLink, distIface);
index 26fe45317b0873796e38ce66ccd4a146ca053c14..79408c304da13ee807e7a089a270cf36778b6bae 100644 (file)
 
 #include "base/random.hh"
 #include "base/trace.hh"
+#include "cpu/thread_context.hh"
 #include "debug/DistEthernet.hh"
 #include "debug/DistEthernetPkt.hh"
 #include "dev/net/etherpkt.hh"
 #include "sim/sim_exit.hh"
 #include "sim/sim_object.hh"
+#include "sim/system.hh"
 
 using namespace std;
 DistIface::Sync *DistIface::sync = nullptr;
+System *DistIface::sys = nullptr;
 DistIface::SyncEvent *DistIface::syncEvent = nullptr;
 unsigned DistIface::distIfaceNum = 0;
 unsigned DistIface::recvThreadsNum = 0;
 DistIface *DistIface::master = nullptr;
+bool DistIface::isSwitch = false;
 
 void
 DistIface::Sync::init(Tick start_tick, Tick repeat_tick)
 {
-    if (start_tick < firstAt) {
-        firstAt = start_tick;
+    if (start_tick < nextAt) {
+        nextAt = start_tick;
         inform("Next dist synchronisation tick is changed to %lu.\n", nextAt);
     }
 
@@ -85,10 +89,11 @@ DistIface::SyncSwitch::SyncSwitch(int num_nodes)
     waitNum = num_nodes;
     numExitReq = 0;
     numCkptReq = 0;
+    numStopSyncReq = 0;
     doExit = false;
     doCkpt = false;
-    firstAt = std::numeric_limits<Tick>::max();
-    nextAt = 0;
+    doStopSync = false;
+    nextAt = std::numeric_limits<Tick>::max();
     nextRepeat = std::numeric_limits<Tick>::max();
 }
 
@@ -97,10 +102,11 @@ DistIface::SyncNode::SyncNode()
     waitNum = 0;
     needExit = ReqType::none;
     needCkpt = ReqType::none;
+    needStopSync = ReqType::none;
     doExit = false;
     doCkpt = false;
-    firstAt = std::numeric_limits<Tick>::max();
-    nextAt = 0;
+    doStopSync = false;
+    nextAt = std::numeric_limits<Tick>::max();
     nextRepeat = std::numeric_limits<Tick>::max();
 }
 
@@ -117,11 +123,14 @@ DistIface::SyncNode::run(bool same_tick)
     header.sendTick = curTick();
     header.syncRepeat = nextRepeat;
     header.needCkpt = needCkpt;
+    header.needStopSync = needStopSync;
     if (needCkpt != ReqType::none)
         needCkpt = ReqType::pending;
     header.needExit = needExit;
     if (needExit != ReqType::none)
         needExit = ReqType::pending;
+    if (needStopSync != ReqType::none)
+        needStopSync = ReqType::pending;
     DistIface::master->sendCmd(header);
     // now wait until all receiver threads complete the synchronisation
     auto lf = [this]{ return waitNum == 0; };
@@ -161,6 +170,13 @@ DistIface::SyncSwitch::run(bool same_tick)
     } else {
         header.needExit = ReqType::none;
     }
+    if (doStopSync || numStopSyncReq == numNodes) {
+        doStopSync = true;
+        numStopSyncReq = 0;
+        header.needStopSync = ReqType::immediate;
+    } else {
+        header.needStopSync = ReqType::none;
+    }
     DistIface::master->sendCmd(header);
 }
 
@@ -168,7 +184,8 @@ void
 DistIface::SyncSwitch::progress(Tick send_tick,
                                  Tick sync_repeat,
                                  ReqType need_ckpt,
-                                 ReqType need_exit)
+                                 ReqType need_exit,
+                                 ReqType need_stop_sync)
 {
     std::unique_lock<std::mutex> sync_lock(lock);
     assert(waitNum > 0);
@@ -186,6 +203,10 @@ DistIface::SyncSwitch::progress(Tick send_tick,
         numExitReq++;
     else if (need_exit == ReqType::immediate)
         doExit = true;
+    if (need_stop_sync == ReqType::collective)
+        numStopSyncReq++;
+    else if (need_stop_sync == ReqType::immediate)
+        doStopSync = true;
 
     waitNum--;
     // Notify the simulation thread if the on-going sync is complete
@@ -199,7 +220,8 @@ void
 DistIface::SyncNode::progress(Tick max_send_tick,
                                Tick next_repeat,
                                ReqType do_ckpt,
-                               ReqType do_exit)
+                               ReqType do_exit,
+                               ReqType do_stop_sync)
 {
     std::unique_lock<std::mutex> sync_lock(lock);
     assert(waitNum > 0);
@@ -208,6 +230,7 @@ DistIface::SyncNode::progress(Tick max_send_tick,
     nextRepeat = next_repeat;
     doCkpt = (do_ckpt != ReqType::none);
     doExit = (do_exit != ReqType::none);
+    doStopSync = (do_stop_sync != ReqType::none);
 
     waitNum--;
     // Notify the simulation thread if the on-going sync is complete
@@ -288,29 +311,27 @@ DistIface::SyncEvent::start()
     // At this point, all DistIface objects has already called Sync::init() so
     // we have a local minimum of the start tick and repeat for the periodic
     // sync.
-    Tick firstAt  = DistIface::sync->firstAt;
     repeat = DistIface::sync->nextRepeat;
     // Do a global barrier to agree on a common repeat value (the smallest
-    // one from all participating nodes
-    DistIface::sync->run(curTick() == 0);
+    // one from all participating nodes.
+    DistIface::sync->run(false);
 
     assert(!DistIface::sync->doCkpt);
     assert(!DistIface::sync->doExit);
+    assert(!DistIface::sync->doStopSync);
     assert(DistIface::sync->nextAt >= curTick());
     assert(DistIface::sync->nextRepeat <= repeat);
 
-    // if this is called at tick 0 then we use the config start param otherwise
-    // the maximum of the current tick of all participating nodes
-    if (curTick() == 0) {
+    if (curTick() == 0)
         assert(!scheduled());
-        assert(DistIface::sync->nextAt == 0);
-        schedule(firstAt);
-    } else {
-        if (scheduled())
-            reschedule(DistIface::sync->nextAt);
-        else
-            schedule(DistIface::sync->nextAt);
-    }
+
+    // Use the maximum of the current tick for all participating nodes or a
+    // user provided starting tick.
+    if (scheduled())
+        reschedule(DistIface::sync->nextAt);
+    else
+        schedule(DistIface::sync->nextAt);
+
     inform("Dist sync scheduled at %lu and repeats %lu\n",  when(),
            DistIface::sync->nextRepeat);
 }
@@ -352,7 +373,27 @@ DistIface::SyncEvent::process()
         exitSimLoop("checkpoint");
     if (DistIface::sync->doExit)
         exitSimLoop("exit request from gem5 peers");
+    if (DistIface::sync->doStopSync) {
+        DistIface::sync->doStopSync = false;
+        inform("synchronization disabled at %lu\n", curTick());
 
+        // The switch node needs to wait for the next sync immediately.
+        if (DistIface::isSwitch) {
+            start();
+        } else {
+            // Wake up thread contexts on non-switch nodes.
+            for (int i = 0; i < DistIface::master->sys->numContexts(); i++) {
+                ThreadContext *tc =
+                    DistIface::master->sys->getThreadContext(i);
+                if (tc->status() == ThreadContext::Suspended)
+                    tc->activate();
+                else
+                    warn_once("Tried to wake up thread in dist-gem5, but it "
+                              "was already awake!\n");
+            }
+        }
+        return;
+    }
     // schedule the next periodic sync
     repeat = DistIface::sync->nextRepeat;
     schedule(curTick() + repeat);
@@ -537,9 +578,10 @@ DistIface::DistIface(unsigned dist_rank,
                      Tick sync_start,
                      Tick sync_repeat,
                      EventManager *em,
+                     bool use_pseudo_op,
                      bool is_switch, int num_nodes) :
     syncStart(sync_start), syncRepeat(sync_repeat),
-    recvThread(nullptr), recvScheduler(em),
+    recvThread(nullptr), recvScheduler(em), syncStartOnPseudoOp(use_pseudo_op),
     rank(dist_rank), size(dist_size)
 {
     DPRINTF(DistEthernet, "DistIface() ctor rank:%d\n",dist_rank);
@@ -547,6 +589,7 @@ DistIface::DistIface(unsigned dist_rank,
     if (master == nullptr) {
         assert(sync == nullptr);
         assert(syncEvent == nullptr);
+        isSwitch = is_switch;
         if (is_switch)
             sync = new SyncSwitch(num_nodes);
         else
@@ -628,7 +671,8 @@ DistIface::recvThreadFunc(Event *recv_done, Tick link_delay)
             sync->progress(header.sendTick,
                            header.syncRepeat,
                            header.needCkpt,
-                           header.needExit);
+                           header.needExit,
+                           header.needStopSync);
         }
     }
 }
@@ -732,7 +776,8 @@ void
 DistIface::startup()
 {
     DPRINTF(DistEthernet, "DistIface::startup() started\n");
-    if (this == master)
+    // Schedule synchronization unless we are not a switch in pseudo_op mode.
+    if (this == master && (!syncStartOnPseudoOp || isSwitch))
         syncEvent->start();
     DPRINTF(DistEthernet, "DistIface::startup() done\n");
 }
@@ -761,6 +806,52 @@ DistIface::readyToCkpt(Tick delay, Tick period)
     return ret;
 }
 
+void
+DistIface::SyncNode::requestStopSync(ReqType req)
+{
+   std::lock_guard<std::mutex> sync_lock(lock);
+   needStopSync = req;
+}
+
+void
+DistIface::toggleSync(ThreadContext *tc)
+{
+    // Unforunate that we have to populate the system pointer member this way.
+    master->sys = tc->getSystemPtr();
+
+    // The invariant for both syncing and "unsyncing" is that all threads will
+    // stop executing intructions until the desired sync state has been reached
+    // for all nodes.  This is the easiest way to prevent deadlock (in the case
+    // of "unsyncing") and causality errors (in the case of syncing).
+    if (master->syncEvent->scheduled()) {
+        inform("Request toggling syncronization off\n");
+        master->sync->requestStopSync(ReqType::collective);
+
+        // At this point, we have no clue when everyone will reach the sync
+        // stop point.  Suspend execution of all local thread contexts.
+        // Dist-gem5 will reactivate all thread contexts when everyone has
+        // reached the sync stop point.
+        for (int i = 0; i < master->sys->numContexts(); i++) {
+            ThreadContext *tc = master->sys->getThreadContext(i);
+            if (tc->status() == ThreadContext::Active)
+                tc->quiesce();
+        }
+    } else {
+        inform("Request toggling syncronization on\n");
+        master->syncEvent->start();
+
+        // We need to suspend all CPUs until the sync point is reached by all
+        // nodes to prevent causality errors.  We can also schedule CPU
+        // activation here, since we know exactly when the next sync will
+        // occur.
+        for (int i = 0; i < master->sys->numContexts(); i++) {
+            ThreadContext *tc = master->sys->getThreadContext(i);
+            if (tc->status() == ThreadContext::Active)
+                tc->quiesceTick(master->syncEvent->when() + 1);
+        }
+    }
+}
+
 bool
 DistIface::readyToExit(Tick delay)
 {
@@ -768,6 +859,10 @@ DistIface::readyToExit(Tick delay)
     DPRINTF(DistEthernet, "DistIface::readyToExit() called, delay:%lu\n",
             delay);
     if (master) {
+        // To successfully coordinate an exit, all nodes must be synchronising
+        if (!master->syncEvent->scheduled())
+            master->syncEvent->start();
+
         if (delay == 0) {
             inform("m5 exit called with zero delay => triggering collaborative "
                    "exit\n");
index e69211fb8f7ed570ea64cb2b2aeeecb415fdd39c..20ac0989b54f78470e4a6c2e5ab045c870ae1ae4 100644 (file)
@@ -91,6 +91,8 @@
 #include "sim/serialize.hh"
 
 class EventManager;
+class System;
+class ThreadContext;
 
 /**
  * The interface class to talk to peer gem5 processes.
@@ -139,13 +141,13 @@ class DistIface : public Drainable, public Serializable
          */
         bool doCkpt;
         /**
-         * The repeat value for the next periodic sync
+         * Flag is set if sync is to stop upon sync completion
          */
-        Tick nextRepeat;
+        bool doStopSync;
         /**
-         * Tick for the very first periodic sync
+         * The repeat value for the next periodic sync
          */
-        Tick firstAt;
+        Tick nextRepeat;
         /**
          * Tick for the next periodic sync (if the event is not scheduled yet)
          */
@@ -172,10 +174,12 @@ class DistIface : public Drainable, public Serializable
         virtual void progress(Tick send_tick,
                               Tick next_repeat,
                               ReqType do_ckpt,
-                              ReqType do_exit) = 0;
+                              ReqType do_exit,
+                              ReqType do_stop_sync) = 0;
 
         virtual void requestCkpt(ReqType req) = 0;
         virtual void requestExit(ReqType req) = 0;
+        virtual void requestStopSync(ReqType req) = 0;
 
         void drainComplete();
 
@@ -194,6 +198,10 @@ class DistIface : public Drainable, public Serializable
          * Ckpt requested
          */
         ReqType needCkpt;
+        /**
+         * Sync stop requested
+         */
+        ReqType needStopSync;
 
       public:
 
@@ -203,10 +211,12 @@ class DistIface : public Drainable, public Serializable
         void progress(Tick max_req_tick,
                       Tick next_repeat,
                       ReqType do_ckpt,
-                      ReqType do_exit) override;
+                      ReqType do_exit,
+                      ReqType do_stop_sync) override;
 
         void requestCkpt(ReqType req) override;
         void requestExit(ReqType req) override;
+        void requestStopSync(ReqType req) override;
 
         void serialize(CheckpointOut &cp) const override;
         void unserialize(CheckpointIn &cp) override;
@@ -223,6 +233,10 @@ class DistIface : public Drainable, public Serializable
          * Counter for recording ckpt requests
          */
         unsigned numCkptReq;
+        /**
+         * Counter for recording stop sync requests
+         */
+        unsigned numStopSyncReq;
         /**
          *  Number of connected simulated nodes
          */
@@ -236,7 +250,8 @@ class DistIface : public Drainable, public Serializable
         void progress(Tick max_req_tick,
                       Tick next_repeat,
                       ReqType do_ckpt,
-                      ReqType do_exit) override;
+                      ReqType do_exit,
+                      ReqType do_stop_sync) override;
 
         void requestCkpt(ReqType) override {
             panic("Switch requested checkpoint");
@@ -244,6 +259,9 @@ class DistIface : public Drainable, public Serializable
         void requestExit(ReqType) override {
             panic("Switch requested exit");
         }
+        void requestStopSync(ReqType) override {
+            panic("Switch requested stop sync");
+        }
 
         void serialize(CheckpointOut &cp) const override;
         void unserialize(CheckpointIn &cp) override;
@@ -437,6 +455,10 @@ class DistIface : public Drainable, public Serializable
      * Meta information about data packets received.
      */
     RecvScheduler recvScheduler;
+    /**
+     * Use pseudoOp to start synchronization.
+     */
+    bool syncStartOnPseudoOp;
 
   protected:
     /**
@@ -476,6 +498,14 @@ class DistIface : public Drainable, public Serializable
      * a master to co-ordinate the global synchronisation.
      */
     static DistIface *master;
+    /**
+     * System pointer used to wakeup sleeping threads when stopping sync.
+     */
+    static System *sys;
+    /**
+     * Is this node a switch?
+     */
+     static bool isSwitch;
 
   private:
     /**
@@ -533,6 +563,7 @@ class DistIface : public Drainable, public Serializable
               Tick sync_start,
               Tick sync_repeat,
               EventManager *em,
+              bool use_pseudo_op,
               bool is_switch,
               int num_nodes);
 
@@ -590,6 +621,10 @@ class DistIface : public Drainable, public Serializable
      * Getter for the dist size param.
      */
     static uint64_t sizeParam();
+    /**
+     * Trigger the master to start/stop synchronization.
+     */
+    static void toggleSync(ThreadContext *tc);
  };
 
 #endif
index b154ab4a77bbe70fbe3d54f135d3759fce3736ae..6e30101d42eed2b586a0690f0dec6348fe3a5426 100644 (file)
@@ -103,6 +103,7 @@ class DistHeaderPkt
             unsigned dataPacketLength;
             struct {
                 ReqType needCkpt;
+                ReqType needStopSync;
                 ReqType needExit;
             };
         };
index fba0696746d7e27b5b537e52ef66b33c1feb5f54..f9c927acb8ec61fd1d6531acc681d85d97e28540 100644 (file)
@@ -82,8 +82,9 @@ bool TCPIface::anyListening = false;
 TCPIface::TCPIface(string server_name, unsigned server_port,
                    unsigned dist_rank, unsigned dist_size,
                    Tick sync_start, Tick sync_repeat,
-                   EventManager *em, bool is_switch, int num_nodes) :
-    DistIface(dist_rank, dist_size, sync_start, sync_repeat, em,
+                   EventManager *em, bool use_pseudo_op, bool is_switch,
+                   int num_nodes) :
+    DistIface(dist_rank, dist_size, sync_start, sync_repeat, em, use_pseudo_op,
               is_switch, num_nodes), serverName(server_name),
     serverPort(server_port), isSwitch(is_switch), listening(false)
 {
index 97ae9cde0b8242782815326e006069505d77926b..8ba5c7e81431c6b7697e39603767276b70e54602 100644 (file)
@@ -148,7 +148,7 @@ class TCPIface : public DistIface
     TCPIface(std::string server_name, unsigned server_port,
              unsigned dist_rank, unsigned dist_size,
              Tick sync_start, Tick sync_repeat, EventManager *em,
-             bool is_switch, int num_nodes);
+             bool use_pseudo_op, bool is_switch, int num_nodes);
 
     ~TCPIface() override;
 };
index 4f9bbff6ea8b2229915f36c14401948ee2530bc4..eed332747ddde513a0f684f3641ec4a642ac20a0 100644 (file)
@@ -211,6 +211,11 @@ pseudoInst(ThreadContext *tc, uint8_t func, uint8_t subfunc)
         m5PageFault(tc);
         break;
 
+      /* dist-gem5 functions */
+      case 0x62: // distToggleSync_func
+        togglesync(tc);
+        break;
+
       default:
         warn("Unhandled m5 op: 0x%x\n", func);
         break;
@@ -574,6 +579,13 @@ switchcpu(ThreadContext *tc)
     exitSimLoop("switchcpu");
 }
 
+void
+togglesync(ThreadContext *tc)
+{
+    DPRINTF(PseudoInst, "PseudoInst::togglesync()\n");
+    DistIface::toggleSync(tc);
+}
+
 //
 // This function is executed when annotated work items begin.  Depending on
 // what the user specified at the command line, the simulation may exit and/or
index 5f0b700c6a97410e205d05c97aff02f9098d10a7..d9b981f947a2e601bb0f31338a58727eb7fbd267 100644 (file)
@@ -88,6 +88,7 @@ void debugbreak(ThreadContext *tc);
 void switchcpu(ThreadContext *tc);
 void workbegin(ThreadContext *tc, uint64_t workid, uint64_t threadid);
 void workend(ThreadContext *tc, uint64_t workid, uint64_t threadid);
+void togglesync(ThreadContext *tc);
 
 } // namespace PseudoInst
 
index 34dabce019aa4606a155e72cd8aa9ed1519f9df8..d9fcb3231a3df691a56d3658794ab65e4a73f4c1 100644 (file)
@@ -57,6 +57,7 @@ uint64_t m5_readfile(void *buffer, uint64_t len, uint64_t offset);
 uint64_t m5_writefile(void *buffer, uint64_t len, uint64_t offset, const char *filename);
 void m5_debugbreak(void);
 void m5_switchcpu(void);
+void m5_togglesync(void);
 void m5_addsymbol(uint64_t addr, char *symbol);
 void m5_panic(void);
 void m5_work_begin(uint64_t workid, uint64_t threadid);
index 2e950c10fdcd1d2c9a62b95ca236018bdfb8ae65..f38803a253d7cda78cf25d9d9b4ad3d5cd17c41f 100644 (file)
@@ -83,3 +83,4 @@ TWO_BYTE_OP(m5_addsymbol, addsymbol_func)
 TWO_BYTE_OP(m5_panic, panic_func)
 TWO_BYTE_OP(m5_work_begin, work_begin_func)
 TWO_BYTE_OP(m5_work_end, work_end_func)
+TWO_BYTE_OP(m5_togglesync, togglesync_func)
index 51dcb5d07847a9b90fd184b7e35f84805f104518..99dc81251b508148950d1b00f3adc4535e460501 100644 (file)
@@ -77,6 +77,7 @@
 
 #define syscall_func            0x60 // Reserved for user
 #define pagefault_func          0x61 // Reserved for user
+#define togglesync_func         0x62
 
 // These operations are for critical path annotation
 #define annotate_func     0x55
     M5OP(m5_addsymbol, addsymbol_func, 0);              \
     M5OP(m5_panic, panic_func, 0);                      \
     M5OP(m5_work_begin, work_begin_func, 0);            \
-    M5OP(m5_work_end, work_end_func, 0);
+    M5OP(m5_work_end, work_end_func, 0);                \
+    M5OP(m5_togglesync, togglesync_func, 0);
 
 #define FOREACH_M5_ANNOTATION                   \
     M5_ANNOTATION(m5a_bsm, an_bsm);             \