-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Opening a side module larger than 4KB fails on Chrome #11753
Comments
It's a Chrome limitation, you can use |
It looks like the preloading logic looks for the normal shared library suffix on POSIX, emscripten/src/library_browser.js Line 241 in df56ba1
We should document that if it isn't already. |
@sbc100 Could you please help me? I would really appreciate it! |
Can you try using the BTW in this mode you are going to delay the startup of you program until both the side module and main module are instantiated, so you are not really getting any benefit at runtime from using the a side module. Would it make more sense to simply compile the side module into the main program? |
@sbc100 How do I build a side module as the
I need the side module to exist separately from the main module, and the side module will be instantiated in the middle of the program not necessarily at the startup. |
Yes If you preload the file with |
Yes, It's okay if there's a delay. I'm using the I used these commands to build:
Now I got these errors when I run it in Chrome:
|
If loading the large-size side module with |
Did you remember to change the name of side module in main.c? Its should work with reloading such that side.so is loaded at start and you should not see a runtime call to . emscripten/src/library_browser.js Line 252 in 8bd9c93
If that doesn't work you can try using the asynrous JS function loadDynamicLibrary as suggested earlier in this thread. |
@sbc100 Yes I did of course. side.c:
main.c:
index.html:
commands:
Result:
|
I tried to use the
But I get this error:
|
Use EM_JS and rename .wasm to .so |
@gerald-dotcom I still get the same error:
Here's my code: main.c:
commands:
|
Strange... somehow the side module is missing the dylink section which emscripten adds to the beginning of it. |
I also posted my question on Stackoverflow https://stackoverflow.com/q/63150966/5224286 with a bounty. |
Ah! The problem is that you are passing If you remove Sadly it looks like you also have to change the output name back to |
@sbc100 But if I remove I get the following error:
|
Sadly it looks like you also have to change the output name back to .wasm to persuade emcc to produce a side module. You will then need to rename the file .so when to preload it. We should fix that part. |
Apologies for that very hard to understand error message... the situation with linking |
So is it not possible to load a side module larger than 4KB at the moment? |
No, its possible, you just need to do the instantiation asynchronously because the browser doesn't support the synchronous loading of larger modules. This limitation is built into the browser and not related to emscripten. Both loadDynamicLibrary (with |
Thank you. so how can I generate the |
Yes, if you want to use dlopen in combination with the preload plugin system that seems necessary today. I will work two changes:
But you should be able to get it working even without either of those changes. |
Thanks, I tried it as you said. Here's my code: main.cpp
index.html
commands:
After the page is loaded, if I press the
What did I do wrong? |
That looks like a name mangling issue. If you compile your side module as C rather than C++ then you need to also add
|
While working on some fixed in this area I noticed that the preload+dlopen approach has some other issues so its good that proceed with the loadDynamicLibrary approach for now until we can address them. |
It worked! Thank you so much! May I ask how to implement a callback function to detect if the |
Looks like you can wait on the returned promise:
|
Thank you so much! |
Try using |
The easy way to actually do this is to load the module with WebAssembly.instantiate in the javascript side. Use this tutorial to get beyond the 4K limit, https://developers.google.com/web/updates/2018/04/loading-wasm Once the module is loaded, get the function this way: side = instance.exports._Z4sidev; So we now have a javascript function of side but this now seems unusable to use in C++, until you realise that Emscripten has a handy function to get the function pointers for C/C++ called addFunction: sidePtr = addFunction(side, 'vi'); The 'vi' to addFunction is really not that important here. It can be anything. That passes back an int that is a function pointer. Some of you will now wonder how can the int be a pointer? Under the hood of Emscripten compiling all functions in C/C++ after the code is compiled in WASM will be an int. With that knowledge you can just do this in C/C++: typedef int (*side_function)(); And somehow compiling with Emscripten this all works great! |
@cuinjune did you meet this kind of issue before? Seems it is caused by my.wasm building settings?EM_JS(void, doLoadLibrary, (), {
Asyncify.handleAsync(async() => {
try
{
await loadDynamicLibrary('ff/lib/my.wasm', {loadAsync : true, global : true, nodelete : true, fs : FS});
//console.log('side module size: ' + Module['_side']());
}
catch (error)
{
console.log(error);
}
});
}); |
Following-up on this thread. @kripken I am working on the code base that @cuinjune originally developed and whose critical component is the solution developed in this thread. It appears that building that code on an older version of emscripten allows for loadDynamicLibrary to work. On the latest emscripten version it fails, as follows (please bear with me since I am new to emscripten and am still trying to find my way around, so apologies in advance if I am poorly describing the problem):
The fiilename reflects the file's original location on the disk. I may be wrong about this, but it seems to me the last two numbers perhaps reflect where it is packed address-wise in the bundled file created by the compile/linking? Curiously, the older version had also , "audio": 0/1} appended at the end, suggesting the older version also offered differentiation between audio files and objects. Not sure why this is not there when compiled with a newer version, and whether that may be causing the aforesaid problem.
Is the GET being called because the code is somehow not finding the object in question inside the bundle?
error: load_object: Symbol "average_setup" not found All objects have their objectname_setup as their entry point function and have been compiled as follows (please pardon the redundancy of the makefile flags--this is a huge code base with over a thousand of objects developed by 3rd parties, so the automated build process is a bit messy):
Below is also the relevant code that calls loadDynamicLibrary:
The relevant build script code that bundles everything together on the emscripten side of things is as follows:
I also updated deprecated EXTRA_EXPORTED_RUNTIME_METHODS to EXPORTED_RUNTIME_METHODS, as follows:
Note the EXPORTED_RUNTIME_METHODS. Not sure if these should be prepended with an underscore. I tried both with or without it. Also tried:
That one generates the following warning:
So, I also tried with SIDE_MODULE=2 and EXPORTED_FUNCTIONS with no success. Curiously, this also suggests that all functions are exported. If so, why are they not being found by loadDynamicLibrary call above?
What can be done to be once again able to reference these bundled objects (preferable), or allowing them to be loaded via the GET option (which I imagine is a fallback routine--please correct me if I may be wrong)? I would greatly appreciate any input you may have here, as I am completely stumped since the only moving variable is the emscripten version (apart from changing the EXPORTED_RUNTIME_METHODS part, where I tried both versions)? Thank you for your time in considering this request. |
@pd-l2ork, it seems likely that the change that broke your solution was #19310. This change moved the filesystem reading part of library loading into native code which is now part of By the way the lines in your binary such as A few other (unrelated) notes on your command line flags.
|
Thank you @sbc100 for your reply. I think the original issue is that many of these objects are larger than 4KB (see the thread title). Has this been resolved? If so, would you be able to provide a skeleton example function that does the same replacing the __PD_loadlib one? Thank you again for your help. |
Chrome has never supported loading module >4kb synchronously. The only way I know of that works around that would be via preloading of the modules, which See |
Thanks again @sbc100 . So, now I tried changing code to use dlopen (I tried transplanting the original code by @cuinjune ) and now the system is unable to locate the file. Is dlopen trying to search --preload-file database/list (or whatever it is), or is it trying to locate the file on the local drive where the server is running? If it is going through the list, I have objects that share the same name (but are in different subfolders). How could one distinguish between the two, when the code above shows only referencing the actual filename.wasm? In other words, if, for instance the stored object is:
should the file to be loaded be referenced as:
And what is this location in reference to? To the folder where the server was originally started? |
In follow-up to my previous question, how would dlopen example code look inside js? When I try to compile js code with dlopen function, it says it does not recognize the function. Here's the original JS code using loadDynamicLibrary:
The C version of dlopen is seen in the top post in this thread:
Note this is a part of the main, so I am not even sure how this would look as a separate C function, since I am quite sure the handle would disappear at the end of that function call. So, how would this dlopen code look inside a js call, like the one above that is wrapped into EM_JS? |
I believe with dlopen is a native call so if you don't need to use an EM_JS function at all to call it. You can, for example, just write a function that calls dlopen + dlsym and then returns that function pointer (i.e. the result of dlsym). |
FWIW, Chrome extended this limit to 8MB in version 115, see commit chromium/chromium@d1a1a8f. |
Thank you for the clarification. After further hacking I am now finding that the object is successfully loaded. However, for some reason, the core code that this project is trying to port into a browser environment, seems to continue iterating looking for the object, even though it has supposedly found it. I suspect this is because it needs the pointer... Also, to clarify, average.wasm was never used and it did not work when I manually entered it, so that part should be ok. More soon... Thanks, again, for all your help. |
@sbc100 @kripken OK, so I was able to verify that the new object is indeed loading the setup function. However, once the setup is loaded, the next is instantiation (average_new function located inside a separate .c file dedicated to this object/plugin that is to be dynamically loaded). What I found out is that if this function is declared as static, I end up having the Uncaught RuntimeError: null function or function signature mismatch error. If I recompile the object with that function not being static, everything works. This is a problem, because there are over a thousand of 3rd party objects, some that may share functions that are named the same, so it would be a really bad idea to keep all those functions in a global space. More so, the same source is used to compiile a native version of the software, which makes an additional argument against removing static declaration of those variables. So, how does one ensure that you can dlopen external object/library/plugin that may have its own static variables that should be exposed within the browser? |
As far as I know there is no way to access at |
This code is actively maintained on desktop and runs on Windows, Linux, and OSX. The average_new function being declared static is no problem because (I think--I am not the original author of this architecture) of the following:
As I mentioned, this works fine on all desktop platforms, so I am unsure why it is not working here. The same code has also worked just fine on the previous version of emscripten that relied upon loadDynamicLibrary. |
So IIUC there is some kind of setup function that returns at struct containing function pointers? That should work fine. Are you able to run the setup function? What does it return? Perhaps if you should share an example of the exact code that is failing that might help (in as simple a form as possible, if you can). |
The setup function runs fine. I know this because when run it prints out a message. The average_new is never reached (I have another printout message on top of it). I wish I could simplify this to make it more easy to share. Alas, this may take me a couple of days just to come up with such an abridged version. The overall code base of this thing is humongous--git suggests over 1.5M lines of code, although some of this is definitely due to a collection of images. |
How is |
Here's a general breakdown: The program (pd-l2ork) reads a text file (a.k.a. patch) that contains visual dataflow code split into visual object creation (think of it as boxes with names, each with particular function), object connection (visual cords connecting those boxes), and encapsulation (boxes grouped into other subpatches). Here, we are only concerned with object creation. Note that everything I am about to share works perfectly in desktop environments and has worked fine in emscripten release sometime in 2021 using loadDynamicLibrary approach that @cuinjune implemented, as shown in the thread above. Below are key structures and functions: Core:
Core or external objects:
When creating objects that come built-in with the core pd-l2ork package (unlike 3rd party objects), everything is created just fine. However, when trying to create dynamically loaded object, this is something that used to work fine around 2021 and now does not, with only notable change between now and then being emscripten version update. So, I set out to adapt loading using dlopen and here's the current status: Core pd-l2ork reads the object creation request from the patch file. It searches the existing class list and if it does not find it, tries to look for an external. This eventually triggers sys_do_load_lib, which in turn triggers average_setup, which triggers class_new, and then invokes creation of the actual object. The resulting relevant console printout excerpt shows what worked and where it fails:
Below is the relevant build output: average.c:
main (combination of core and all bundled externals):
|
Regarding the old EM_JS code, I think I do see a problem there. The code is expecting symbols from the loaded library to be presend on the global Module object with the underscore prefix. i.e. These days we use However, the dlopen approach should also work and be simpler and more robust over time. Are the dlsym calls succeeding? Presumable one of the |
If you want to get better stack traces can you try building with
|
Thank you. I will try to revert back to that implementation and see how it goes.
gensym is simply generating a symbol (string) that is kept in a string table. This is needed for a number of reasons, including wirelessly connecting objects via internal send and receive objects (among other things). BTW, dlopen does work up through the setup function. Where it fails is when trying to instantiate average_new, which should've been stored in class_new, and whose address, per my debug printouts, appears to be tracking across multiple function calls. |
I will also make sure to compile with a debug flag and will let you know. Thank you. |
So, I am completely baffled. I just went to enable the debug options and I don't think I even did it on all components that comprise the final main wasm file and upon recompiling dlopen worked as expected. I then, went on to disable debugging to revert everything back and was unable to break it again. Eventually, I was able to enable debugging for everything and that ended-up spawning some odd errors, but this is a separate issue at this point. I know for a fact that I cleaned the entire tree before. Could it be that there may have been a stale .o file that could've caused this? I am thinking .o file from a different compile time may generate a different signature, resulting in an incompatible build perhaps? I am at a loss, and my brain is fried... More tomorrow... Thanks again for all your help. |
For whatever rason I am now unable to break it in the way it was broken before, but am now running into the debug-enabled version throwing bunch of errors that are not present in an optimized build. Below is an example of the output. Everything loads fine in the browser, but as son as I click, I get the following stream of errors (they repeat, so below is only a snapshot). None of this is a problem when running -O3 build (this one is -g):
I tried compiling with -s USE_PTHREADS as one of the online resources suggested, but that made things even worse, making the program fail to even load (threw one error that I don't have on hand and stopped right there). Should I make this a separate issue? |
Here is a more on-topic question. So, when I run a patch (a snippet of code) that does not require dlopen, everything works fine. However, if I have a patch that invokes dlopen AND the object in question is supposed to process audio, even though the object now loads correctly, as soon as I do anything on the page with my mouse (e.g. click onto a visual widget, a slider or a button), I get the following error:
The source of error may be different depending on what I pressed, but the end result is always the same. I tried recompiling with |
Looks like a memory corruption issue. I would recommend a debug build with |
Regarding the original issue: Chome just increased the limit from 4kb to 8Mb |
So, as a closure to this discussion, I learned two critical things:
Thanks @sbc100 again for all your time and assistance. |
Hi, I'm trying to dynamically load a side module from the main module using
dlopen()
.The side module loads fine as long as its size is smaller than 4KB but I need to load large-size side modules.
Here's a simple code you can test this:
side.c:
main.c:
index.html:
commands:
And this is the result I get in the Chrome browser:
Can someone please guild me on how to dynamically load a side module larger than 4KB?
Thank you in advance!
The text was updated successfully, but these errors were encountered: