Thankfully this was a lot easier than tracing hiwire keys. 

I located one actual leak: if an error is thrown during `to_js` it would forget to free `proxies`. Other than that, I fixed a few "dumb leaks" in `api.js` where we leak the methods like `eval_code` or `find_imports`. So the code was already in surprisingly good shape in this way.

It's still possible that we leak some Python objects somehow, but this is as good as we can do for systematic checks short of compiling Python itself with `TRACE_MALLOC`.
Eliminate pyproxy leaks. Thankfully this was a lot easier than tracing hiwire keys. 

I located one actual leak: if an error is thrown during `to_js` it would forget to free `proxies`. Other than that, I fixed a few "dumb leaks" in `api.js` where we leak the methods like `eval_code` or `find_imports`. So the code was already in surprisingly good shape in this way.

It's still possible that we leak some Python objects somehow, but this is as good as we can do for systematic checks short of compiling Python itself with `TRACE_MALLOC`.
diff --git a/emsdk/upstream/emscripten/src/library.js b/emsdk/upstream/emscripten/src/library.js
index 2990646..517fc33 100644
@@ -3749,6 +3749,15 @@ LibraryManager.library = {
 #endif
 #if ASSERTIONS
     assert(wasmTable.get(ptr), 'missing table entry in dynCall: ' + ptr);
+#endif
+#if EMULATE_FUNCTION_POINTER_CASTS
+    if( ptr in dyncallInvokeMap)
+    {
+        // in fpcast mode, we use a separate table or else we'll attempt
+        // to call the fpcast emulated version which is not what the linker
+        // expects for a linked dynamic call
+        ptr=dyncallInvokeMap[ptr];
+    }
 #endif
     return wasmTable.get(ptr).apply(null, args)
 #endif
--- a/emsdk/upstream/emscripten/src/runtime_init_table.js
+++ b/emsdk/upstream/emscripten/src/runtime_init_table.js
@@ -13,3 +13,17 @@ var wasmTable = new WebAssembly.Table({
 // the exports are available.
 var wasmTable;
 #endif
+#if EMULATE_FUNCTION_POINTER_CASTS==1
+    // when we call dynamic link functions through the relocation
+    // invoke xxx methods, we aren't doing a function pointer
+    // call, so we don't mess with the function prototype.
+    // however, we do need to make the function table
+    // point to the emulated bny$fpcast-emu$ function pointer calls or else you
+    // can't take function pointers in wasm code.
+    // this means we need a different table for javascript dyncall invokes
+    // as given these are pre-linked and don't use function pointer casts, they
+    // will use the non-emulated version with the normal arguments.
+    // nb. this doesn't have to be a proper wasm table because it is
+    // only ever called from javascript
+    var dyncallInvokeMap=new Map();
+#endif
diff --git a/emsdk/upstream/emscripten/tools/shared.py b/emsdk/upstream/emscripten/tools/shared.py
index 74666ee..3d5657b 100644
--- a/emsdk/upstream/emscripten/tools/shared.py
+++ b/emsdk/upstream/emscripten/tools/shared.py
@@ -844,7 +844,7 @@ class JS(object):
       else:
         return 'Module["dynCall_%s"](%s)' % (sig, args)
     else:
-      return 'wasmTable.get(%s)(%s)' % (args[0], ','.join(args[1:]))
+        return 'dynCall("%s",%s,[%s])' %(sig,args[0],','.join(args[1:]))
 
   @staticmethod
   def make_invoke(sig, named=True):
diff --git a/emdsk/upstream/emscripten/src/library_dylink.js b/emsdk/upstream/emscripten/src/library_dylink.js
index 876492b..750a18d 100644
--- a/emdsk/upstream/emscripten/src/library_dylink.js
+++ b/emsdk/upstream/emscripten/src/library_dylink.js
@@ -90,33 +93,84 @@ var LibraryDylink = {
       var value = exports[symName];
 #if !WASM_BIGINT
       if (symName.indexOf('orig$') == 0) {
-        symName = symName.split('$')[1];
+        symName = symName.substring(5);
         replace = true;
       }
 #endif
-
-      if (!GOT[symName]) {
-        GOT[symName] = new WebAssembly.Global({value: 'i32', mutable: true});
+      baseName=symName;
+#if EMULATE_FUNCTION_POINTER_CASTS==1
+      const FPCAST_PREFIX="byn$fpcast-emu$";
+      const FPCAST_DYNCALL_PREFIX="$no-fpcast-emu$";
+      var dyncallName;
+      var is_fpcast=false;
+      var is_nonfpcast=false;
+      var newFn;
+      if(typeof(value) === 'function')
+      {
+          if(symName.startsWith(FPCAST_PREFIX))
+          {
+              baseName=symName.substr(FPCAST_PREFIX.length);
+              dyncallName=FPCAST_DYNCALL_PREFIX+baseName;
+              is_fpcast=true;
+          }else
+          {
+             if(FPCAST_PREFIX+symName in exports)
+             {
+                 baseName=FPCAST_DYNCALL_PREFIX+symName;
+                 is_nonfpcast=true;
+             }
+          }
       }
-      if (replace || GOT[symName].value == 0) {
+#endif
+      if (!GOT[baseName]) {
+        GOT[baseName] = new WebAssembly.Global({value: 'i32', mutable: true});
+      }
+      if (replace || GOT[baseName].value == 0) {
         if (typeof value === 'function') {
-          GOT[symName].value = addFunctionWasm(value);
+          newFn=addFunctionWasm(value);
+          GOT[baseName].value = newFn
 #if DYLINK_DEBUG
-          err("updateGOT FUNC: " + symName + ' : ' + GOT[symName].value);
+          err("updateGOT FUNC: " + symName + ' : ' + GOT[baseName].value);
 #endif
         } else if (typeof value === 'number') {
-          GOT[symName].value = value;
+          GOT[baseName].value = value;
         } else {
           err("unhandled export type for `" + symName + "`: " + (typeof value));
         }
 #if DYLINK_DEBUG
-        err("updateGOT: " + symName + ' : ' + GOT[symName].value);
+        err("updateGOT: " + baseName + "("+ symName + ")" + ' : ' + GOT[symName].value);
 #endif
       }
 #if DYLINK_DEBUG
-      else if (GOT[symName].value != value) {
-        err("updateGOT: EXISTING SYMBOL: " + symName + ' : ' + GOT[symName].value + " " + value);
+      else if (GOT[baseName].value != value) {
+        err("updateGOT: EXISTING SYMBOL: " + symName + ' : ' + GOT[baseName].value + " " + value);
       }
+#endif
+#if EMULATE_FUNCTION_POINTER_CASTS==1
+        // update map for dynamic calls so they can bypass fp casts
+        if(!newFn)newFn= GOT[baseName].value;
+        if(is_fpcast && newFn)
+        {
+            dyncallGOT=GOT[dyncallName]
+            if(dyncallGOT){
+                dcVal=dyncallGOT.value;
+                if(dcVal)
+                {
+                    dyncallInvokeMap[newFn]=dcVal;
+                }
+            }
+        }else if(is_nonfpcast && newFn)
+        {
+            fpcGOT=GOT[symName];
+            if(fpcGOT)
+            {
+                fpVal=fpcGOT.value;
+                if(fpVal)
+                {
+                    dyncallInvokeMap[fpVal]=newFn;
+                }
+            }
+        }
 #endif
     }
 #if DYLINK_DEBUG
@@ -713,6 +769,8 @@ var LibraryDylink = {
       nodelete: Boolean(flags & {{{ cDefine('RTLD_NODELETE') }}}),

       fs: FS, // load libraries from provided filesystem
+      allowUndefined: true, // allow undefined symbols - otherwise scipy won't
+                            // import because modules have cross dependencies
     }

     try {
@@ -754,6 +812,10 @@ var LibraryDylink = {
     // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html
     symbol = UTF8ToString(symbol);
     var result;
+#if EMULATE_FUNCTION_POINTER_CASTS==1
+      // look for fpcast-emu version as this is deffo going to be a function pointer that we return
+      symbol="byn$fpcast-emu$"+symbol
+#endif

     if (handle == {{{ cDefine('RTLD_DEFAULT') }}}) {
       result = resolveGlobalSymbol(symbol, true);

diff --git a/emsdk/upstream/emscripten/src/library.js b/emsdk/upstream/emscripten/src/library.js
index 2990646..517fc33 100644
@@ -3749,6 +3749,15 @@ LibraryManager.library = {
 #endif
 #if ASSERTIONS
     assert(wasmTable.get(ptr), 'missing table entry in dynCall: ' + ptr);
+#endif
+#if EMULATE_FUNCTION_POINTER_CASTS
+    if( ptr in dyncallInvokeMap)
+    {
+        // in fpcast mode, we use a separate table or else we'll attempt
+        // to call the fpcast emulated version which is not what the linker
+        // expects for a linked dynamic call
+        ptr=dyncallInvokeMap[ptr];
+    }
 #endif
     return wasmTable.get(ptr).apply(null, args)
 #endif
--- a/emsdk/upstream/emscripten/src/runtime_init_table.js
+++ b/emsdk/upstream/emscripten/src/runtime_init_table.js
@@ -13,3 +13,17 @@ var wasmTable = new WebAssembly.Table({
 // the exports are available.
 var wasmTable;
 #endif
+#if EMULATE_FUNCTION_POINTER_CASTS==1
+    // when we call dynamic link functions through the relocation
+    // invoke xxx methods, we aren't doing a function pointer
+    // call, so we don't mess with the function prototype.
+    // however, we do need to make the function table
+    // point to the emulated bny$fpcast-emu$ function pointer calls or else you
+    // can't take function pointers in wasm code.
+    // this means we need a different table for javascript dyncall invokes
+    // as given these are pre-linked and don't use function pointer casts, they
+    // will use the non-emulated version with the normal arguments.
+    // nb. this doesn't have to be a proper wasm table because it is
+    // only ever called from javascript
+    var dyncallInvokeMap=new Map();
+#endif
diff --git a/emsdk/upstream/emscripten/tools/shared.py b/emsdk/upstream/emscripten/tools/shared.py
index 74666ee..3d5657b 100644
--- a/emsdk/upstream/emscripten/tools/shared.py
+++ b/emsdk/upstream/emscripten/tools/shared.py
@@ -844,7 +844,7 @@ class JS(object):
       else:
         return 'Module["dynCall_%s"](%s)' % (sig, args)
     else:
-      return 'wasmTable.get(%s)(%s)' % (args[0], ','.join(args[1:]))
+        return 'dynCall("%s",%s,[%s])' %(sig,args[0],','.join(args[1:]))
 
   @staticmethod
   def make_invoke(sig, named=True):
diff --git a/emdsk/upstream/emscripten/src/library_dylink.js b/emsdk/upstream/emscripten/src/library_dylink.js
index 876492b..750a18d 100644
--- a/emdsk/upstream/emscripten/src/library_dylink.js
+++ b/emsdk/upstream/emscripten/src/library_dylink.js
@@ -90,33 +93,84 @@ var LibraryDylink = {
       var value = exports[symName];
 #if !WASM_BIGINT
       if (symName.indexOf('orig$') == 0) {
-        symName = symName.split('$')[1];
+        symName = symName.substring(5);
         replace = true;
       }
 #endif
-
-      if (!GOT[symName]) {
-        GOT[symName] = new WebAssembly.Global({value: 'i32', mutable: true});
+      baseName=symName;
+#if EMULATE_FUNCTION_POINTER_CASTS==1
+      const FPCAST_PREFIX="byn$fpcast-emu$";
+      const FPCAST_DYNCALL_PREFIX="$no-fpcast-emu$";
+      var dyncallName;
+      var is_fpcast=false;
+      var is_nonfpcast=false;
+      var newFn;
+      if(typeof(value) === 'function')
+      {
+          if(symName.startsWith(FPCAST_PREFIX))
+          {
+              baseName=symName.substr(FPCAST_PREFIX.length);
+              dyncallName=FPCAST_DYNCALL_PREFIX+baseName;
+              is_fpcast=true;
+          }else
+          {
+             if(FPCAST_PREFIX+symName in exports)
+             {
+                 baseName=FPCAST_DYNCALL_PREFIX+symName;
+                 is_nonfpcast=true;
+             }
+          }
       }
-      if (replace || GOT[symName].value == 0) {
+#endif
+      if (!GOT[baseName]) {
+        GOT[baseName] = new WebAssembly.Global({value: 'i32', mutable: true});
+      }
+      if (replace || GOT[baseName].value == 0) {
         if (typeof value === 'function') {
-          GOT[symName].value = addFunctionWasm(value);
+          newFn=addFunctionWasm(value);
+          GOT[baseName].value = newFn
 #if DYLINK_DEBUG
-          err("updateGOT FUNC: " + symName + ' : ' + GOT[symName].value);
+          err("updateGOT FUNC: " + symName + ' : ' + GOT[baseName].value);
 #endif
         } else if (typeof value === 'number') {
-          GOT[symName].value = value;
+          GOT[baseName].value = value;
         } else {
           err("unhandled export type for `" + symName + "`: " + (typeof value));
         }
 #if DYLINK_DEBUG
-        err("updateGOT: " + symName + ' : ' + GOT[symName].value);
+        err("updateGOT: " + baseName + "("+ symName + ")" + ' : ' + GOT[symName].value);
 #endif
       }
 #if DYLINK_DEBUG
-      else if (GOT[symName].value != value) {
-        err("updateGOT: EXISTING SYMBOL: " + symName + ' : ' + GOT[symName].value + " " + value);
+      else if (GOT[baseName].value != value) {
+        err("updateGOT: EXISTING SYMBOL: " + symName + ' : ' + GOT[baseName].value + " " + value);
       }
+#endif
+#if EMULATE_FUNCTION_POINTER_CASTS==1
+        // update map for dynamic calls so they can bypass fp casts
+        if(!newFn)newFn= GOT[baseName].value;
+        if(is_fpcast && newFn)
+        {
+            dyncallGOT=GOT[dyncallName]
+            if(dyncallGOT){
+                dcVal=dyncallGOT.value;
+                if(dcVal)
+                {
+                    dyncallInvokeMap[newFn]=dcVal;
+                }
+            }
+        }else if(is_nonfpcast && newFn)
+        {
+            fpcGOT=GOT[symName];
+            if(fpcGOT)
+            {
+                fpVal=fpcGOT.value;
+                if(fpVal)
+                {
+                    dyncallInvokeMap[fpVal]=newFn;
+                }
+            }
+        }
 #endif
     }
 #if DYLINK_DEBUG
@@ -713,6 +769,8 @@ var LibraryDylink = {
       nodelete: Boolean(flags & {{{ cDefine('RTLD_NODELETE') }}}),

       fs: FS, // load libraries from provided filesystem
+      allowUndefined: true, // allow undefined symbols - otherwise scipy won't
+                            // import because modules have cross dependencies
     }

     try {
@@ -754,6 +812,10 @@ var LibraryDylink = {
     // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html
     symbol = UTF8ToString(symbol);
     var result;
+#if EMULATE_FUNCTION_POINTER_CASTS==1
+      // look for fpcast-emu version as this is deffo going to be a function pointer that we return
+      symbol="byn$fpcast-emu$"+symbol
+#endif

     if (handle == {{{ cDefine('RTLD_DEFAULT') }}}) {
       result = resolveGlobalSymbol(symbol, true);
