Proxy exitJS synchronously, just like the low level proc_exit

This simplifies the code and reduces code size.

Add testing of both `exit` and `_exit` in test_pthread_exit_runtime.
diff --git a/emcc.py b/emcc.py
index ae1db63..fd47555 100755
--- a/emcc.py
+++ b/emcc.py
@@ -1677,9 +1677,6 @@
       '__dl_seterr',
     ]
 
-  settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
-    '$exitOnMainThread',
-  ]
   # Some symbols are required by worker.js.
   # Because emitDCEGraph only considers the main js file, and not worker.js
   # we have explicitly mark these symbols as user-exported so that they will
diff --git a/src/library.js b/src/library.js
index cb8af40..249a0ec 100644
--- a/src/library.js
+++ b/src/library.js
@@ -61,6 +61,11 @@
   },
 
 #if !MINIMAL_RUNTIME
+  // Handles exiting the entire program.  This function only ever runs on the
+  // main thread and will be proxied synchronously when called from a worker.
+  // In either case this function never returns.
+  // Programs that call the lower level `_exit` end will bypass this code and
+  $exitJS__proxy: 'sync',
   $exitJS__docs: '/** @param {boolean|number=} implicit */',
   $exitJS__deps: ['proc_exit'],
   $exitJS: function(status, implicit) {
@@ -70,26 +75,12 @@
     checkUnflushedContent();
 #endif // ASSERTIONS && !EXIT_RUNTIME
 
-#if USE_PTHREADS
-    if (ENVIRONMENT_IS_PTHREAD) {
-      // implict exit can never happen on a pthread
-#if ASSERTIONS
-      assert(!implicit);
+#if PROXY_TO_PTHREAD
+    {{{ runtimeKeepalivePop() }}};
 #endif
-#if PTHREADS_DEBUG
-      dbg('Pthread ' + ptrToString(_pthread_self()) + ' called exit(), posting exitOnMainThread.');
+#if USE_PTHREADS && PTHREADS_DEBUG
+    dbg('exit called: keepRuntimeAlive=' + keepRuntimeAlive() + ' (counter=' + runtimeKeepaliveCounter + ')');
 #endif
-      // When running in a pthread we propagate the exit back to the main thread
-      // where it can decide if the whole process should be shut down or not.
-      // The pthread may have decided not to exit its own runtime, for example
-      // because it runs a main loop, but that doesn't affect the main thread.
-      exitOnMainThread(status);
-      throw 'unwind';
-    }
-#if PTHREADS_DEBUG
-    err('main thread called exit: keepRuntimeAlive=' + keepRuntimeAlive() + ' (counter=' + runtimeKeepaliveCounter + ')');
-#endif // PTHREADS_DEBUG
-#endif // USE_PTHREADS
 
 #if EXIT_RUNTIME
     if (!keepRuntimeAlive()) {
diff --git a/src/library_pthread.js b/src/library_pthread.js
index 3255dc0..511fce6 100644
--- a/src/library_pthread.js
+++ b/src/library_pthread.js
@@ -928,28 +928,8 @@
     return 0;
   },
 
-  // This function is call by a pthread to signal that exit() was called and
-  // that the entire process should exit.
-  // This function is always called from a pthread, but is executed on the
-  // main thread due the __proxy attribute.
-  $exitOnMainThread__deps: ['exit',
-#if !MINIMAL_RUNTIME
-    '$handleException',
-#endif
-  ],
-  $exitOnMainThread__proxy: 'async',
-  $exitOnMainThread: function(returnCode) {
-#if PTHREADS_DEBUG
-    dbg('exitOnMainThread');
-#endif
-#if PROXY_TO_PTHREAD
-    {{{ runtimeKeepalivePop() }}};
-#endif
-    _exit(returnCode);
-  },
-
   emscripten_proxy_to_main_thread_js__deps: ['$withStackSave', '_emscripten_run_in_main_runtime_thread_js'],
-  emscripten_proxy_to_main_thread_js__docs: '/** @type{function(number, (number|boolean), ...(number|boolean))} */',
+  emscripten_proxy_to_main_thread_js__docs: '/** @type{function(number, (number|boolean), ...(number|boolean|undefined))} */',
   emscripten_proxy_to_main_thread_js: function(index, sync) {
     // Additional arguments are passed after those two, which are the actual
     // function arguments.
diff --git a/src/library_wasi.js b/src/library_wasi.js
index 97e3cbc..344f28d 100644
--- a/src/library_wasi.js
+++ b/src/library_wasi.js
@@ -15,6 +15,10 @@
   proc_exit__deps: ['$ExitStatus'],
 #endif
 
+  // Handles exiting the entire program.  This function only ever runs on the
+  // main thread and will be proxied synchronously when called from a worker.
+  // (wrapSyscallFunction handles the proxying).  In either case this function
+  // never returns.
   proc_exit__nothrow: true,
   proc_exit__sig: 'vi',
   proc_exit: function(code) {
diff --git a/test/core/pthread/test_pthread_exit_runtime.c b/test/core/pthread/test_pthread_exit_runtime.c
index b90e2b4..91d4d4b 100644
--- a/test/core/pthread/test_pthread_exit_runtime.c
+++ b/test/core/pthread/test_pthread_exit_runtime.c
@@ -4,21 +4,34 @@
 #include <pthread.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <unistd.h>
 #include <emscripten/emscripten.h>
 
 pthread_t t;
 
-void* thread_main_exit(void* arg) {
-  printf("calling exit\n");
-  exit(42);
-}
-
 // This location should never get set to true.
 // We verify that it false from JS after the program exits.
-atomic_bool join_returned = false;
+// If the main thread ever returns from `join` or the worker thread returns
+// from `exit` this gets set to true, which would be bug.
+atomic_bool fail = false;
 
-EMSCRIPTEN_KEEPALIVE atomic_bool* join_returned_address() {
-  return &join_returned;
+EMSCRIPTEN_KEEPALIVE atomic_bool* fail_address() {
+  return &fail;
+}
+
+void* thread_main_exit(void* arg) {
+  // This test run with both _EXIT defined and without to test low level
+  // and high level exit.
+#ifdef _EXIT
+  printf("calling _exit\n");
+  _exit(43);
+#else
+  printf("calling exit\n");
+  exit(42);
+#endif
+  fail = true;
+  printf("after exit -- should never get here\n");
+  __builtin_trap();
 }
 
 int main() {
@@ -30,7 +43,7 @@
   assert(rc == 0);
   // pthread_join should never return because the runtime should
   // exit first.
-  join_returned = true;
+  fail = true;
   printf("done join %d -- should never get here\n", rc);
   __builtin_trap();
 }
diff --git a/test/core/pthread/test_pthread_exit_runtime.pre.js b/test/core/pthread/test_pthread_exit_runtime.pre.js
index 646b3a5..bce2806 100644
--- a/test/core/pthread/test_pthread_exit_runtime.pre.js
+++ b/test/core/pthread/test_pthread_exit_runtime.pre.js
@@ -1,7 +1,7 @@
 var address = 0;
 
 Module.onRuntimeInitialized = function() {
-  address = Module['_join_returned_address']();
+  address = Module['_fail_address']();
   assert(address);
   assert(HEAP8[address] == 0);
 }
@@ -10,7 +10,7 @@
   out('onExit status: ' + status);
   // Verify that the join never returned
   assert(address);
-  assert(HEAP8[address] == 0, 'pthread_join should not have returned!');
+  assert(HEAP8[address] == 0, 'fail should never get set!');
   if (typeof reportResultToServer !== 'undefined') {
     reportResultToServer('onExit status: ' + status);
   }
diff --git a/test/core/pthread/test_pthread_exit_runtime_immediate.out b/test/core/pthread/test_pthread_exit_runtime_immediate.out
new file mode 100644
index 0000000..12aa1be
--- /dev/null
+++ b/test/core/pthread/test_pthread_exit_runtime_immediate.out
@@ -0,0 +1 @@
+onExit status: 43
diff --git a/test/other/metadce/test_metadce_minimal_pthreads.jssize b/test/other/metadce/test_metadce_minimal_pthreads.jssize
index 2a37d47..5c94783 100644
--- a/test/other/metadce/test_metadce_minimal_pthreads.jssize
+++ b/test/other/metadce/test_metadce_minimal_pthreads.jssize
@@ -1 +1 @@
-15855
+15687
diff --git a/test/test_core.py b/test/test_core.py
index 3e161ad..cfd5237 100644
--- a/test/test_core.py
+++ b/test/test_core.py
@@ -9295,8 +9295,12 @@
   def test_pthread_exit_process(self):
     self.set_setting('PROXY_TO_PTHREAD')
     self.set_setting('EXIT_RUNTIME')
-    self.emcc_args += ['-DEXIT_RUNTIME', '--pre-js', test_file('core/pthread/test_pthread_exit_runtime.pre.js')]
+    self.emcc_args += ['--pre-js', test_file('core/pthread/test_pthread_exit_runtime.pre.js')]
     self.do_run_in_out_file_test('core/pthread/test_pthread_exit_runtime.c', assert_returncode=42)
+    # Run the same test again but with `_exit` rather than `exit`
+    self.emcc_args += ['-D_EXIT']
+    self.do_run_in_out_file_test('core/pthread/test_pthread_exit_runtime.c', assert_returncode=43,
+                                 out_suffix='_immediate')
 
   @node_pthreads
   def test_pthread_keepalive(self):