Skip to content

Kotlin Compose Multiplatform WasmGC workload #84

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

danleh
Copy link
Contributor

@danleh danleh commented Jul 8, 2025

In short, this is a shell-compatible benchmark containing several subitems from the Kotlin/Wasm Compose Multiplatform benchmarks at https://github.com/JetBrains/compose-multiplatform/blob/master/benchmarks/multiplatform/README.md. Compose Multiplatform is a Kotlin UI framework with support for mobile, desktop, and Web. Similar to the Dart/Flutter workload, the actual rendering is stubbed out, and there are two components: the application and layout logic is compiled to WasmGC, whereas low-level drawing is done by skiko, a Wasm linear build of Skia. @eymar from JetBrains would be the expert, in case more background and details on the benchmark are needed.

In terms of measurements: On an x64 workstation, running path/to/d8 cli.js -- Kotlin-compose-wasm in a ToT d8 takes about 12s with 15 iterations. In the CPU profile, we see ~11% of the cycles spent on Wasm compilation and ~30% in the GC. Around ~7400 functions are compiled lazily with our baseline tier (Liftoff) and ~1400 tiered up to TurboFan, and in the profile the hottest JIT-ed function takes only ~1.5% of the CPU cycles, so I think it's quite a good example of a "large" workload with lots of code exercised. All in all, sounds like it reflects real-world usage (@eymar, please correct me).

Copy link

netlify bot commented Jul 8, 2025

Deploy Preview for webkit-jetstream-preview ready!

Name Link
🔨 Latest commit b914739
🔍 Latest deploy log https://app.netlify.com/projects/webkit-jetstream-preview/deploys/689606387eb8b300087a95b6
😎 Deploy Preview https://deploy-preview-84--webkit-jetstream-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@danleh
Copy link
Contributor Author

danleh commented Jul 8, 2025

One issue for the shell version right now is that SpiderMonkey's js shell doesn't implement setTimeout(). I didn't want to polyfill that in userspace/JavaScript with Promise.resolve().then(f) to not introduce any difference between engines. @iainireland I remember we spoke about this some time ago, would it be possible to implement that?

@danleh
Copy link
Contributor Author

danleh commented Jul 8, 2025

Also right now, this is built with the current exception handling proposal using exnref and try_table, not the legacy one. This seems to be the reason why it's not running in Safari right now (even though it does run with in a local build of jsc shell) @kmiller68.

AFAICT no exceptions are thrown and there is no effect on the CPU profile or binary size either way, so I would be also happy to switch to the legacy EH for now, and recompile later once support for exnref lands in Safari (after the workload freeze, but before a JetStream release).

@kmiller68
Copy link
Contributor

Also right now, this is built with the current exception handling proposal using exnref and try_table, not the legacy one. This seems to be the reason why it's not running in Safari right now (even though it does run with in a local build of jsc shell) @kmiller68.

I think try_table shipped in Safari 18.4 (May). Although there were bugs in unreachable try_tables that we only fixed recently so maybe it's hitting that?

@danleh
Copy link
Contributor Author

danleh commented Jul 8, 2025

Mhm, I rebuilt after updating the upstream sources and now it works on Safari, maybe I messed up the Gradle build config earlier. In any case, seems like new EH is not a problem :)

@eqrion
Copy link

eqrion commented Jul 11, 2025

I'm working on getting setTimeout into the SM shell here. I'll probably land it sometime next week. I don't think that's a hard blocker here though, because it'll still work in the Web.

@eqrion
Copy link

eqrion commented Jul 17, 2025

@danleh

setTimeout should now be in the latest SM shell nightly.

the actual rendering is stubbed out

Just to clarify, I still see skiko.wasm in the profiles here. Is it still doing rendering to an internal image that just is not displayed?

@eymar
Copy link

eymar commented Jul 18, 2025

Hey :)

Just to clarify, I still see skiko.wasm in the profiles here. Is it still doing rendering to an internal image that just is not displayed?

In shell environment there is no real render target. So there is no real rendering. The "higher level" skiko functions are still called and can be seen in the profiles. But since the render target is dumb, there won't be any rendering commands.

@danleh danleh requested review from kmiller68 and camillobruni July 30, 2025 15:48
@danleh
Copy link
Contributor Author

danleh commented Jul 30, 2025

setTimeout should now be in the latest SM shell nightly.

Nice, thanks! Works in SM shell as well now :)

I'd still be happy to merge, feel free to review/comment.

danleh added 3 commits August 6, 2025 12:56
...to fix eager stack trace creation and wrong FinalizationRegistry.register usage.
@danleh
Copy link
Contributor Author

danleh commented Aug 7, 2025

With the updated Kotlin/Wasm toolchain, I am getting some JsException errors in jsc shell (and just jsc, not in V8 or SpiderMonkey):

$ jsc cli.js -- Kotlin-compose-wasm
Starting JetStream3
Running Kotlin-compose-wasm:
Error: JsException: Exception was thrown while running JavaScript code
Error: JsException: Exception was thrown while running JavaScript code
Error: JsException: Exception was thrown while running JavaScript code
...

