diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 143e6f8665..7f59879463 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -5914,10 +5914,26 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ for ((arg, wasRepeated) <- args.zipAll(wereRepeated, EmptyTree, false)) yield { if (wasRepeated) { - tryGenRepeatedParamAsJSArray(arg, handleNil = false).fold { - genExpr(arg) - } { genArgs => - genJSArrayToVarArgs(js.JSArrayConstr(genArgs)) + /* If the argument is a call to the compiler's chosen `wrapArray` + * method with an array literal as argument, we know it actually + * came from expanded varargs. In that case, rewrite to calling our + * custom `scala.scalajs.runtime.to*VarArgs` method. These methods + * choose the best implementation of varargs depending on the + * target platform. + */ + arg match { + case MaybeAsInstanceOf(wrapArray @ WrapArray( + MaybeAsInstanceOf(arrayValue: ArrayValue))) => + implicit val pos = wrapArray.pos + js.Apply( + js.ApplyFlags.empty, + genLoadModule(RuntimePackageModule), + js.MethodIdent(WrapArray.wrapArraySymToToVarArgsName(wrapArray.symbol)), + List(genExpr(arrayValue)) + )(jstpe.ClassType(encodeClassName(SeqClass), nullable = true)) + + case _ => + genExpr(arg) } } else { genExpr(arg) @@ -6069,27 +6085,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * Otherwise, it returns a JSSpread with the Seq converted to a js.Array. */ private def genPrimitiveJSRepeatedParam(arg: Tree): List[js.TreeOrJSSpread] = { - tryGenRepeatedParamAsJSArray(arg, handleNil = true) getOrElse { - /* Fall back to calling runtime.toJSVarArgs to perform the conversion - * to js.Array, then wrap in a Spread operator. - */ - implicit val pos = arg.pos - val jsArrayArg = genApplyMethod( - genLoadModule(RuntimePackageModule), - Runtime_toJSVarArgs, - List(genExpr(arg))) - List(js.JSSpread(jsArrayArg)) - } - } - - /** Try and expand a repeated param (xs: T*) at compile-time. - * This method recognizes the shapes of tree generated by the desugaring - * of repeated params in Scala, and expands them. - * If `arg` does not have the shape of a generated repeated param, this - * method returns `None`. - */ - private def tryGenRepeatedParamAsJSArray(arg: Tree, - handleNil: Boolean): Option[List[js.Tree]] = { implicit val pos = arg.pos // Given a method `def foo(args: T*)` @@ -6101,15 +6096,22 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * the type before erasure. */ val elemTpe = tpt.tpe - Some(elems.map(e => ensureBoxed(genExpr(e), elemTpe))) + elems.map(e => ensureBoxed(genExpr(e), elemTpe)) // foo() - case Select(_, _) if handleNil && arg.symbol == NilModule => - Some(Nil) + case Select(_, _) if arg.symbol == NilModule => + Nil // foo(argSeq:_*) - cannot be optimized case _ => - None + /* Fall back to calling runtime.toJSVarArgs to perform the conversion + * to js.Array, then wrap in a Spread operator. + */ + val jsArrayArg = genApplyMethod( + genLoadModule(RuntimePackageModule), + Runtime_toJSVarArgs, + List(genExpr(arg))) + List(js.JSSpread(jsArrayArg)) } } @@ -6137,25 +6139,36 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def isClassTagBasedWrapArrayMethod(sym: Symbol): Boolean = sym == wrapRefArrayMethod || sym == genericWrapArrayMethod - private val isWrapArray: Set[Symbol] = { - Seq( - nme.wrapRefArray, - nme.wrapByteArray, - nme.wrapShortArray, - nme.wrapCharArray, - nme.wrapIntArray, - nme.wrapLongArray, - nme.wrapFloatArray, - nme.wrapDoubleArray, - nme.wrapBooleanArray, - nme.wrapUnitArray, - nme.genericWrapArray - ).map(getMemberMethod(wrapArrayModule, _)).toSet + val wrapArraySymToToVarArgsName: Map[Symbol, MethodName] = { + val SeqClassRef = jstpe.ClassRef(encodeClassName(SeqClass)) + + def make(simpleName: String, argTypeRef: jstpe.TypeRef): MethodName = + MethodName(simpleName, argTypeRef :: Nil, SeqClassRef) + + val items: Seq[(Name, String, jstpe.TypeRef)] = Seq( + (nme.genericWrapArray, "toGenericVarArgs", jswkn.ObjectRef), + (nme.wrapRefArray, "toRefVarArgs", jstpe.ArrayTypeRef(jswkn.ObjectRef, 1)), + (nme.wrapUnitArray, "toUnitVarArgs", jstpe.ArrayTypeRef(jstpe.ClassRef(jswkn.BoxedUnitClass), 1)), + (nme.wrapBooleanArray, "toBooleanVarArgs", jstpe.ArrayTypeRef(jstpe.BooleanRef, 1)), + (nme.wrapCharArray, "toCharVarArgs", jstpe.ArrayTypeRef(jstpe.CharRef, 1)), + (nme.wrapByteArray, "toByteVarArgs", jstpe.ArrayTypeRef(jstpe.ByteRef, 1)), + (nme.wrapShortArray, "toShortVarArgs", jstpe.ArrayTypeRef(jstpe.ShortRef, 1)), + (nme.wrapIntArray, "toIntVarArgs", jstpe.ArrayTypeRef(jstpe.IntRef, 1)), + (nme.wrapLongArray, "toLongVarArgs", jstpe.ArrayTypeRef(jstpe.LongRef, 1)), + (nme.wrapFloatArray, "toFloatVarArgs", jstpe.ArrayTypeRef(jstpe.FloatRef, 1)), + (nme.wrapDoubleArray, "toDoubleVarArgs", jstpe.ArrayTypeRef(jstpe.DoubleRef, 1)) + ) + + items.map { case (wrapArrayName, simpleName, argTypeRef) => + val wrapArraySym = getMemberMethod(wrapArrayModule, wrapArrayName) + val toVarArgsName = MethodName(simpleName, argTypeRef :: Nil, SeqClassRef) + wrapArraySym -> toVarArgsName + }.toMap } def unapply(tree: Apply): Option[Tree] = tree match { case Apply(wrapArray_?, List(wrapped)) - if isWrapArray(wrapArray_?.symbol) => + if wrapArraySymToToVarArgsName.contains(wrapArray_?.symbol) => Some(wrapped) case _ => None diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala index b872deb2b3..9f44c4c4ec 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala @@ -85,9 +85,10 @@ class OptimizationTest extends JSASTTest { val d = js.Array(Nil) val e = js.Array(new VC(151189)) } - """. - hasNot("any of the wrapArray methods") { + """.hasNot("any of the wrapArray methods") { case WrapArrayCall() => + }.hasNot("any toVarArgs calls") { + case ToVarArgsCall() => } } @@ -108,9 +109,10 @@ class OptimizationTest extends JSASTTest { val d = List(Nil) val e = List(new VC(151189)) } - """. - hasNot("any of the wrapArray methods") { + """.hasNot("any of the wrapArray methods") { case WrapArrayCall() => + }.hasExactly(5, "toVarArgs calls") { + case ToVarArgsCall() => } /* #2265 and #2741: @@ -136,9 +138,10 @@ class OptimizationTest extends JSASTTest { def single(x: Int, ys: Int*): Int = x + ys.size def multiple(x: Int)(ys: Int*): Int = x + ys.size } - """. - hasNot("any of the wrapArray methods") { + """.hasNot("any of the wrapArray methods") { case WrapArrayCall() => + }.hasExactly(3, "toVarArgs calls") { + case ToVarArgsCall() => } /* Make sure our wrapper matcher has the right name. @@ -162,6 +165,8 @@ class OptimizationTest extends JSASTTest { } sanityCheckCode.has("one of the wrapArray methods") { case WrapArrayCall() => + }.hasNot("any toVarArgs calls") { + case ToVarArgsCall() => } } @@ -697,6 +702,7 @@ class OptimizationTest extends JSASTTest { object OptimizationTest { private val ArrayModuleClass = ClassName("scala.Array$") + private val ScalaJSRunTimeModuleClass = ClassName("scala.scalajs.runtime.package$") private val applySimpleMethodName = SimpleMethodName("apply") @@ -718,4 +724,15 @@ object OptimizationTest { } } + private object ToVarArgsCall { + def unapply(tree: js.Apply): Boolean = { + tree.method.name.simpleName.nameString.endsWith("VarArgs") && { + tree.receiver match { + case js.LoadModule(ScalaJSRunTimeModuleClass) => true + case _ => false + } + } + } + } + } diff --git a/library/src/main/scala-new-collections/scala/scalajs/runtime/Compat.scala b/library/src/main/scala-new-collections/scala/scalajs/runtime/Compat.scala index c95be9a685..9580221f06 100644 --- a/library/src/main/scala-new-collections/scala/scalajs/runtime/Compat.scala +++ b/library/src/main/scala-new-collections/scala/scalajs/runtime/Compat.scala @@ -13,6 +13,7 @@ package scala.scalajs.runtime import scala.collection.IterableOnce +import scala.collection.immutable.ArraySeq import scala.scalajs.js @@ -32,4 +33,37 @@ private[runtime] object Compat { } } + @inline def toGenericVarArgsWasmImpl[T](xs: Array[T]): Seq[T] = + ArraySeq.unsafeWrapArray(xs) + + @inline def toRefVarArgsWasmImpl[T <: AnyRef](xs: Array[T]): Seq[T] = + new ArraySeq.ofRef[T](xs) + + @inline def toUnitVarArgsWasmImpl(xs: Array[Unit]): Seq[Unit] = + new ArraySeq.ofUnit(xs) + + @inline def toBooleanVarArgsWasmImpl(xs: Array[Boolean]): Seq[Boolean] = + new ArraySeq.ofBoolean(xs) + + @inline def toCharVarArgsWasmImpl(xs: Array[Char]): Seq[Char] = + new ArraySeq.ofChar(xs) + + @inline def toByteVarArgsWasmImpl(xs: Array[Byte]): Seq[Byte] = + new ArraySeq.ofByte(xs) + + @inline def toShortVarArgsWasmImpl(xs: Array[Short]): Seq[Short] = + new ArraySeq.ofShort(xs) + + @inline def toIntVarArgsWasmImpl(xs: Array[Int]): Seq[Int] = + new ArraySeq.ofInt(xs) + + @inline def toLongVarArgsWasmImpl(xs: Array[Long]): Seq[Long] = + new ArraySeq.ofLong(xs) + + @inline def toFloatVarArgsWasmImpl(xs: Array[Float]): Seq[Float] = + new ArraySeq.ofFloat(xs) + + @inline def toDoubleVarArgsWasmImpl(xs: Array[Double]): Seq[Double] = + new ArraySeq.ofDouble(xs) + } diff --git a/library/src/main/scala-old-collections/scala/scalajs/runtime/Compat.scala b/library/src/main/scala-old-collections/scala/scalajs/runtime/Compat.scala index c58ec71b7e..c864249752 100644 --- a/library/src/main/scala-old-collections/scala/scalajs/runtime/Compat.scala +++ b/library/src/main/scala-old-collections/scala/scalajs/runtime/Compat.scala @@ -13,6 +13,8 @@ package scala.scalajs.runtime import scala.collection.GenTraversableOnce +import scala.collection.mutable.WrappedArray + import scala.scalajs.js private[runtime] object Compat { @@ -32,4 +34,37 @@ private[runtime] object Compat { } } + @inline def toGenericVarArgsWasmImpl[T](xs: Array[T]): Seq[T] = + WrappedArray.make(xs) + + @inline def toRefVarArgsWasmImpl[T <: AnyRef](xs: Array[T]): Seq[T] = + new WrappedArray.ofRef[T](xs) + + @inline def toUnitVarArgsWasmImpl(xs: Array[Unit]): Seq[Unit] = + new WrappedArray.ofUnit(xs) + + @inline def toBooleanVarArgsWasmImpl(xs: Array[Boolean]): Seq[Boolean] = + new WrappedArray.ofBoolean(xs) + + @inline def toCharVarArgsWasmImpl(xs: Array[Char]): Seq[Char] = + new WrappedArray.ofChar(xs) + + @inline def toByteVarArgsWasmImpl(xs: Array[Byte]): Seq[Byte] = + new WrappedArray.ofByte(xs) + + @inline def toShortVarArgsWasmImpl(xs: Array[Short]): Seq[Short] = + new WrappedArray.ofShort(xs) + + @inline def toIntVarArgsWasmImpl(xs: Array[Int]): Seq[Int] = + new WrappedArray.ofInt(xs) + + @inline def toLongVarArgsWasmImpl(xs: Array[Long]): Seq[Long] = + new WrappedArray.ofLong(xs) + + @inline def toFloatVarArgsWasmImpl(xs: Array[Float]): Seq[Float] = + new WrappedArray.ofFloat(xs) + + @inline def toDoubleVarArgsWasmImpl(xs: Array[Double]): Seq[Double] = + new WrappedArray.ofDouble(xs) + } diff --git a/library/src/main/scala/scala/scalajs/runtime/package.scala b/library/src/main/scala/scala/scalajs/runtime/package.scala index 342081817d..c986926ba4 100644 --- a/library/src/main/scala/scala/scalajs/runtime/package.scala +++ b/library/src/main/scala/scala/scalajs/runtime/package.scala @@ -14,6 +14,8 @@ package scala.scalajs import scala.annotation.tailrec +import scala.scalajs.LinkingInfo.{isWebAssembly, linkTimeIf} + package object runtime { import scala.scalajs.runtime.Compat._ @@ -32,6 +34,95 @@ package object runtime { @inline def toJSVarArgs[A](seq: Seq[A]): js.Array[A] = toJSVarArgsImpl(seq) + /* Factories for varargs seqs. + * + * The compiler backend introduces calls to these methods, instead of the + * default {Predef,ScalaRunTime}.wrap*Array methods. They choose the best + * implementation of Seq for varargs depending on the target. + * + * On Wasm, we use sci.ArraySeq/scm.WrappedArray, like on the JVM. + * On JavaScript, we convert the Scala arrays to JS arrays and use + * scala.scalajs.runtime.WrappedVarArgs/WrappedArray. + * + * The conversions from Scala array to JS array are intrinsified by the + * optimizer, which directly creates a JS array from the start, instead of + * creating a temporary Scala array. + * + * We use linkTimeIf's not to have any regression compared to the code + * generated by Scala.js < 1.20.0, where we unconditionally generated calls + * to toScalaVarArgs with JS arrays. + */ + + @inline def toGenericVarArgs[T](xs: Array[T]): Seq[T] = + linkTimeIf[Seq[T]](isWebAssembly)(toGenericVarArgsWasmImpl(xs))(toScalaVarArgs(genericArrayToJSArray(xs))) + + @inline def toRefVarArgs[T <: AnyRef](xs: Array[T]): Seq[T] = + linkTimeIf[Seq[T]](isWebAssembly)(toRefVarArgsWasmImpl[T](xs))(toScalaVarArgs(refArrayToJSArray(xs))) + + @inline def toUnitVarArgs(xs: Array[Unit]): Seq[Unit] = + linkTimeIf[Seq[Unit]](isWebAssembly)(toUnitVarArgsWasmImpl(xs))(toScalaVarArgs(unitArrayToJSArray(xs))) + + @inline def toBooleanVarArgs(xs: Array[Boolean]): Seq[Boolean] = + linkTimeIf[Seq[Boolean]](isWebAssembly)(toBooleanVarArgsWasmImpl(xs))(toScalaVarArgs(booleanArrayToJSArray(xs))) + + @inline def toCharVarArgs(xs: Array[Char]): Seq[Char] = + linkTimeIf[Seq[Char]](isWebAssembly)(toCharVarArgsWasmImpl(xs))(toScalaVarArgs(charArrayToJSArray(xs))) + + @inline def toByteVarArgs(xs: Array[Byte]): Seq[Byte] = + linkTimeIf[Seq[Byte]](isWebAssembly)(toByteVarArgsWasmImpl(xs))(toScalaVarArgs(byteArrayToJSArray(xs))) + + @inline def toShortVarArgs(xs: Array[Short]): Seq[Short] = + linkTimeIf[Seq[Short]](isWebAssembly)(toShortVarArgsWasmImpl(xs))(toScalaVarArgs(shortArrayToJSArray(xs))) + + @inline def toIntVarArgs(xs: Array[Int]): Seq[Int] = + linkTimeIf[Seq[Int]](isWebAssembly)(toIntVarArgsWasmImpl(xs))(toScalaVarArgs(intArrayToJSArray(xs))) + + @inline def toLongVarArgs(xs: Array[Long]): Seq[Long] = + linkTimeIf[Seq[Long]](isWebAssembly)(toLongVarArgsWasmImpl(xs))(toScalaVarArgs(longArrayToJSArray(xs))) + + @inline def toFloatVarArgs(xs: Array[Float]): Seq[Float] = + linkTimeIf[Seq[Float]](isWebAssembly)(toFloatVarArgsWasmImpl(xs))(toScalaVarArgs(floatArrayToJSArray(xs))) + + @inline def toDoubleVarArgs(xs: Array[Double]): Seq[Double] = + linkTimeIf[Seq[Double]](isWebAssembly)(toDoubleVarArgsWasmImpl(xs))(toScalaVarArgs(doubleArrayToJSArray(xs))) + + // Intrinsics to convert arrays to JS arrays + + @inline + private def arrayToJSArrayImpl[T](array: Array[T]): js.Array[T] = { + val len = array.length + val result = js.Array[T]() + var i = 0 + while (i != len) { + result.push(array(i)) + i += 1 + } + result + } + + @noinline def genericArrayToJSArray[T](array: Array[T]): js.Array[T] = + arrayToJSArrayImpl(array) + @noinline def refArrayToJSArray[T <: AnyRef](array: Array[T]): js.Array[T] = + arrayToJSArrayImpl(array) + @noinline def unitArrayToJSArray(array: Array[Unit]): js.Array[Unit] = + arrayToJSArrayImpl(array) + @noinline def booleanArrayToJSArray(array: Array[Boolean]): js.Array[Boolean] = + arrayToJSArrayImpl(array) + @noinline def charArrayToJSArray(array: Array[Char]): js.Array[Char] = + arrayToJSArrayImpl(array) + @noinline def byteArrayToJSArray(array: Array[Byte]): js.Array[Byte] = + arrayToJSArrayImpl(array) + @noinline def shortArrayToJSArray(array: Array[Short]): js.Array[Short] = + arrayToJSArrayImpl(array) + @noinline def intArrayToJSArray(array: Array[Int]): js.Array[Int] = + arrayToJSArrayImpl(array) + @noinline def longArrayToJSArray(array: Array[Long]): js.Array[Long] = + arrayToJSArrayImpl(array) + @noinline def floatArrayToJSArray(array: Array[Float]): js.Array[Float] = + arrayToJSArrayImpl(array) + @noinline def doubleArrayToJSArray(array: Array[Double]): js.Array[Double] = + arrayToJSArrayImpl(array) + /** Dummy method used to preserve the type parameter of * `js.constructorOf[T]` through erasure. * diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 194e5cbca9..a9fe54aa47 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -362,6 +362,11 @@ private[optimizer] abstract class OptimizerCore( pretransformSelectCommon(lhs, isLhsOfAssign = true)(cont) } + case lhs: ArraySelect => + trampoline { + pretransformArraySelect(lhs, isLhsOfAssign = true)(cont) + } + case lhs: JSSelect => trampoline { pretransformJSSelect(lhs, isLhsOfAssign = true)(cont) @@ -549,28 +554,12 @@ private[optimizer] abstract class OptimizerCore( case ArrayValue(tpe, elems) => ArrayValue(tpe, elems map transformExpr) - case ArraySelect(array, index) => - val newArray = transformExpr(array) - - val newElemType = newArray.tpe match { - case NothingType | NullType => - /* Will throw / NPE / UB. - * - * Note that we cannot easily replace the ArraySelect with, say a - * checkNotNullStatement, because it might be used as lhs of an Assign node. - */ - NothingType - - case tpe: ArrayType => - arrayElemType(tpe) - - case _ => - throw new AssertionError( - s"got non-array type after transforming ArraySelect at ${tree.pos}") + case tree: ArraySelect => + trampoline { + pretransformArraySelect(tree, isLhsOfAssign = false)( + finishTransform(isStat = false)) } - ArraySelect(newArray, transformExpr(index))(newElemType) - case RecordValue(tpe, elems) => RecordValue(tpe, elems map transformExpr) @@ -998,6 +987,34 @@ private[optimizer] abstract class OptimizerCore( case tree: BinaryOp => pretransformBinaryOp(tree)(cont) + case ArrayValue(typeRef, items) => + /* Trying to virtualize more than 64 items in an array is probably + * a bad idea, and will slow down the optimizer for no good reason. + * See for example #2943. + */ + if (items.size > 64) { + cont(ArrayValue(typeRef, items.map(transformExpr(_))).toPreTransform) + } else { + pretransformExprs(items) { titems => + tryOrRollback { cancelFun => + withNewTempLocalDefs(titems) { (itemLocalDefs, cont1) => + val replacement = InlineArrayReplacement( + typeRef, itemLocalDefs.toVector, cancelFun) + val localDef = LocalDef( + RefinedType(tree.tpe), + mutable = false, + replacement) + cont1(localDef.toPreTransform) + } (cont) + } { () => + cont(PreTransTree(ArrayValue(typeRef, titems.map(finishTransformExpr)))) + } + } + } + + case tree: ArraySelect => + pretransformArraySelect(tree, isLhsOfAssign = false)(cont) + case tree: JSSelect => pretransformJSSelect(tree, isLhsOfAssign = false)(cont) @@ -1310,6 +1327,46 @@ private[optimizer] abstract class OptimizerCore( } } + private def pretransformArraySelect(tree: ArraySelect, isLhsOfAssign: Boolean)( + cont: PreTransCont)( + implicit scope: Scope): TailRec[Tree] = { + + val ArraySelect(array, index) = tree + implicit val pos = tree.pos + + pretransformExprs(array, index) { (tarray, tindex) => + (tarray, tindex) match { + case (PreTransLocalDef(LocalDef(tpe, /* mutable = */ false, + replacement: InlineArrayReplacement)), + PreTransLit(IntLiteral(indexValue))) + if !isLhsOfAssign && replacement.elemLocalDefs.indices.contains(indexValue) => + cont(replacement.elemLocalDefs(indexValue).toPreTransform) + + case _ => + def newArray = finishTransformExpr(tarray) + def newIndex = finishTransformExpr(tindex) + + tarray.tpe.base match { + case NothingType | NullType if isLhsOfAssign => + /* We need to preserve a real ArraySelect for the Assign node. + * However, we can drop the side effects of the index, since we + * won't get that far. + */ + cont(ArraySelect(newArray, IntLiteral(0))(NothingType).toPreTransform) + case NothingType => + cont(tarray) + case NullType => + cont(checkNotNull(tarray)) + case arrayType: ArrayType => + cont(ArraySelect(newArray, newIndex)(arrayElemType(arrayType)).toPreTransform) + case tpe => + throw new AssertionError( + s"got non-array type $tpe after transforming ArraySelect at $pos") + } + } + } + } + private def pretransformAssign(tlhs: PreTransform, trhs: PreTransform)( cont: PreTransCont)(implicit scope: Scope, pos: Position): TailRec[Tree] = { def contAssign(lhs: Tree, rhs: Tree) = @@ -2874,6 +2931,21 @@ private[optimizer] abstract class OptimizerCore( default } + case ArrayToJSArray => + val tarray = targs.head + tarray match { + case PreTransLocalDef(LocalDef(_, /* mutable = */ false, replacement: InlineArrayReplacement)) => + tryOrRollback { cancelFun => + val jsArrayReplacement = InlineJSArrayReplacement(replacement.elemLocalDefs, cancelFun) + val localDef = LocalDef(RefinedType(AnyNotNullType), mutable = false, jsArrayReplacement) + cont(localDef.toPreTransform) + } { () => + cont(JSArrayConstr(replacement.elemLocalDefs.map(_.newReplacement).toList).toPreTransform) + } + case _ => + default + } + // java.lang.Integer case IntegerNTZ => @@ -3915,6 +3987,14 @@ private[optimizer] abstract class OptimizerCore( default } + case Array_length => + arg match { + case PreTransLocalDef(LocalDef(_, _, replacement: InlineArrayReplacement)) => + PreTransLit(IntLiteral(replacement.elemLocalDefs.size)) + case _ => + default + } + case GetClass => def constant(typeRef: TypeRef): PreTransform = PreTransTree(Block(finishTransformStat(arg), ClassOf(typeRef))) @@ -6216,6 +6296,9 @@ private[optimizer] object OptimizerCore { case InlineClassInstanceReplacement(_, _, cancelFun) => cancelFun() + case InlineArrayReplacement(_, _, cancelFun) => + cancelFun() + case InlineJSArrayReplacement(_, cancelFun) => cancelFun() } @@ -6230,6 +6313,8 @@ private[optimizer] object OptimizerCore { fieldLocalDefs.valuesIterator.exists(_.contains(that)) case InlineClassInstanceReplacement(_, fieldLocalDefs, _) => fieldLocalDefs.valuesIterator.exists(_.contains(that)) + case InlineArrayReplacement(_, elemLocalDefs, _) => + elemLocalDefs.exists(_.contains(that)) case InlineJSArrayReplacement(elemLocalDefs, _) => elemLocalDefs.exists(_.contains(that)) @@ -6294,6 +6379,12 @@ private[optimizer] object OptimizerCore { fieldLocalDefs: Map[FieldName, LocalDef], cancelFun: CancelFun) extends LocalDefReplacement + private final case class InlineArrayReplacement( + arrayTypeRef: ArrayTypeRef, + elemLocalDefs: Vector[LocalDef], + cancelFun: CancelFun) + extends LocalDefReplacement + private final case class InlineJSArrayReplacement( elemLocalDefs: Vector[LocalDef], cancelFun: CancelFun) extends LocalDefReplacement @@ -6828,7 +6919,9 @@ private[optimizer] object OptimizerCore { final val ClassGetName = GenericArrayBuilderResult + 1 - final val ObjectLiteral = ClassGetName + 1 + final val ArrayToJSArray = ClassGetName + 1 + + final val ObjectLiteral = ArrayToJSArray + 1 final val ByteArrayToInt8Array = ObjectLiteral + 1 final val ShortArrayToInt16Array = ByteArrayToInt8Array + 1 @@ -6850,6 +6943,10 @@ private[optimizer] object OptimizerCore { } private val V = VoidRef + private val Z = BooleanRef + private val C = CharRef + private val B = ByteRef + private val S = ShortRef private val I = IntRef private val J = LongRef private val F = FloatRef @@ -6880,6 +6977,18 @@ private[optimizer] object OptimizerCore { ClassName("java.lang.Class") -> List( m("getName", Nil, StringClassRef) -> ClassGetName ), + ClassName("scala.scalajs.runtime.package$") -> List( + m("genericArrayToJSArray", List(O), JSArrayClassRef) -> ArrayToJSArray, + m("refArrayToJSArray", List(ArrayTypeRef(O, 1)), JSArrayClassRef) -> ArrayToJSArray, + m("booleanArrayToJSArray", List(ArrayTypeRef(Z, 1)), JSArrayClassRef) -> ArrayToJSArray, + m("charArrayToJSArray", List(ArrayTypeRef(C, 1)), JSArrayClassRef) -> ArrayToJSArray, + m("byteArrayToJSArray", List(ArrayTypeRef(B, 1)), JSArrayClassRef) -> ArrayToJSArray, + m("shortArrayToJSArray", List(ArrayTypeRef(S, 1)), JSArrayClassRef) -> ArrayToJSArray, + m("intArrayToJSArray", List(ArrayTypeRef(I, 1)), JSArrayClassRef) -> ArrayToJSArray, + m("longArrayToJSArray", List(ArrayTypeRef(J, 1)), JSArrayClassRef) -> ArrayToJSArray, + m("floatArrayToJSArray", List(ArrayTypeRef(F, 1)), JSArrayClassRef) -> ArrayToJSArray, + m("doubleArrayToJSArray", List(ArrayTypeRef(D, 1)), JSArrayClassRef) -> ArrayToJSArray + ), ClassName("scala.scalajs.js.special.package$") -> List( m("objectLiteral", List(SeqClassRef), JSObjectClassRef) -> ObjectLiteral, // 2.12 m("objectLiteral", List(ImmutableSeqClassRef), JSObjectClassRef) -> ObjectLiteral // 2.13 diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-expand-varargs-implicit-over-varargs.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-expand-varargs-implicit-over-varargs.check-js similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-expand-varargs-implicit-over-varargs.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-expand-varargs-implicit-over-varargs.check-js diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/sammy_vararg_cbn.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/sammy_vararg_cbn.check-js similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/sammy_vararg_cbn.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/sammy_vararg_cbn.check-js diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5966.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5966.check-js similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5966.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5966.check-js diff --git a/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSSBTRunner.scala b/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSSBTRunner.scala index 5873adbe15..b823124f03 100644 --- a/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSSBTRunner.scala +++ b/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSSBTRunner.scala @@ -57,7 +57,7 @@ class ScalaJSSBTRunner( val onlyIndividualTests = false val start = System.nanoTime() - val info = new ScalaJSTestInfo(testFile, listDir) + val info = new ScalaJSTestInfo(testFile, listDir, options) val runner = new ScalaJSRunner(info, this, options) var stopwatchDuration: Option[Long] = None diff --git a/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSTestInfo.scala b/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSTestInfo.scala index 1e1b2acc3e..895a5c9392 100644 --- a/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSTestInfo.scala +++ b/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSTestInfo.scala @@ -17,14 +17,16 @@ import java.io.File import scala.tools.partest.FileOps import scala.tools.partest.nest.TestInfo -class ScalaJSTestInfo(testFile: File, scalaJSOverridePath: String) +class ScalaJSTestInfo(testFile: File, scalaJSOverridePath: String, options: ScalaJSPartestOptions) extends TestInfo(testFile) { override val checkFile: File = { - scalaJSConfigFile("check").getOrElse { - // this is super.checkFile, but apparently we can't do that - new FileOps(testFile).changeExtension("check") - } + scalaJSConfigFile("check") + .orElse(scalaJSConfigFile("check" + options.targetSpecificCheckFileSuffix)) + .getOrElse { + // this is super.checkFile, but apparently we can't do that + new FileOps(testFile).changeExtension("check") + } } val compliantSems: List[String] = { diff --git a/partest/src/main/scala-old-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala b/partest/src/main/scala-old-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala index 8fa429a125..8476334425 100644 --- a/partest/src/main/scala-old-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala +++ b/partest/src/main/scala-old-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala @@ -34,10 +34,12 @@ class ScalaJSRunner(testFile: File, suiteRunner: SuiteRunner, } override val checkFile: File = { - scalaJSConfigFile("check") getOrElse { - // this is super.checkFile, but apparently we can't do that - new FileOps(testFile).changeExtension("check") - } + scalaJSConfigFile("check") + .orElse(scalaJSConfigFile("check" + options.targetSpecificCheckFileSuffix)) + .getOrElse { + // this is super.checkFile, but apparently we can't do that + new FileOps(testFile).changeExtension("check") + } } private def scalaJSConfigFile(ext: String): Option[File] = { diff --git a/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala b/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala index 397d571454..416289b162 100644 --- a/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala +++ b/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala @@ -29,6 +29,10 @@ class ScalaJSPartestOptions private ( |testFilter: ${testFilter.descr} """.stripMargin } + + val targetSpecificCheckFileSuffix: String = + if (useWasm) "-wasm" + else "-js" } object ScalaJSPartestOptions {