Skip to content

Wasm-friendly implementation of varargs. #5215

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 1 commit into
base: main
Choose a base branch
from

Conversation

sjrd
Copy link
Member

@sjrd sjrd commented Jul 26, 2025

For JavaScript, using Scala arrays wrapped in WrappedArray (2.12) or ArraySeq (2.13) is not ideal for performance. Instead, since about forever, we have re-emitted them as JS arrays wrapped in our own js.WrappedArray/WrappedVarArgs. This, however, has poor performance on Wasm.

We now call generic methods to choose the best implementation of a varargs Seq, depending on the platform. They take Scala arrays, and "convert" them to JS arrays if necessary. The conversions are intrinsified so that they are zero-cost.

For this to work, we add an InlineArrayReplacement in the optimizer, similar to the existing InlineJSArrayReplacement.

@sjrd
Copy link
Member Author

sjrd commented Jul 26, 2025

@tanishiking Could you check that this PR achieves the same performance improvements on Wasm that you have measured before?

For JS, the resulting code is basically not affected. (there are more intermediate variables in non-minify, but they are removed in minify mode)

@tanishiking
Copy link
Contributor

Sure! It looks this makes Wasm varargs faster without de-optimizing JS 👍

fastopt 1.19.0 JS 1.20.0-SNAPSHOT JS 1.19.0 Wasm 1.20.0-SNAPSHOT Wasm
Varargs.Call_0_Args 540.86 558.65 1444.62 1495.60
Varargs.Call_1_Arg 2081.17 2085.40 6805.43 3861.56
Varargs.Call_5_Args 3189.43 3115.38 13857.60 6174.55

For JavaScript, using Scala arrays wrapped in `WrappedArray` (2.12)
or `ArraySeq` (2.13) is not ideal for performance. Instead, since
about forever, we have re-emitted them as JS arrays wrapped in our
own `js.WrappedArray`/`WrappedVarArgs`. This, however, has poor
performance on Wasm.

We now call generic methods to choose the best implementation of
a varargs Seq, depending on the platform. They take Scala arrays,
and "convert" them to JS arrays if necessary. The conversions are
intrinsified so that they are zero-cost.

For this to work, we add an `InlineArrayReplacement` in the
optimizer, similar to the existing `InlineJSArrayReplacement`.

These changes revert the `toString()` of varargs seqs to what it
is on the JVM, but only on Wasm. This breaks three partests, for
which we had checkfiles with the Scala.js-specific strings. We
extend our `partest` fork to recognize platform-specific
checkfiles in order to fix this issue.
@sjrd sjrd force-pushed the wasm-friendly-varargs branch from 8c8cb84 to e7fb94b Compare July 28, 2025 12:45
@sjrd sjrd requested a review from gzm0 July 28, 2025 22:06
Copy link
Contributor

@tanishiking tanishiking left a comment

Choose a reason for hiding this comment

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

I left one question, otherwise it looks good from my side :).

result
}

@noinline def genericArrayToJSArray[T](array: Array[T]): js.Array[T] =
Copy link
Contributor

Choose a reason for hiding this comment

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

[note]
These scala.Array to js.Array conversions are optimized to JSArrayConstr, so there shouldn't be runtime js.Array construct in optimized pass 👍

case ArrayToJSArray =>
val tarray = targs.head
tarray match {
case PreTransLocalDef(LocalDef(_, /* mutable = */ false, replacement: InlineArrayReplacement)) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Just out of curiosity, does't it works to just replace xxxArrayToJSArray(ArrayValue(...)) with JSArrayConstr(...), in this specific case?
It won't work with the following case without Replacement, and can't optimize other Array related operations though.

val vararg = Array(1,2,3)
foo(varar: _*)

def foo(x: Int*) = ...

Anyway, InlineArrayReplacement looks good 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

No, that wouldn't really work in our optimizer architecture. We would have to identify a method target before pretransforming its arguments, and that goes against some other decisions that we make (like deciding to inline because of properties of the pretransformed arguments).

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.

2 participants