From 9e5bf000b5d9c3304d954e6d35922de8d7174681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 17 Jan 2025 17:04:40 +0100 Subject: [PATCH 1/2] Scala.js: Emit `js.NewArray` IR nodes when possible. Although there is a *correct* implementation of `sr.Arrays.newArray`, it is not efficient when creating 1-dimensional arrays. The JVM backend special-cases it to emit `newarray` bytecode instructions. We now also special-case it in the JS backend. In the Scala.js IR however, `js.NewArray` only accepts a single dimension. For multiple dimensions, the right thing to do is to emit a direct call to `jlr.Array.newInstance`. [Cherry-picked 2852168e8ddac8aa318635a73c79f64282aa39d1] --- .../dotty/tools/backend/sjs/JSCodeGen.scala | 25 ++++++++++++++++++- .../tools/backend/sjs/JSPrimitives.scala | 6 +++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 13425d5effaa..e8ad93082804 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -2550,6 +2550,8 @@ class JSCodeGen()(using genCtx: Context) { genCoercion(tree, receiver, code) else if (code == JSPrimitives.THROW) genThrow(tree, args) + else if (code == JSPrimitives.NEW_ARRAY) + genNewArray(tree, args) else if (JSPrimitives.isJSPrimitive(code)) genJSPrimitive(tree, args, code, isStat) else @@ -3019,6 +3021,24 @@ class JSCodeGen()(using genCtx: Context) { } } + /** Gen a call to the special `newArray` method. */ + private def genNewArray(tree: Apply, args: List[Tree]): js.Tree = { + implicit val pos: SourcePosition = tree.sourcePos + + val List(elemClazz, Literal(arrayClassConstant), dimsArray: JavaSeqLiteral) = args: @unchecked + + dimsArray.elems match { + case singleDim :: Nil => + // Use a js.NewArray + val arrayTypeRef = toTypeRef(arrayClassConstant.typeValue).asInstanceOf[jstpe.ArrayTypeRef] + js.NewArray(arrayTypeRef, genExpr(singleDim)) + case _ => + // Delegate to jlr.Array.newInstance + js.ApplyStatic(js.ApplyFlags.empty, JLRArrayClassName, js.MethodIdent(JLRArrayNewInstanceMethodName), + List(genExpr(elemClazz), genJavaSeqLiteral(dimsArray)))(jstpe.AnyType) + } + } + /** Gen a "normal" apply (to a true method). * * But even these are further refined into: @@ -4831,7 +4851,7 @@ class JSCodeGen()(using genCtx: Context) { object JSCodeGen { - private val NullPointerExceptionClass = ClassName("java.lang.NullPointerException") + private val JLRArrayClassName = ClassName("java.lang.reflect.Array") private val JSObjectClassName = ClassName("scala.scalajs.js.Object") private val JavaScriptExceptionClassName = ClassName("scala.scalajs.js.JavaScriptException") @@ -4841,6 +4861,9 @@ object JSCodeGen { private val selectedValueMethodName = MethodName("selectedValue", Nil, ObjectClassRef) + private val JLRArrayNewInstanceMethodName = + MethodName("newInstance", List(jstpe.ClassRef(jsNames.ClassClass), jstpe.ArrayTypeRef(jstpe.IntRef, 1)), ObjectClassRef) + private val ObjectArgConstructorName = MethodName.constructor(List(ObjectClassRef)) private val thisOriginalName = OriginalName("this") diff --git a/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala b/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala index a3a37795826a..f23c816b5204 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala @@ -48,9 +48,10 @@ object JSPrimitives { inline val UNWRAP_FROM_THROWABLE = WRAP_AS_THROWABLE + 1 // js.special.unwrapFromThrowable inline val DEBUGGER = UNWRAP_FROM_THROWABLE + 1 // js.special.debugger - inline val THROW = DEBUGGER + 1 + inline val THROW = DEBUGGER + 1 // .throw + inline val NEW_ARRAY = THROW + 1 // scala.runtime.Arrays.newArray - inline val UNION_FROM = THROW + 1 // js.|.from + inline val UNION_FROM = NEW_ARRAY + 1 // js.|.from inline val UNION_FROM_TYPE_CONSTRUCTOR = UNION_FROM + 1 // js.|.fromTypeConstructor inline val REFLECT_SELECTABLE_SELECTDYN = UNION_FROM_TYPE_CONSTRUCTOR + 1 // scala.reflect.Selectable.selectDynamic @@ -137,6 +138,7 @@ class JSPrimitives(ictx: Context) extends DottyPrimitives(ictx) { addPrimitive(jsdefn.Special_debugger, DEBUGGER) addPrimitive(defn.throwMethod, THROW) + addPrimitive(defn.newArrayMethod, NEW_ARRAY) addPrimitive(jsdefn.PseudoUnion_from, UNION_FROM) addPrimitive(jsdefn.PseudoUnion_fromTypeConstructor, UNION_FROM_TYPE_CONSTRUCTOR) From a3269ef4b6f3309ebb7850c967ec8979c6f46b7b Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Thu, 3 Apr 2025 16:40:05 +0200 Subject: [PATCH 2/2] bugfix: Fix issues after backporting --- compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala | 3 ++- compiler/src/dotty/tools/dotc/core/Types.scala | 8 +++++--- compiler/src/dotty/tools/dotc/typer/Checking.scala | 1 + .../dotty/tools/pc/utils/InteractiveEnrichments.scala | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index e8ad93082804..53ff14c3aa78 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -3031,7 +3031,7 @@ class JSCodeGen()(using genCtx: Context) { case singleDim :: Nil => // Use a js.NewArray val arrayTypeRef = toTypeRef(arrayClassConstant.typeValue).asInstanceOf[jstpe.ArrayTypeRef] - js.NewArray(arrayTypeRef, genExpr(singleDim)) + js.NewArray(arrayTypeRef, List(genExpr(singleDim))) case _ => // Delegate to jlr.Array.newInstance js.ApplyStatic(js.ApplyFlags.empty, JLRArrayClassName, js.MethodIdent(JLRArrayNewInstanceMethodName), @@ -4852,6 +4852,7 @@ class JSCodeGen()(using genCtx: Context) { object JSCodeGen { private val JLRArrayClassName = ClassName("java.lang.reflect.Array") + private val NullPointerExceptionClass = ClassName("java.lang.NullPointerException") private val JSObjectClassName = ClassName("scala.scalajs.js.Object") private val JavaScriptExceptionClassName = ClassName("scala.scalajs.js.JavaScriptException") diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ac6089516571..1113f7413e6e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5794,11 +5794,13 @@ object Types extends TypeUtils { protected def derivedLambdaType(tp: LambdaType)(formals: List[tp.PInfo], restpe: Type): Type = tp.derivedLambdaType(tp.paramNames, formals, restpe) + protected def mapArg(arg: Type, tparam: ParamInfo): Type = arg match + case arg: TypeBounds => this(arg) + case arg => atVariance(variance * tparam.paramVarianceSign)(this(arg)) + protected def mapArgs(args: List[Type], tparams: List[ParamInfo]): List[Type] = args match case arg :: otherArgs if tparams.nonEmpty => - val arg1 = arg match - case arg: TypeBounds => this(arg) - case arg => atVariance(variance * tparams.head.paramVarianceSign)(this(arg)) + val arg1 = mapArg(arg, tparams.head) val otherArgs1 = mapArgs(otherArgs, tparams.tail) if ((arg1 eq arg) && (otherArgs1 eq otherArgs)) args else arg1 :: otherArgs1 diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 2757304f32e8..3e005d20233f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -40,6 +40,7 @@ import printing.Formatting.hlAsKeyword import collection.mutable import reporting.* +import dotty.tools.dotc.core.TypeApplications.TypeParamInfo object Checking { import tpd.* diff --git a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala index b65f23fae40f..d909b6ee7e04 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala @@ -281,7 +281,7 @@ object InteractiveEnrichments extends CommonMtagsEnrichments: sym, () => parentSymbols.iterator.map(toSemanticdbSymbol).toList.asJava, contentType, - ) + ).nn documentation.nn.toScala end symbolDocumentation end extension