A while back we added tests to make sure we weren't leaking hiwire keys. As a special case of this, we also were testing that we weren't leaking JsProxies. In the future, I would like to add tests to show that we aren't leaking PyProxies either. Currently we leak quite a lot of them.

Before we actually add any tests to show that we aren't leaking proxies, I'd like to rework the model of the PyProxy lifetime. The planned changes should make it far easier to actually avoid these leaks while still using a reasonably natural coding style.

This change has no user-facing effect: the tracing is disabled by default and the API to enable it is private and always will be.
Add support for pyproxy allocation tracing. A while back we added tests to make sure we weren't leaking hiwire keys. As a special case of this, we also were testing that we weren't leaking JsProxies. In the future, I would like to add tests to show that we aren't leaking PyProxies either. Currently we leak quite a lot of them.

Before we actually add any tests to show that we aren't leaking proxies, I'd like to rework the model of the PyProxy lifetime. The planned changes should make it far easier to actually avoid these leaks while still using a reasonably natural coding style.

This change has no user-facing effect: the tracing is disabled by default and the API to enable it is private and always will be.
diff --git a/emsdk/binary/src/passes/FuncCastEmulation.cpp b/src/passes/FuncCastEmulation.cpp
index 9d232c9..60d3990 100644
--- a/emsdk/binaryen/src/passes/FuncCastEmulation.cpp
+++ b/emsdk/binaryen/src/passes/FuncCastEmulation.cpp
@@ -170,23 +170,86 @@ struct FuncCastEmulation : public Pass {
       std::stoul(runner->options.getArgumentOrDefault("max-func-params", "16"));
     // we just need the one ABI function type for all indirect calls
     Signature ABIType(Type(std::vector<Type>(numParams, Type::i64)), Type::i64);
-    // Add a thunk for each function in the table, and do the call through it.
+
+    // make sure everything in exports has a thunk
+    // so dlsym works
     std::unordered_map<Name, Name> funcThunks;
+    std::unordered_map<Name, Name> exportThunks;
+    for(auto& function: module->exports)
+    {
+        auto& exportName=function->name;
+        auto& exportValue=function->value;
+        // don't create FPCAST emulation for javascript legalizer stubs
+        // (used when javascript doesn't have 64 bit support, so 
+        // can't use fpcast emulation calls which need 64 bit support)
+        if(module->getFunctionOrNull(exportValue) && strncmp(exportValue.str,"legalstub$",10)!=0 )
+        {
+          Name exportThunkName = std::string("byn$fpcast-emu$") + exportName.str;
+          auto iter = funcThunks.find(exportValue);          
+          if (iter == funcThunks.end()) 
+          {
+            // an export without a thunk yet - make it and export it
+            auto thunk = makeThunk(exportValue, module, numParams);
+            funcThunks[exportValue] = thunk;
+            exportThunks[exportThunkName]=thunk;
+          }else
+          {
+            // just export existing thunk
+            exportThunks[exportThunkName]=iter->second;
+          }
+       }
+     }
+    int exportCount=0;
+    // Add a thunk for each function in the table, and do the call through it.
     for (auto& table : module->tables) {
       for (auto& segment : table->segments) {
         for (auto& name : segment.data) {
-          auto iter = funcThunks.find(name);
-          if (iter == funcThunks.end()) {
-            auto thunk = makeThunk(name, module, numParams);
-            funcThunks[name] = thunk;
-            name = thunk;
-          } else {
-            name = iter->second;
+          // don't create FPCAST emulation for javascript legalizer stubs
+          // (used when javascript doesn't have 64 bit support, so
+          // can't use fpcast emulation calls which need 64 bit support)
+          if(strncmp(name.str,"legalstub$",10)!=0) {
+            auto iter = funcThunks.find(name);
+            if (iter == funcThunks.end()) {
+              // we've already made thunks for exported funcionts so
+              // this is a static function - make the thunk and make an anon export 
+              // to it so that it can be called by pyodide
+              Name orig_name(name);
+              auto thunk = makeThunk(name, module, numParams);
+              funcThunks[name] = thunk;
+              exportCount+=1;
+              char buffer[256];
+              snprintf(buffer,256,"byn$fpcast-emu$__static_%d",exportCount);
+              // first make the fpcast version of the function
+              auto* export_ = new Export;
+              export_->name = buffer;
+              export_->value = thunk;
+              export_->kind = ExternalKind::Function;
+              module->addExport(export_);
+              // now make the non-fpcast version
+              snprintf(buffer,256,"__static_%d",exportCount);
+              auto* export2_ = new Export;
+              export2_->name = buffer;
+              export2_->value = orig_name;
+              export2_->kind = ExternalKind::Function;
+              module->addExport(export2_);
+              name = thunk;
+            } else {
+              name = iter->second;
+            }
           }
         }
       }
     }

+    // make exports for all export thunks    
+    for(auto& thunk: exportThunks) {
+        auto* export_ = new Export;
+        export_->name = thunk.first;
+        export_->value = thunk.second;
+        export_->kind = ExternalKind::Function;
+        module->addExport(export_);
+    }
+
     // update call_indirects
     ParallelFuncCastEmulation(ABIType, numParams).run(runner, module);
   }

diff --git a/emsdk/binary/src/passes/FuncCastEmulation.cpp b/src/passes/FuncCastEmulation.cpp
index 9d232c9..60d3990 100644
--- a/emsdk/binaryen/src/passes/FuncCastEmulation.cpp
+++ b/emsdk/binaryen/src/passes/FuncCastEmulation.cpp
@@ -170,23 +170,86 @@ struct FuncCastEmulation : public Pass {
       std::stoul(runner->options.getArgumentOrDefault("max-func-params", "16"));
     // we just need the one ABI function type for all indirect calls
     Signature ABIType(Type(std::vector<Type>(numParams, Type::i64)), Type::i64);
-    // Add a thunk for each function in the table, and do the call through it.
+
+    // make sure everything in exports has a thunk
+    // so dlsym works
     std::unordered_map<Name, Name> funcThunks;
+    std::unordered_map<Name, Name> exportThunks;
+    for(auto& function: module->exports)
+    {
+        auto& exportName=function->name;
+        auto& exportValue=function->value;
+        // don't create FPCAST emulation for javascript legalizer stubs
+        // (used when javascript doesn't have 64 bit support, so 
+        // can't use fpcast emulation calls which need 64 bit support)
+        if(module->getFunctionOrNull(exportValue) && strncmp(exportValue.str,"legalstub$",10)!=0 )
+        {
+          Name exportThunkName = std::string("byn$fpcast-emu$") + exportName.str;
+          auto iter = funcThunks.find(exportValue);          
+          if (iter == funcThunks.end()) 
+          {
+            // an export without a thunk yet - make it and export it
+            auto thunk = makeThunk(exportValue, module, numParams);
+            funcThunks[exportValue] = thunk;
+            exportThunks[exportThunkName]=thunk;
+          }else
+          {
+            // just export existing thunk
+            exportThunks[exportThunkName]=iter->second;
+          }
+       }
+     }
+    int exportCount=0;
+    // Add a thunk for each function in the table, and do the call through it.
     for (auto& table : module->tables) {
       for (auto& segment : table->segments) {
         for (auto& name : segment.data) {
-          auto iter = funcThunks.find(name);
-          if (iter == funcThunks.end()) {
-            auto thunk = makeThunk(name, module, numParams);
-            funcThunks[name] = thunk;
-            name = thunk;
-          } else {
-            name = iter->second;
+          // don't create FPCAST emulation for javascript legalizer stubs
+          // (used when javascript doesn't have 64 bit support, so
+          // can't use fpcast emulation calls which need 64 bit support)
+          if(strncmp(name.str,"legalstub$",10)!=0) {
+            auto iter = funcThunks.find(name);
+            if (iter == funcThunks.end()) {
+              // we've already made thunks for exported funcionts so
+              // this is a static function - make the thunk and make an anon export 
+              // to it so that it can be called by pyodide
+              Name orig_name(name);
+              auto thunk = makeThunk(name, module, numParams);
+              funcThunks[name] = thunk;
+              exportCount+=1;
+              char buffer[256];
+              snprintf(buffer,256,"byn$fpcast-emu$__static_%d",exportCount);
+              // first make the fpcast version of the function
+              auto* export_ = new Export;
+              export_->name = buffer;
+              export_->value = thunk;
+              export_->kind = ExternalKind::Function;
+              module->addExport(export_);
+              // now make the non-fpcast version
+              snprintf(buffer,256,"__static_%d",exportCount);
+              auto* export2_ = new Export;
+              export2_->name = buffer;
+              export2_->value = orig_name;
+              export2_->kind = ExternalKind::Function;
+              module->addExport(export2_);
+              name = thunk;
+            } else {
+              name = iter->second;
+            }
           }
         }
       }
     }

+    // make exports for all export thunks    
+    for(auto& thunk: exportThunks) {
+        auto* export_ = new Export;
+        export_->name = thunk.first;
+        export_->value = thunk.second;
+        export_->kind = ExternalKind::Function;
+        module->addExport(export_);
+    }
+
     // update call_indirects
     ParallelFuncCastEmulation(ABIType, numParams).run(runner, module);
   }