@kmiller68 Is there some convenient way / jsc flag to dump a full stack trace whenever an exception is thrown (caught or not)? Or abort on the first exception?

CC @bashor Possibly the new Kotlin toolchain imports some new JavaScript code that throws (for whatever reason, maybe a missing polyfill?)

@bashor
Copy link

bashor commented Aug 7, 2025

With the updated Kotlin/Wasm toolchain, I am getting some JsException errors in jsc shell (and just jsc, not in V8 or SpiderMonkey):

$ jsc cli.js -- Kotlin-compose-wasm
Starting JetStream3
Running Kotlin-compose-wasm:
Error: JsException: Exception was thrown while running JavaScript code
Error: JsException: Exception was thrown while running JavaScript code
Error: JsException: Exception was thrown while running JavaScript code
...

@kmiller68 Is there some convenient way / jsc flag to dump a full stack trace whenever an exception is thrown (caught or not)? Or abort on the first exception?

CC @bashor Possibly the new Kotlin toolchain imports some new JavaScript code that throws (for whatever reason, maybe a missing polyfill?)

@danleh @kmiller68
I haven't managed to catch all details yet, but it looks like there is some issue with WebAssembly.JSTag in jsc. In the new Kotlin toolchain, JSTag is used for all exceptions when it's available at runtime.

@kmiller68
Copy link
Contributor

kmiller68 commented Aug 8, 2025

@kmiller68 Is there some convenient way / jsc flag to dump a full stack trace whenever an exception is thrown (caught or not)? Or abort on the first exception?

Unfortunately, no, not really, I tried looking for uncaught exceptions in the inspector but it appears that they seem to happen repeatedly (both in Safari but also Chrome). This makes it hard to log to anything that even makes it to the inspector, unfortunately. It appears they're thrown from kotlinx.coroutines.channels.$receiveCOROUTINE$.doResume.

JsException makes me think this is coming from Kotlin itself as JsException with that casing doesn't appear in JSC at all and it's in Kotlin's.

I haven't managed to catch all details yet, but it looks like there is some issue with WebAssembly.JSTag in jsc. In the new Kotlin toolchain, JSTag is used for all exceptions when it's available at runtime.

It's possible there's a bug in our JSTag implementation but I wouldn't rule out an issue somewhere else propagating to an uncaught exception inside Kotlin itself. I also tried turning off all our wasm JIT tiers and the issue didn't go away, so at least it doesn't appear to be a JIT bug or anything.

@bashor
Copy link

bashor commented Aug 8, 2025

@danleh, as a workaround, now you can comment/remove WebAssembly.JSTag ?? here. I think it should affect how these benchmarks work.

@danleh
Copy link
Contributor Author

danleh commented Aug 8, 2025

I tried looking for uncaught exceptions in the inspector but it appears that they seem to happen repeatedly (both in Safari but also Chrome). This makes it hard to log to anything that even makes it to the inspector, unfortunately. It appears they're thrown from kotlinx.coroutines.channels.$receiveCOROUTINE$.doResume.

I think repeated uncaught exceptions are expected, since Kotlin/Wasm uses CancellationExceptions for aborting coroutines, right @bashor?

@danleh, as a workaround, now you can comment/remove WebAssembly.JSTag ?? here. I think it should affect how these benchmarks work.

Thanks, const wasmTag = new WebAssembly.Tag({ parameters: ['externref'] }); works without errors in JSC as well. CC @kmiller68

@bashor
Copy link

bashor commented Aug 8, 2025

@kmiller68 so far I found 2 (~3) issues and filed 297134 and 297126.

@danleh I'm going to work around the problem on our side, so a fix will be available in 2.2.20-RC in a week or so, but I think you can try it with a dev build earlier.
The main difference from the workaround above is that all exceptions will be thrown using a JS helper (when JSTag is available).

@kmiller68
Copy link
Contributor

WebKit/WebKit#49130 Should fix the issue.

@Constellation
Copy link
Member

It may take a bit of time to be available on Safari, so please keep the workaround for now ;) Once we shipped the fix, let you know about that and let's remove it.

@danleh
Copy link
Contributor Author

danleh commented Aug 11, 2025

Let's merge this because I don't think there is anything actionable for us at this point.

After the WebKit fixes are merged and released in Safari, we can drop the hotfix here and update the Kotlin toolchain to stable 2.2.20 (which we should do anyway, once it's out in September).

Does that sound good @kmiller68 @eqrion?

@kmiller68
Copy link
Contributor

That sounds reasonable, although I'd like to take another look after uploading the hotfix here.

@danleh
Copy link
Contributor Author

danleh commented Aug 12, 2025

The hotfix is already included in the last commit, see https://github.com/WebKit/JetStream/blob/b91473929f5c7f0f742fdbcba49a13891b941e4b/Kotlin-compose/jstag-workaround.patch (essentially what Zalim proposed).

@eqrion
Copy link

eqrion commented Aug 12, 2025

Sounds good to me, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants