sim,cpu: make exit_group halt all threads in a group

When a thread calls exit_group, in addition to halting the thread
itself, it needs to halt all other threads in its group (i.e., threads
sharing the same thread group ID). This patch enables threads to do
that.

Change-Id: Ib2e158fb27cf98843f177a64a2d643b1bbc94d03
Reviewed-on: https://gem5-review.googlesource.com/c/9623
Reviewed-by: Jason Lowe-Power <jason@lowepower.com>
Maintainer: Jason Lowe-Power <jason@lowepower.com>
diff --git a/src/cpu/o3/cpu.cc b/src/cpu/o3/cpu.cc
index e50741e..965ab04 100644
--- a/src/cpu/o3/cpu.cc
+++ b/src/cpu/o3/cpu.cc
@@ -1861,9 +1861,8 @@
 {
     DPRINTF(O3CPU, "Thread %d is inserted to exitingThreads list\n", tid);
 
-    // make sure the thread is Active
-    assert(std::find(activeThreads.begin(), activeThreads.end(), tid)
-                                              != activeThreads.end());
+    // the thread trying to exit can't be already halted
+    assert(tcBase(tid)->status() != ThreadContext::Halted);
 
     // make sure the thread has not been added to the list yet
     assert(exitingThreads.count(tid) == 0);
diff --git a/src/sim/syscall_emul.cc b/src/sim/syscall_emul.cc
index 25e0b68..fbfe21a 100644
--- a/src/sim/syscall_emul.cc
+++ b/src/sim/syscall_emul.cc
@@ -102,28 +102,6 @@
 
     System *sys = tc->getSystemPtr();
 
-    int activeContexts = 0;
-    for (auto &system: sys->systemList)
-        activeContexts += system->numRunningContexts();
-    if (activeContexts == 1) {
-        /**
-         * Even though we are terminating the final thread context, dist-gem5
-         * requires the simulation to remain active and provide
-         * synchronization messages to the switch process. So we just halt
-         * the last thread context and return. The simulation will be
-         * terminated by dist-gem5 in a coordinated manner once all nodes
-         * have signaled their readiness to exit. For non dist-gem5
-         * simulations, readyToExit() always returns true.
-         */
-        if (!DistIface::readyToExit(0)) {
-            tc->halt();
-            return status;
-        }
-
-        exitSimLoop("exiting with last active thread context", status & 0xff);
-        return status;
-    }
-
     if (group)
         *p->exitGroup = true;
 
@@ -146,16 +124,34 @@
         if (walk->pid() == p->tgid())
             tg_lead = walk;
 
-        if ((sys->threadContexts[i]->status() != ThreadContext::Halted)
-            && (walk != p)) {
+        if ((sys->threadContexts[i]->status() != ThreadContext::Halted) &&
+            (sys->threadContexts[i]->status() != ThreadContext::Halting) &&
+            (walk != p)) {
             /**
              * Check if we share thread group with the pointer; this denotes
              * that we are not the last thread active in the thread group.
              * Note that setting this to false also prevents further
              * iterations of the loop.
              */
-            if (walk->tgid() == p->tgid())
-                last_thread = false;
+            if (walk->tgid() == p->tgid()) {
+                /**
+                 * If p is trying to exit_group and both walk and p are in
+                 * the same thread group (i.e., sharing the same tgid),
+                 * we need to halt walk's thread context. After all threads
+                 * except p are halted, p becomes the last thread in the
+                 * group.
+                 *
+                 * If p is not doing exit_group and there exists another
+                 * active thread context in the group, last_thread is
+                 * set to false to prevent the parent thread from killing
+                 * all threads in the group.
+                 */
+                if (*(p->exitGroup)) {
+                    sys->threadContexts[i]->halt();
+                } else {
+                    last_thread = false;
+                }
+            }
 
             /**
              * A corner case exists which involves execve(). After execve(),
@@ -189,6 +185,33 @@
     }
 
     tc->halt();
+
+    /**
+     * check to see if there is no more active thread in the system. If so,
+     * exit the simulation loop
+     */
+    int activeContexts = 0;
+    for (auto &system: sys->systemList)
+        activeContexts += system->numRunningContexts();
+
+    if (activeContexts == 0) {
+        /**
+         * Even though we are terminating the final thread context, dist-gem5
+         * requires the simulation to remain active and provide
+         * synchronization messages to the switch process. So we just halt
+         * the last thread context and return. The simulation will be
+         * terminated by dist-gem5 in a coordinated manner once all nodes
+         * have signaled their readiness to exit. For non dist-gem5
+         * simulations, readyToExit() always returns true.
+         */
+        if (!DistIface::readyToExit(0)) {
+            return status;
+        }
+
+        exitSimLoop("exiting with last active thread context", status & 0xff);
+        return status;
+    }
+
     return status;
 }
 
diff --git a/src/sim/system.cc b/src/sim/system.cc
index fc2578f..ffa8eda 100644
--- a/src/sim/system.cc
+++ b/src/sim/system.cc
@@ -291,7 +291,8 @@
         threadContexts.cbegin(),
         threadContexts.cend(),
         [] (ThreadContext* tc) {
-            return tc->status() != ThreadContext::Halted;
+            return ((tc->status() != ThreadContext::Halted) &&
+                    (tc->status() != ThreadContext::Halting));
         }
     );
 }