-
-
Notifications
You must be signed in to change notification settings - Fork 271
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
WASM backend throws ArrayIndexOutOfBoundsException while compiling org.teavm.backend.wasm.runtime.WasmSupport.initClasses()V #955
Comments
It's a known issue. Asyncs in WebAssembly BE de facto does not work. They should be completely redesigned and re-written, the way how they are written right now is completely wrong. However, I never had enough time and motivation to do that, so you better give up. Also, I believe it's not that easy to get rid of asyncs in your code base, as you did it with JSO. You can assume that I'll never fix this issue in Wasm BE ever, sorry. Anyway, right now I'm working on Wasm GC BE, which relies on Wasm GC proposal, currently supported in Chrome and Firefox, you can learn which versions here. I also suggest you to use Chrome dev tools to profile your app, perhaps a lot can be done without switching to WebAssembly. For example, you app is primarily GPU-bound, not CPU-bound. And I remember that I saw how |
Its very easy for me to remove asyncs from the code if you mean So if I'm not using |
In the most recent versions, we did profile it and discover the issue was |
Oh it is a little faster without glGetError (that was put in the game by the original developers not me), I get about 2800 FPS max in my browser now so it adds like 0.071 milliseconds per frame on my hardware. |
No, this won't help. Java synchronization primitives use
I suppose that impact on old slow Chromebooks is even much more significant. As for my own experience, with our app we can't rely on our developers' PCs when testing performance, because they are not just faster, but also their GPUs are much more advanced and do a lot of performance work for you. With those poor GPUs on Chromebooks patterns are quite different and things that you haven't noticed on good hardware, become making a tremendous impact on performance. |
I see, currently there’s no part of my codebase that really depends on being async, it’s designed to run in both a browser and ordinary JVM using LWJGL, so all the higher-level code is already synchronous and only needs a single thread to work. The only part I’m using asyncs is for blocking the thread in the browser runtime while certain async browser functions complete. In the case of eliminating all async, images and sounds can just be loaded in WASM (my code already supports JOrbis for synchronous OGG file decoding on unsupported browsers, and images are all PNGs which is easy to parse). File IO can just use the WASI filesystem interface, and for requestAnimationFrame I can just make the application’s main loop an exported function. None of these things are especially difficult to implement in my opinion. I have two questions though, if you could clarify it would be great:
Final note: I am aware that my PC I test on is terrible for realistically profiling something meant to run on a chromebook, however it’s just a project I work on in my free time it’s not something I work on professionally, therefore I have no intention of actually going out and spending potentially hundreds of dollars on a test device I’m never even gonna use otherwise. Thank you for responding. |
I recommend to load them as separate files, but prior to the main game logic. So at the moment the game logic begins, all resources are ready and can be read synchronously. Rationale: when you update your game, you make your user to download all resources again.
No, it does not. C backend shares same architecture as Wasm BE, so both suffer from inability to properly support coroutines.
Don't be so naive. I wish you good luck even with compiling simple Hello, world app using this pipeline. If this was a working solution, I never developed Wasm BE at the first place.
No, it does not work this way. So please give up. All you can do is to wait and hope that eventually I support asyncs in Wasm GC BE. |
I've already thought of synchronous ways to do everything that's currently |
No, I want to help you. But please note that many years ago I spent a lot of time and effort to make asyncs work in JavaScript. Right now I don't have this amount of time. Also, I don't want to maintain classical Wasm BE, because Wasm GC is more appropriate target. While classical Wasm binary is 5 times bigger than JS for the same codebase, Wasm GC BE produces binary of comparable size to JS. Also, it's a bit faster (because there's no overhead on supporting shadow stack) and it's possible to support entire JSO (except for passing arrays by reference) with it. So why would I invest my time into classical Wasm? I'm determined to support all the features of JS in Wasm GC BE, including reflection and asyncs, but it still requires time. Eventually you'll be able to convert your project into wasm without any effort. But please note that there no way to work around this. You either have the compiler that can turn you code into state machine to be able to save/restore its state, or just proceed with callbacks. No Wasm or WASI features or some sort of magic can help. So the only way I can help you is to implement this in TeaVM, but I can't do it just in couple days.
There's no such thing as "WASI filesystem" in the browser. WASI is intended to interact directly with OS from headless Wasm environments. There's WASI polyfill for the browser, but it still relies on browser APIs. I don't know how exactly WASI polyfill for filesystem works, but to be synchronous it has to rely on keeping entire FS in memory (and perhaps syncing with IndexedDB on the background). Nothing magical, everything you could do yourself. |
Yeah, but what you can do here is ask your users. Perhaps there are some who agree to test two different versions and report FPS change. For example, one is with this fix regarding |
Sorry let me rephrase, what I mean is you obviously don't want to spend time attempting to get my app working on the WASM BE in its current state, and that the WASM BE also is in the middle of being rewritten, so I won't bother you with any more questions on how to get it working until sometime in the distant future when WASM GC is complete and has async support.
I see, when I first read up on it I was under the impression that WASI was also supported by browsers for the purpose of eliminating all the boilerplate JavaScript code, since that was the only way I saw it even being a useful thing in the first place for most people who write apps that run in WASM.
Okay thanks for the advice, I might settle for a localized version of this this but I'm also thinking if there's just some "green" way to sit in an infinite loop in WASM without taking 100% of an underlying CPU core from the OS that I could perform asynchronous filesystem tasks in a worker and just watch the contents of a SharedArrayBuffer from the client side in order to exchange blocks with the worker or wait while any asynchronous IO tasks complete. Whatever I end up doing though will be done in a way that I can transition to using |
Is there any reason I can't use asyncify? It looks like it's able to be injected into an already fully compiled WASM binary. I don't need the ability to create multiple TeaVM threads to switch between, I just need to be able to save the current state of the WASM while I execute some async JavaScript then have the WASM resume from where it was saved. https://kripken.github.io/blog/wasm/2019/07/16/asyncify.html |
I've now completely eliminated all |
Ahaha, our project is somewhere between 0.5M and 1M LOC of code, with around 100K classes, resulting in 16M of minified JS, and everything works just fine. You can try to find async method using following technique. Run TeaVM in debugger and set breakpoint on |
I meant the WASM backend, you're not saying you can 100K classes with the WASM backend, right? The JS backend has worked great for me, I have absolutely no complaints, I'm wondering what amount of code the WASM backend has been proven to be able to compile. |
Ah, you meant that. I just compile tests (some of which are relatively big, but still far from 4000K classes) and couple of small demos, so I'm really not sure if it's able to compile big projects. However, size does not really matter here if you use poorly supported features like asyncs. Coroutine transformation won't do anything if you don't have any async methods. So this particular error means that you still have some. |
Okay, thanks, I will keep that in mind.
I'll spend a few more hours hunting for asyncs tomorrow, I'm pretty confident I've completely eliminated them from my source folders so I'll start looking into what other TeaVM classlib functions could potentially be indirectly calling |
Please, use the trick I proposed. It would only take few minutes to clone TeaVM sources and set up remote debugger, but then you save hours. |
The issue is I physically cannot compile my (still WIP) WASM runtime for my app to the JS backend, it's made specifically for WASM and will not work in JS. I went all out with the Address class. In order to use the debugger I would have to take my app's JS runtime and breakpoint that instead and try to make assumptions from it about what is making it into the WASM builds. |
Sorry, I still don't understand how it's related to debugging TeaVM. I didn't propose you to compile anything to JS |
I misunderstood what you meant sorry, 3am moment, I thought you said compile it to JS and breakpoint the JS. I think what you actually meant was breakpoint the compiler while compiling it to WASM, which I'll do tomorrow. |
It was |
I have not tested it in browser yet but
|
And I do fully understand I will not be receiving support for any issues that asyncify causes on the legacy WASM backend, I'm just reporting my findings. |
Sure, I won't fix these issues immediately or help you directly with your project. However, I can help with some hints. For example, recent TeaVM has it own secret disassembly. Clone and in |
I hope disassembler could help to identify the reason of this issue. It might be issue in asyncify or in TeaVM. And if it's issue in TeaVM, perhaps I can fix it. However, I still don't fully understand why you need this asyncify. You successfully eliminated all async calls, right? So why don't you just proceed this this? FYI, async transformation not only increases binary size, but kills performance as well. BTW, if you want to increase JS performance, you can try to reduce number of direct or indirect calls to asyncs. For example, you can remove any |
Okay thanks, I will try that.
I thought I could eliminate all async calls if I used the WASI filesystem, but that was before I was aware that WASI isn't supported in browsers. I thought WASI was supported in browsers to help eliminate all the boilerplate JavaScript, but I know that's wrong now. I've returned to the approach of having my own "promise" synchronization primitive that I can implement with asyncify and then eventually convert to using
One of my rules is to completely avoid threads and async as much as possible, generally only a single async call happens per frame and it's for |
I managed to hunt down the source of the offending function, wasm-opt explodes on this line in the compiled WASM image
It crashes on the end of the catch block, as in the last instructions to successfully decode before it crashes appears to be a break (
|
This fragment gives absolutely nothing. Could you post the entire function? Is it somewhere inside your code, or it's Java library code? If it's former, you can use any appropriate tool to strip |
I'm sorry. I included the link to the source, its my code, the disassembly of this functions is several thousand lines so I've uploaded it here with the comment if you really feel like getting a migraine. I realized it calls a function that uses certain reflection that isn't supported in WASM (yet), so I'm eliminating that before I keep trying to make it work. |
No-no-no, this fragment is totally fine. When I claim that Wasm BE does not support reflection, I mean advanced reflection like |
I don't mean the fragment I posted, I mean another function called by this function. I know that getSimpleName is well supported. |
There were errors printed about a few other functions that I didn't notice because it was late at night and I was compiling it with |
I've eliminated the unsupported reflection ( |
@konsoletyper Google Chrome also says the WASM image is invalid when I parse it using
I think I'm just gonna wait until WASM GC is available before I try to compile my app to WASM with TeaVM. I don't think its worth distracting you with issues in the legacy WASM backend when WASM GC will make it irrelevant anyway. Thank you for helping me with this, I will definitely be back once WASM GC is ready. |
I figured out the correct way to implement asyncs, I just had to stick to my delusions of finding a way to block the WASM without taking 100% of an underlying CPU core. If I just run the entire app in a worker I can use |
I think you should definitely report this. Reimplementing asyncs would take weeks, which I don't have, but there's a chance that this particular issue is easy to fix. There's also a chance that it's inside some code that's shared between Wasm and Wasm GC, so it will help me anyway. So, does it reproduce without FULL (AGGRESSIVE) optimization? Can you turn off optimization and check if issue persists? Can you try fresh TeaVM from
Yes, but please note that you can't perform any WebGL rendering from within worker, as well as getting any input events. But I still don't understand your problem. Do you need a game loop like: while (true) {
takeInput();
performLogic();
renderFrame();
waitALittle();
} Then you just don't do it this way in the browser. Instead, you do something like this: function gameLoop() {
takeInput();
performLogic();
renderFrame();
requestAnimationFrame(() => gameLoop());
} |
It will only compile on BALANCED, the others crash and don't produce any WASM. I was gonna try again once WASM GC is done and complain then if it also happens on the latest instead of the legacy WASM backend.
I'm very well aware of this and my plan is to put the WebGL (and audio, and returning input events) all in a queue based on a SharedArrayBuffer to be handled by the page instead of the worker.
Congratulations
One of the main goals of my project is to create a runtime environment modeled after LWJGL that can be compiled to run in both desktop and browser. Several people use my codebase to port other versions of the same game to run in a browser and by changing the app to need to swap buffers this way instead of having the ability to do it on demand without completing the current loop would require the codebases of the games to be fundamentally restructured in order to work right (trust me I did it last week before realizing it's more beneficial to people to find a way not to). By doing things synchronously and blocking the thread while async functions complete, suddenly ANYTHING that is built on my current codebase can then run in WASM without fundamentally restructuring. I think it's completely worth the pain of inventing this SharedArrayBuffer queue if it means that EVERYONE will get WASM instead of just those who want to waste their time rewriting all the synchronous loading screens and chopping their main loops up. |
If you would like me to email you a copy of the gradle project that caused this I can, however please be aware that it is for the legacy WASM backend and from when I was still planning to use asyncify. There aren't any asyncs in the java portions that I know of. I don't have the JavaScript imports to go with it finished either and in light of deciding to invent this "queue" idea with the SharedArrayBuffer I probably won't be using most of this code. The queue-based system will be WASM GC for Java, but the queue encoder/renderer and atomics will be separate and use conventional WASM instead and not compiled with TeaVM (probably written in C and compiled with Emscripten). I will not have any asyncs in the Java code in hope that the backend won't apply async transformations to the code for better performance, so that feature will not be a requirement for me for now in WASM GC. |
@konsoletyper I just wanted to let you know that JSPI exists, on chrome it still appears to be locked behind a |
@lax1dude I know about this proposal, but this has nothing to do with async transformation. The relevant proposal is called "stack switching". |
What I mean is JSPI can be used instead of async transformation for imported functions that deal with promises. I know that the JSPI proposal is probably not fit for creating green threads, but for my specific app I am not using multiple threads anyway and I only need the ability to suspend/resume the app's main thread while some promises complete. |
@lax1dude nope, it does not work this way. JS promise integration is completely irrelevant for your case |
How is it irrelevant, one of the main problems we've discussed is that my app loads resources and uses IndexedDB in the main loop using async methods, I could implement those portions of my code in JavaScript instead using functions that return a Promise, and complete the promise with the loaded resource or whatever the result is of the IndexedDB operation. |
@lax1dude sure, but how are you going to transform a synchronous-style methods into a method that stops its execution and instead provides a callback that continues execution? This is what coroutine transformation for. The proposal you are referring is completely unrelated |
Because I’m not gonna implement these async portions in Java, I’m gonna implement them in a JavaScript file, as imported functions that return Promises. I’m not as dependent on |
I think you misunderstood what I was trying to say last week when I said that "JSPI eliminates the need for async transformation", I meant purely in the context of my specific app that JSPI can eliminate all my needs for async transformation, I didn't mean to say that you could be using JSPI in TeaVM to add support for the existing |
TeaVM Version: 0.10.x branch
Backend: WASM
I'm compiling a very large program (about 3000 classes) and the compiler is crashing due to some internal issue, I have no idea what to do about it because it doesn't appear to be caused by any code that I wrote.
At first, there was no way to tell at all what was causing the crash, so I cloned the 0.10.x source and added a print to
optimizeMethodCacheMiss
to print out the name of the method that was crashing it. It turns out the name of the method that causes it to throw the exception isorg.teavm.backend.wasm.runtime.WasmSupport.initClasses()V
.That function completely empty in the WasmSupport.java source file so I assume its generated by the compiler.
I am not using the JSO in my program, I have taken the time to completely rework all parts that used to use the JSO to instead use
@Import
and@Export
functions that take only numbers and memory addresses. If you would like the source code of the project to try compiling yourself I can email it to you.The text was updated successfully, but these errors were encountered: