Use HandleAllocator for wasm workers. NFC

Similar to what we have been doing to other APIs.  For example, see
\#19337 and #19054
diff --git a/src/library.js b/src/library.js
index 75fc483..183d6ed 100644
--- a/src/library.js
+++ b/src/library.js
@@ -3661,21 +3661,21 @@
     // Reserve slot 0 so that 0 is always an invalid handle
     this.allocated = [undefined];
     this.freelist = [];
-    this.get = function(id) {
+    // Hack to save on codesize: use arrow functions here even though it
+    // means that `this` gets statically bound to the constrcuted object.
+    this.get = (id) => {
 #if ASSERTIONS
       assert(this.allocated[id] !== undefined, 'invalid handle: ' + id);
 #endif
       return this.allocated[id];
     };
-    this.has = function(id) {
-      return this.allocated[id] !== undefined;
-    };
-    this.allocate = function(handle) {
+    this.has = (id) => this.allocated[id] !== undefined;
+    this.allocate = (handle) => {
       var id = this.freelist.pop() || this.allocated.length;
       this.allocated[id] = handle;
       return id;
     };
-    this.free = function(id) {
+    this.free = (id) => {
 #if ASSERTIONS
       assert(this.allocated[id] !== undefined);
 #endif
diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js
index 6b0a47b..9cd3257 100644
--- a/src/library_wasm_worker.js
+++ b/src/library_wasm_worker.js
@@ -30,8 +30,8 @@
 
 
 mergeInto(LibraryManager.library, {
-  $_wasmWorkers: {},
-  $_wasmWorkersID: 1,
+  $_wasmWorkers__deps: ['$HandleAllocator'],
+  $_wasmWorkers: "new HandleAllocator();",
 
   // Starting up a Wasm Worker is an asynchronous operation, hence if the parent thread performs any
   // postMessage()-based wasm function calls s to the Worker, they must be delayed until the async
@@ -105,17 +105,19 @@
   $_wasmWorkerBlobUrl: "URL.createObjectURL(new Blob(['onmessage=function(d){onmessage=null;d=d.data;{{{ captureModuleArg() }}}{{{ instantiateWasm() }}}importScripts(d.js);{{{ instantiateModule() }}}d.wasm=d.mem=d.js=0;}'],{type:'application/javascript'}))",
 #endif
 
-  _emscripten_create_wasm_worker__deps: ['$_wasmWorkers', '$_wasmWorkersID', '$_wasmWorkerAppendToQueue', '$_wasmWorkerRunPostMessage'
+  _emscripten_create_wasm_worker__deps: ['$_wasmWorkers', '$_wasmWorkerAppendToQueue',
+    '$_wasmWorkerRunPostMessage',
 #if WASM_WORKERS == 2
-    , '$_wasmWorkerBlobUrl'
+    '$_wasmWorkerBlobUrl',
 #endif
   ],
-  _emscripten_create_wasm_worker__postset: 'if (ENVIRONMENT_IS_WASM_WORKER) {\n'
-    + '_wasmWorkers[0] = this;\n'
-    + 'addEventListener("message", _wasmWorkerAppendToQueue);\n'
-    + '}\n',
+  _emscripten_create_wasm_worker__postset: `
+  if (ENVIRONMENT_IS_WASM_WORKER) {
+    _wasmWorkers.allocated[0] = this;
+    addEventListener('message', _wasmWorkerAppendToQueue);
+  }`,
   _emscripten_create_wasm_worker: function(stackLowestAddress, stackSize) {
-    let worker = _wasmWorkers[_wasmWorkersID] = new Worker(
+    let worker = new Worker(
 #if WASM_WORKERS == 2 // WASM_WORKERS=2 mode embeds .ww.js file contents into the main .js file as a Blob URL. (convenient, but not CSP security safe, since this is eval-like)
       _wasmWorkerBlobUrl
 #elif MINIMAL_RUNTIME // MINIMAL_RUNTIME has a structure where the .ww.js file is loaded from the main HTML file in parallel to all other files for best performance
@@ -124,9 +126,13 @@
       locateFile('{{{ WASM_WORKER_FILE }}}')
 #endif
     );
+    var id = _wasmWorkers.allocate(worker);
+#if RUNTIME_DEBUG
+    dbg('new wasm worker -> ' + id);
+#endif
     // Craft the Module object for the Wasm Worker scope:
     worker.postMessage({
-      '$ww': _wasmWorkersID, // Signal with a non-zero value that this Worker will be a Wasm Worker, and not the main browser thread.
+      '$ww': id, // Signal with a non-zero value that this Worker will be a Wasm Worker, and not the main browser thread.
 #if MINIMAL_RUNTIME
       'wasm': Module['wasm'],
       'js': Module['js'],
@@ -140,16 +146,19 @@
       'sz': stackSize,          // sz = stack size
     });
     worker.onmessage = _wasmWorkerRunPostMessage;
-    return _wasmWorkersID++;
+    return id;
   },
 
   emscripten_terminate_wasm_worker: function(id) {
 #if ASSERTIONS
     assert(id != 0, 'emscripten_terminate_wasm_worker() cannot be called with id=0!');
 #endif
-    if (_wasmWorkers[id]) {
-      _wasmWorkers[id].terminate();
-      delete _wasmWorkers[id];
+    if (_wasmWorkers.has(id)) {
+#if RUNTIME_DEBUG
+      dbg('terminating wasm worker -> ' + id);
+#endif
+      _wasmWorkers.get(id).terminate();
+      _wasmWorkers.free(id);
     }
   },
 
@@ -157,10 +166,15 @@
 #if ASSERTIONS
     assert(!ENVIRONMENT_IS_WASM_WORKER, 'emscripten_terminate_all_wasm_workers() cannot be called from a Wasm Worker: only the main browser thread has visibility to terminate all Workers!');
 #endif
-    Object.values(_wasmWorkers).forEach((worker) => {
-      worker.terminate();
+#if RUNTIME_DEBUG
+    dbg('emscripten_terminate_all_wasm_workers');
+#endif
+    Object.values(_wasmWorkers.allocated).forEach((worker) => {
+      if (typeof worker !== 'undefined') {
+        worker.terminate();
+      }
     });
-    _wasmWorkers = {};
+    _wasmWorkers = new HandleAllocator();
   },
 
   emscripten_current_thread_is_wasm_worker: function() {
@@ -176,12 +190,12 @@
   },
 
   emscripten_wasm_worker_post_function_v: function(id, funcPtr) {
-    _wasmWorkers[id].postMessage({'_wsc': funcPtr, 'x': [] }); // "WaSm Call"
+    _wasmWorkers.get(id).postMessage({'_wsc': funcPtr, 'x': [] }); // "WaSm Call"
   },
 
   $_wasmWorkerPostFunction1__sig: 'vipd',
   $_wasmWorkerPostFunction1: function(id, funcPtr, arg0) {
-    _wasmWorkers[id].postMessage({'_wsc': funcPtr, 'x': [arg0] }); // "WaSm Call"
+    _wasmWorkers.get(id).postMessage({'_wsc': funcPtr, 'x': [arg0] }); // "WaSm Call"
   },
 
   emscripten_wasm_worker_post_function_vi: '$_wasmWorkerPostFunction1',
@@ -189,14 +203,14 @@
 
   $_wasmWorkerPostFunction2__sig: 'vipdd',
   $_wasmWorkerPostFunction2: function(id, funcPtr, arg0, arg1) {
-    _wasmWorkers[id].postMessage({'_wsc': funcPtr, 'x': [arg0, arg1] }); // "WaSm Call"
+    _wasmWorkers.get(id).postMessage({'_wsc': funcPtr, 'x': [arg0, arg1] }); // "WaSm Call"
   },
   emscripten_wasm_worker_post_function_vii: '$_wasmWorkerPostFunction2',
   emscripten_wasm_worker_post_function_vdd: '$_wasmWorkerPostFunction2',
 
   $_wasmWorkerPostFunction3__sig: 'vipddd',
   $_wasmWorkerPostFunction3: function(id, funcPtr, arg0, arg1, arg2) {
-    _wasmWorkers[id].postMessage({'_wsc': funcPtr, 'x': [arg0, arg1, arg2] }); // "WaSm Call"
+    _wasmWorkers.get(id).postMessage({'_wsc': funcPtr, 'x': [arg0, arg1, arg2] }); // "WaSm Call"
   },
   emscripten_wasm_worker_post_function_viii: '$_wasmWorkerPostFunction3',
   emscripten_wasm_worker_post_function_vddd: '$_wasmWorkerPostFunction3',
@@ -210,7 +224,7 @@
     assert(UTF8ToString(sigPtr)[0] != 'v', 'Do NOT specify the return argument in the signature string for a call to emscripten_wasm_worker_post_function_sig(), just pass the function arguments.');
     assert(varargs);
 #endif
-    _wasmWorkers[id].postMessage({'_wsc': funcPtr, 'x': readEmAsmArgs(sigPtr, varargs) });
+    _wasmWorkers.get(id).postMessage({'_wsc': funcPtr, 'x': readEmAsmArgs(sigPtr, varargs) });
   },
 
   $atomicWaitStates: "['ok', 'not-equal', 'timed-out']",
diff --git a/src/library_webaudio.js b/src/library_webaudio.js
index aaf28d3..bcefd3b 100644
--- a/src/library_webaudio.js
+++ b/src/library_webaudio.js
@@ -123,7 +123,7 @@
 
 #if AUDIO_WORKLET
   emscripten_start_wasm_audio_worklet_thread_async__deps: [
-    '$_wasmWorkersID',
+    '$_wasmWorkers',
     '$_EmAudioDispatchProcessorCallback'],
   emscripten_start_wasm_audio_worklet_thread_async: function(contextHandle, stackLowestAddress, stackSize, callback, userData) {
 
@@ -173,9 +173,11 @@
 #if WEBAUDIO_DEBUG
       console.log(`emscripten_start_wasm_audio_worklet_thread_async() addModule('audioworklet.js') completed`);
 #endif
+      // Assign the loaded AudioWorkletGlobalScope a Wasm Worker ID so that it can utilized its own TLS slots, and it is recognized to not be the main browser thread.
+      var id = _wasmWorkers.allocate({});
       audioWorklet.bootstrapMessage = new AudioWorkletNode(audioContext, 'message', {
         processorOptions: {
-          '$ww': _wasmWorkersID++, // Assign the loaded AudioWorkletGlobalScope a Wasm Worker ID so that it can utilized its own TLS slots, and it is recognized to not be the main browser thread.
+          '$ww': id,
 #if MINIMAL_RUNTIME
           'wasm': Module['wasm'],
           'mem': wasmMemory,
diff --git a/test/code_size/hello_wasm_worker_wasm.js b/test/code_size/hello_wasm_worker_wasm.js
index 77a6b5a..1d9133f 100644
--- a/test/code_size/hello_wasm_worker_wasm.js
+++ b/test/code_size/hello_wasm_worker_wasm.js
@@ -1,42 +1,52 @@
-var b = Module, c = b.$ww, f, e = b.mem || new WebAssembly.Memory({
+var b = Module, d = b.$ww, f, e = b.mem || new WebAssembly.Memory({
     initial: 256,
     maximum: 256,
     shared: !0
-}), g = e.buffer, h = [], m = {}, n = 1, p, q;
+}), g = e.buffer, h = [], n = new function() {
+    this.j = [ void 0 ];
+    this.m = [];
+    this.get = a => this.j[a];
+    this.has = a => void 0 !== this.j[a];
+    this.l = a => {
+        var c = this.m.pop() || this.j.length;
+        this.j[c] = a;
+        return c;
+    };
+}, p, q;
 
 function k(a) {
     a = a.data;
-    let d = a._wsc;
-    d && f.get(d)(...a.x);
+    let c = a._wsc;
+    c && f.get(c)(...a.x);
 }
 
-function l(a) {
+function m(a) {
     h.push(a);
 }
 
-c && (m[0] = this, addEventListener("message", l));
+d && (n.j[0] = this, addEventListener("message", m));
 
 WebAssembly.instantiate(b.wasm, {
     a: {
-        b: function(a, d) {
-            let r = m[n] = new Worker(b.$wb);
-            r.postMessage({
-                $ww: n,
+        b: function(a, c) {
+            let l = new Worker(b.$wb), r = n.l(l);
+            l.postMessage({
+                $ww: r,
                 wasm: b.wasm,
                 js: b.js,
                 mem: e,
                 sb: a,
-                sz: d
+                sz: c
             });
-            r.onmessage = k;
-            return n++;
+            l.onmessage = k;
+            return r;
         },
         c: function() {
             return !1;
         },
-        d: function(a, d) {
-            m[a].postMessage({
-                _wsc: d,
+        d: function(a, c) {
+            n.get(a).postMessage({
+                _wsc: c,
                 x: []
             });
         },
@@ -50,7 +60,7 @@
     p = a.g;
     q = a.i;
     f = a.h;
-    c ? (a = b, q(a.sb, a.sz), removeEventListener("message", l), h = h.forEach(k), 
+    d ? (a = b, q(a.sb, a.sz), removeEventListener("message", m), h = h.forEach(k), 
     addEventListener("message", k)) : a.f();
-    c || p();
+    d || p();
 }));
\ No newline at end of file
diff --git a/test/code_size/hello_wasm_worker_wasm.json b/test/code_size/hello_wasm_worker_wasm.json
index b8f284b..ecaa701 100644
--- a/test/code_size/hello_wasm_worker_wasm.json
+++ b/test/code_size/hello_wasm_worker_wasm.json
@@ -1,10 +1,10 @@
 {
   "a.html": 737,
   "a.html.gz": 433,
-  "a.js": 715,
-  "a.js.gz": 465,
+  "a.js": 878,
+  "a.js.gz": 533,
   "a.wasm": 1862,
   "a.wasm.gz": 1040,
-  "total": 3314,
-  "total_gz": 1938
+  "total": 3477,
+  "total_gz": 2006
 }
diff --git a/test/wasm_worker/terminate_wasm_worker.c b/test/wasm_worker/terminate_wasm_worker.c
index ce3989b..5b77b4f 100644
--- a/test/wasm_worker/terminate_wasm_worker.c
+++ b/test/wasm_worker/terminate_wasm_worker.c
@@ -45,11 +45,15 @@
 int should_throw(void(*func)())
 {
   int threw = EM_ASM_INT({
+    var oldAbort = abort;
+    abort = () => { throw 'abort' };
     try {
       dynCall('v', $0);
     } catch(e) {
       console.error('Threw an exception like expected: ' + e);
       return 1;
+    } finally {
+      abort = oldAbort;
     }
     console.error('Function was expected to throw, but did not!');
     return 0;