Skip to content

SKIKO-1159: introduce wasm-opt build step - #1247

Open
Dustin Feucht (nopjar) wants to merge 12 commits into
masterfrom
feat/add-optimize-wasm-step
Open

SKIKO-1159: introduce wasm-opt build step#1247
Dustin Feucht (nopjar) wants to merge 12 commits into
masterfrom
feat/add-optimize-wasm-step

Conversation

@nopjar

Copy link
Copy Markdown
Member

This PR introduces a separate optimization step for wasm target.

It is a cleaner approach than the one included in #1242.

Emscripten basically does the same, but with -O2 instead of -Oz, which results in a size reduction of ~200KB (9 MB -> 8.8 MB).

Ticket can be found here.

@nopjar
Dustin Feucht (nopjar) force-pushed the feat/add-optimize-wasm-step branch from 7d463c7 to cee1838 Compare July 29, 2026 11:06

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the comments left, there's one fundamental thing this PR should take care of so that we won't harm workflow for our fellow skiko developers.

the EMSDK_DIR is a good direction - let's discuss whether it should be gradle property rather then environment variable (Ivan Matkov (@MatkovIvan) what is your though on this) - however ideologically its the right thing to do.

However what will be broken is that for people who don't have this param set but already been relying on the existence of emsdk/upstream (to which the which emcc is resolved).

