Closes https://github.com/pyodide/pyodide/issues/604

This creates an installable `pyodide-build` Python package that we can also upload to PyPi under https://pypi.org/project/pyodide-build/ (currently uploaded the dev version to reserve the name).

There is also the identically named `pyodide-build` CLI command which replaces `bin/pyodide`.

The short term goals is to use fewer hacks with `PATH` and `PYTHONPATH`. Long term goal make it a user facing interface for building packages instead of the Makefile.
Create an installable pyodide-build package. Closes https://github.com/pyodide/pyodide/issues/604

This creates an installable `pyodide-build` Python package that we can also upload to PyPi under https://pypi.org/project/pyodide-build/ (currently uploaded the dev version to reserve the name).

There is also the identically named `pyodide-build` CLI command which replaces `bin/pyodide`.

The short term goals is to use fewer hacks with `PATH` and `PYTHONPATH`. Long term goal make it a user facing interface for building packages instead of the Makefile.
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);
   }