Can you please, add following logic:

  1. if EMSDK_DIR is present (again, later on may be we'll switch to a property) then both emcc and wasm-opt are resolved relying on it

  2. if it is not present, then we fallback to emcc and if that emcc is actually in the upstream folder of the emsdk then we resolve wasm-opt accordingly

  3. otherwise we fall back to wasm-opt available globally

Comment thread skiko/buildSrc/src/main/kotlin/tasks/configuration/WasmTasksConfiguration.kt Outdated
Comment thread skiko/buildSrc/src/main/kotlin/tasks/configuration/WasmTasksConfiguration.kt Outdated
if (logger.isInfoEnabled || argFile == null) {
val argsString = args.toArray().joinToString(", ", prefix = "[", postfix = "]")
logger.info("$prefix: $argsString")
val logLevel = if (logger.isInfoEnabled) LogLevel.INFO else LogLevel.WARN

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this changes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've oriented myself on the LinkSkikoTask.kt and the CompileSkikoCppTask.kt. Both store their arguments for the emcc command in a text file and call it with @path/to/args.txt. That syntax is not support by wasm-opt. So we need to pass the arguments directly. But that method requires an arg-file. So I updated the method to also accept no arg-file while the result is unchanged and still respects log-levels like before.

@Schahen

Copy link
Copy Markdown
Contributor

Another thing that make sense to do in the framework of this PR is to make sure that emcc step is not invoking wasm-opt by itself.

@nopjar

Copy link
Copy Markdown
Member Author

Another thing that make sense to do in the framework of this PR is to make sure that emcc step is not invoking wasm-opt by itself.

I just did a few experiments and I couldn't find a way to just disable the wasm-opt optimization without having to sacrifice other optimizations that emcc currently does.

@nopjar
Dustin Feucht (nopjar) force-pushed the feat/add-optimize-wasm-step branch from 2c8eeeb to 3532125 Compare July 30, 2026 09:55
@Schahen

Copy link
Copy Markdown
Contributor

Dustin Feucht (@nopjar) are you comparing with the +wasm-opt scenario?
in theory switching off the emcc-side wasm-opt invocation should not affect negatively artefact size if we still have ours.

can you elaborate on that?

@nopjar

Copy link
Copy Markdown
Member Author

Yes, passing -O1 instead of -O2 will result in a optimized wasm which is roughly 100 KB bigger. The 'unoptimized' is even 700 KB bigger.

If I understand it correctly, it passes the same arg to clang and wasm-opt. We can't therefore only omit one.
WebAssembly/binaryen#5598

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a dedicated wasm-opt optimization step in the wasm build pipeline so the produced .wasm is post-processed separately from the link step, aiming to reduce binary size (e.g., using -Oz).

Changes:

  • Added a new Gradle task (OptimizeSkikoWasmTask) and wired it into the wasm build so the jar packages the optimized .wasm.
  • Implemented wasm-opt discovery logic (EMSDK-based and PATH-based) for the optimization step.
  • Adjusted native tool argument logging behavior to support tasks that don’t use an args file.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
skiko/buildSrc/src/main/kotlin/tasks/configuration/WasmTasksConfiguration.kt Registers and wires an optimization task into the wasm build/jar pipeline; adds wasm-opt discovery logic.
skiko/buildSrc/src/main/kotlin/OptimizeSkikoWasmTask.kt New Gradle task that runs wasm-opt to optimize a produced wasm artifact.
skiko/buildSrc/src/main/kotlin/AbstractSkikoNativeToolTask.kt Updates argument logging to handle calls without an args file and adjusts log level behavior.
Comments suppressed due to low confidence (1)

skiko/buildSrc/src/main/kotlin/OptimizeSkikoWasmTask.kt:40

  • After switching inputFile to a RegularFileProperty, this should pass the input file path to wasm-opt (instead of the RegularFile object).
            arg(inputFile.get())

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread skiko/buildSrc/src/main/kotlin/tasks/configuration/WasmTasksConfiguration.kt Outdated
Comment on lines +106 to +110
protected fun logArgs(prefix: String, args: ArgBuilder, argFile: File? = null) {
if (logger.isInfoEnabled || argFile == null) {
val argsString = args.toArray().joinToString(", ", prefix = "[", postfix = "]")
logger.info("$prefix: $argsString")
val logLevel = if (logger.isInfoEnabled) LogLevel.INFO else LogLevel.WARN
logger.log(logLevel, "$prefix: $argsString")
Comment thread skiko/buildSrc/src/main/kotlin/OptimizeSkikoWasmTask.kt Outdated
Comment thread skiko/buildSrc/src/main/kotlin/tasks/configuration/WasmTasksConfiguration.kt Outdated
…tFileName property

With having an explicit `@OutputFile` we end up with the same output being declared multiple times as `outDir` is also declared with `@OutputDir`. This will result in having the same artifact (`skiko.wasm`) multiple times present.

But as the whole directory is marked as an output, it should not interfere with gradle.
…sing

Refactored to process all files in the input directory, supporting `.wasm` optimization and `.mjs` file copying. Updated task configuration to use `DirectoryProperty` for input and adjusted output file naming conventions for better clarity.
Simplified wasm-opt path resolution to handle OS-specific executable names and better fallback handling. Removed unused imports and redundant code for efficiency.
Updated task dependencies to use `optimizeWasm` instead of `linkWasm` for improved Wasm build optimizations. Introduced `optimizeWasmD8` task and refactored related task logic for better modularity and consistency.

Reintroducing the buildSuffix as we now have two different optimize folders.
Refactored .mjs file processing to update references to optimized .wasm filenames, ensuring correct linkage with emcc-generated outputs. Removed unused `Files` import.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any strong subjections anymore

Corrected environment variable name for wasm-opt path resolution to align with the standard EMSDK setup.

flags.addAll(
listOf(
"-Oz", // set optimization level to compress (highest size reduction)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since -Oz optimizes size, it might affect the performance (comparing with -O2).
We have Compose benchmarks - https://github.com/JetBrains/compose-multiplatform/tree/master/benchmarks/multiplatform
Have a look at different modes in README - https://github.com/JetBrains/compose-multiplatform/tree/master/benchmarks/multiplatform#benchmark-modes
Checking with SIMPLE and REAL should be enough.

Before leaving it -Oz it's worth to verify O2 vs Oz on Compose benchmarks. Could you please check it?

Alternatively, to move forward with the PR sooner than later, we can set O2 here for now and then compare O2 vs Oz on the benchmarks + change to Oz in a new PR.

What do you prefer?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After running some benchmarks with different cases, the conclusion is that with -Oz we do not have a regression overall. Neither compared to the current master nor with just passing -O2 again.

This table illustrates it to some extend. Also one needs to keep in mind that of course some variation in the timings is possible as various external factors influence the execution. Therefore, there were also runs where -Oz performed better and worse than master in almost all categories. But it has been always just a few percent. So I'd say that there is almost no impact (if at all) to performance.

Benchmark -Oz (ms) -O2 (ms) master (ms)
AnimatedVisibility 0.090 0.091 0.090
CanvasDrawing 9.524 9.621 9.552
HeavyShader 1.612 1.626 1.597
LazyGrid 4.695 4.635 4.642
LazyGrid-ItemLaunchedEffect 4.660 4.602 4.689
LazyGrid-SmoothScroll 0.448 0.443 0.457
LazyGrid-SmoothScroll-ItemLaunchedEffect 0.463 0.463 0.467
LazyList 0.124 0.124 0.124
MultipleComponents 0.179 0.179 0.182
MultipleComponents-NoVectorGraphics 0.146 0.145 0.147
TextLayout 41.874 41.532 41.423
VisualEffects 1.034 1.024 1.011

So if nothing from your side speak against it, I would go with the -Oz step as it also reduces size.

@Schahen Shagen Ogandzhanian (Schahen) changed the title feat SKIKO-1159: introduce wasm-opt build step SKIKO-1159: introduce wasm-opt build step Aug 4, 2026
Updated Wasm task configuration to skip stripping debug info when profiling is enabled, ensuring compatibility with profiling workflows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants