@@ -1226,8 +1226,15 @@ object Serializers {
1226
1226
1227
1227
case TagDebugger => Debugger ()
1228
1228
1229
- case TagNew => New (readClassName(), readMethodIdent(), readTrees())
1230
- case TagLoadModule => LoadModule (readClassName())
1229
+ case TagNew =>
1230
+ val tree = New (readClassName(), readMethodIdent(), readTrees())
1231
+ if (hacks.useBelow(19 ))
1232
+ anonFunctionNewNodeHackBelow19(tree)
1233
+ else
1234
+ tree
1235
+
1236
+ case TagLoadModule =>
1237
+ LoadModule (readClassName())
1231
1238
1232
1239
case TagStoreModule =>
1233
1240
if (hacks.useBelow(16 )) {
@@ -1542,6 +1549,129 @@ object Serializers {
1542
1549
UnaryOp (UnaryOp .CheckNotNull , expr)
1543
1550
}
1544
1551
1552
+ /** Rewrites `New` nodes of `AnonFunctionN`s coming from before 1.19 into `NewLambda` nodes.
1553
+ *
1554
+ * Before 1.19, the codegen for `scala.FunctionN` lambda was of the following shape:
1555
+ * {{{
1556
+ * new scala.scalajs.runtime.AnonFunctionN(arrow-lambda<...captures>(...args: any): any = {
1557
+ * body
1558
+ * })
1559
+ * }}}
1560
+ *
1561
+ * This function rewrites such calls to `NewLambda` nodes, using the new
1562
+ * definition of these classes:
1563
+ * {{{
1564
+ * <newLambda>(scala.scalajs.runtime.AnonFunctionN,
1565
+ * apply;Ljava.lang.Object;...;Ljava.lang.Object,
1566
+ * any, any, (typed-lambda<...captures>(...args: any): any = {
1567
+ * body
1568
+ * }))
1569
+ * }}}
1570
+ *
1571
+ * The rewrite ensures that previously published lambdas get the same
1572
+ * optimizations on Wasm as those recompiled with 1.19+.
1573
+ *
1574
+ * The rewrite also applies to Scala 3's `AnonFunctionXXL` classes, with
1575
+ * an additional adaptation of the parameter's type. It rewrites
1576
+ * {{{
1577
+ * new scala.scalajs.runtime.AnonFunctionXXL(arrow-lambda<...captures>(argArray: any): any = {
1578
+ * body
1579
+ * })
1580
+ * }}}
1581
+ * to
1582
+ * {{{
1583
+ * <newLambda>(scala.scalajs.runtime.AnonFunctionXXL,
1584
+ * apply;Ljava.lang.Object[];Ljava.lang.Object,
1585
+ * any, any, (typed-lambda<...captures>(argArray: jl.Object[]): any = {
1586
+ * newBody
1587
+ * }))
1588
+ * }}}
1589
+ * where `newBody` is `body` transformed to adapt the type of `argArray`
1590
+ * everywhere.
1591
+ *
1592
+ * Tests are in `sbt-plugin/src/sbt-test/linker/anonfunction-compat/`.
1593
+ *
1594
+ * ---
1595
+ *
1596
+ * In case the argument is not an arrow-lambda of the expected shape, we
1597
+ * use a fallback. This never happens for our published codegens, but
1598
+ * could happen for other valid IR. We rewrite
1599
+ * {{{
1600
+ * new scala.scalajs.runtime.AnonFunctionN(jsFunctionArg)
1601
+ * }}}
1602
+ * to
1603
+ * {{{
1604
+ * <newLambda>(scala.scalajs.runtime.AnonFunctionN,
1605
+ * apply;Ljava.lang.Object;...;Ljava.lang.Object,
1606
+ * any, any, (typed-lambda<f: any = jsFunctionArg>(...args: any): any = {
1607
+ * f(...args)
1608
+ * }))
1609
+ * }}}
1610
+ *
1611
+ * This code path is not tested in the CI, but can be locally tested by
1612
+ * commenting out the `case Closure(...) =>`.
1613
+ */
1614
+ private def anonFunctionNewNodeHackBelow19 (tree : New ): Tree = {
1615
+ tree match {
1616
+ case New (cls, _, funArg :: Nil ) =>
1617
+ def makeFallbackTypedClosure (paramTypes : List [Type ]): Closure = {
1618
+ implicit val pos = funArg.pos
1619
+ val fParamDef = ParamDef (LocalIdent (LocalName (" f" )), NoOriginalName , AnyType , mutable = false )
1620
+ val xParamDefs = paramTypes.zipWithIndex.map { case (ptpe, i) =>
1621
+ ParamDef (LocalIdent (LocalName (s " x $i" )), NoOriginalName , ptpe, mutable = false )
1622
+ }
1623
+ Closure (ClosureFlags .typed, List (fParamDef), xParamDefs, None , AnyType ,
1624
+ JSFunctionApply (fParamDef.ref, xParamDefs.map(_.ref)),
1625
+ List (funArg))
1626
+ }
1627
+
1628
+ cls match {
1629
+ case HackNames .AnonFunctionClass (arity) =>
1630
+ val typedClosure = funArg match {
1631
+ // The shape produced by our earlier compilers, which we can optimally rewrite
1632
+ case Closure (ClosureFlags .arrow, captureParams, params, None , AnyType , body, captureValues)
1633
+ if params.lengthCompare(arity) == 0 =>
1634
+ Closure (ClosureFlags .typed, captureParams, params, None , AnyType ,
1635
+ body, captureValues)(funArg.pos)
1636
+
1637
+ // Fallback for other shapes (theoretically required; dead code in practice)
1638
+ case _ =>
1639
+ makeFallbackTypedClosure(List .fill(arity)(AnyType ))
1640
+ }
1641
+
1642
+ NewLambda (HackNames .anonFunctionDescriptors(arity), typedClosure)(tree.tpe)(tree.pos)
1643
+
1644
+ case HackNames .AnonFunctionXXLClass =>
1645
+ val typedClosure = funArg match {
1646
+ // The shape produced by our earlier compilers, which we can optimally rewrite
1647
+ case Closure (ClosureFlags .arrow, captureParams, oldParam :: Nil , None , AnyType , body, captureValues) =>
1648
+ // Here we need to adapt the type of the parameter from `any` to `jl.Object[]`.
1649
+ val newParam = oldParam.copy(ptpe = HackNames .ObjectArrayType )(oldParam.pos)
1650
+ val newBody = new Transformers .LocalScopeTransformer {
1651
+ override def transform (tree : Tree ): Tree = tree match {
1652
+ case tree @ VarRef (newParam.name.name) => tree.copy()(newParam.ptpe)(tree.pos)
1653
+ case _ => super .transform(tree)
1654
+ }
1655
+ }.transform(body)
1656
+ Closure (ClosureFlags .typed, captureParams, List (newParam), None , AnyType ,
1657
+ newBody, captureValues)(funArg.pos)
1658
+
1659
+ // Fallback for other shapes (theoretically required; dead code in practice)
1660
+ case _ =>
1661
+ makeFallbackTypedClosure(List (HackNames .ObjectArrayType ))
1662
+ }
1663
+
1664
+ NewLambda (HackNames .anonFunctionXXLDescriptor, typedClosure)(tree.tpe)(tree.pos)
1665
+
1666
+ case _ =>
1667
+ tree
1668
+ }
1669
+
1670
+ case _ =>
1671
+ tree
1672
+ }
1673
+ }
1674
+
1545
1675
def readTrees (): List [Tree ] =
1546
1676
List .fill(readInt())(readTree())
1547
1677
@@ -1641,10 +1771,15 @@ object Serializers {
1641
1771
1642
1772
val jsNativeMembers = jsNativeMembersBuilder.result()
1643
1773
1644
- ClassDef (name, originalName, kind, jsClassCaptures, superClass, parents,
1774
+ val classDef = ClassDef (name, originalName, kind, jsClassCaptures, superClass, parents,
1645
1775
jsSuperClass, jsNativeLoadSpec, fields, methods, jsConstructor,
1646
1776
jsMethodProps, jsNativeMembers, topLevelExportDefs)(
1647
1777
optimizerHints)
1778
+
1779
+ if (hacks.useBelow(19 ))
1780
+ anonFunctionClassDefHackBelow19(classDef)
1781
+ else
1782
+ classDef
1648
1783
}
1649
1784
1650
1785
private def jlClassMethodsHackBelow17 (methods : List [MethodDef ]): List [MethodDef ] = {
@@ -1931,6 +2066,88 @@ object Serializers {
1931
2066
(jsConstructorBuilder.result(), jsMethodPropsBuilder.result())
1932
2067
}
1933
2068
2069
+ /** Rewrites `scala.scalajs.runtime.AnonFunctionN`s from before 1.19.
2070
+ *
2071
+ * Before 1.19, these classes were defined as
2072
+ * {{{
2073
+ * // final in source code
2074
+ * class AnonFunctionN extends AbstractFunctionN {
2075
+ * val f: any
2076
+ * def this(f: any) = {
2077
+ * this.f = f;
2078
+ * super()
2079
+ * }
2080
+ * def apply(...args: any): any = f(...args)
2081
+ * }
2082
+ * }}}
2083
+ *
2084
+ * Starting with 1.19, they were rewritten to be used as SAM classes for
2085
+ * `NewLambda` nodes. The new IR shape is
2086
+ * {{{
2087
+ * // sealed abstract in source code
2088
+ * class AnonFunctionN extends AbstractFunctionN {
2089
+ * def this() = super()
2090
+ * }
2091
+ * }}}
2092
+ *
2093
+ * This function rewrites those classes to the new shape.
2094
+ *
2095
+ * The rewrite also applies to Scala 3's `AnonFunctionXXL`.
2096
+ *
2097
+ * Tests are in `sbt-plugin/src/sbt-test/linker/anonfunction-compat/`.
2098
+ */
2099
+ private def anonFunctionClassDefHackBelow19 (classDef : ClassDef ): ClassDef = {
2100
+ import classDef ._
2101
+
2102
+ if (! HackNames .allAnonFunctionClasses.contains(className)) {
2103
+ classDef
2104
+ } else {
2105
+ val newCtor : MethodDef = {
2106
+ // Find the old constructor to get its position and version
2107
+ val oldCtor = methods.find(_.methodName.isConstructor).getOrElse {
2108
+ throw new InvalidIRException (classDef,
2109
+ s " Did not find a constructor in ${className.nameString}" )
2110
+ }
2111
+ implicit val pos = oldCtor.pos
2112
+
2113
+ // constructor def <init>() = this.superClass::<init>()
2114
+ MethodDef (
2115
+ MemberFlags .empty.withNamespace(MemberNamespace .Constructor ),
2116
+ MethodIdent (NoArgConstructorName ),
2117
+ NoOriginalName ,
2118
+ Nil ,
2119
+ VoidType ,
2120
+ Some {
2121
+ ApplyStatically (
2122
+ ApplyFlags .empty.withConstructor(true ),
2123
+ This ()(ClassType (className, nullable = false )),
2124
+ superClass.get.name,
2125
+ MethodIdent (NoArgConstructorName ),
2126
+ Nil
2127
+ )(VoidType )
2128
+ }
2129
+ )(OptimizerHints .empty, oldCtor.version)
2130
+ }
2131
+
2132
+ ClassDef (
2133
+ name,
2134
+ originalName,
2135
+ kind,
2136
+ jsClassCaptures,
2137
+ superClass,
2138
+ interfaces,
2139
+ jsSuperClass,
2140
+ jsNativeLoadSpec,
2141
+ fields = Nil , // throws away the `f` field
2142
+ methods = List (newCtor), // throws away the old constructor and `apply` method
2143
+ jsConstructor,
2144
+ jsMethodProps,
2145
+ jsNativeMembers,
2146
+ topLevelExportDefs
2147
+ )(OptimizerHints .empty)(pos) // throws away the `@inline`
2148
+ }
2149
+ }
2150
+
1934
2151
private def readFieldDef ()(implicit pos : Position ): FieldDef = {
1935
2152
val flags = MemberFlags .fromBits(readInt())
1936
2153
val name = readFieldIdentForEnclosingClass()
@@ -2602,6 +2819,8 @@ object Serializers {
2602
2819
2603
2820
/** Names needed for hacks. */
2604
2821
private object HackNames {
2822
+ val AnonFunctionXXLClass =
2823
+ ClassName (" scala.scalajs.runtime.AnonFunctionXXL" ) // from the Scala 3 library
2605
2824
val CloneNotSupportedExceptionClass =
2606
2825
ClassName (" java.lang.CloneNotSupportedException" )
2607
2826
val SystemModule : ClassName =
@@ -2611,14 +2830,50 @@ object Serializers {
2611
2830
val ReflectArrayModClass =
2612
2831
ClassName(" java.lang.reflect.Array$" )
2613
2832
2833
+ val ObjectArrayType = ArrayType (ArrayTypeRef (ObjectRef , 1 ), nullable = true )
2834
+
2835
+ private val applySimpleName = SimpleMethodName (" apply" )
2836
+
2614
2837
val cloneName : MethodName =
2615
- MethodName (" clone" , Nil , ClassRef ( ObjectClass ) )
2838
+ MethodName (" clone" , Nil , ObjectRef )
2616
2839
val identityHashCodeName : MethodName =
2617
- MethodName (" identityHashCode" , List (ClassRef ( ObjectClass ) ), IntRef )
2840
+ MethodName (" identityHashCode" , List (ObjectRef ), IntRef )
2618
2841
val newInstanceSingleName : MethodName =
2619
- MethodName (" newInstance" , List (ClassRef (ClassClass ), IntRef ), ClassRef ( ObjectClass ) )
2842
+ MethodName (" newInstance" , List (ClassRef (ClassClass ), IntRef ), ObjectRef )
2620
2843
val newInstanceMultiName : MethodName =
2621
- MethodName (" newInstance" , List (ClassRef (ClassClass ), ArrayTypeRef (IntRef , 1 )), ClassRef (ObjectClass ))
2844
+ MethodName (" newInstance" , List (ClassRef (ClassClass ), ArrayTypeRef (IntRef , 1 )), ObjectRef )
2845
+
2846
+ private val anonFunctionArities : Map [ClassName , Int ] =
2847
+ (0 to 22 ).map(arity => ClassName (s " scala.scalajs.runtime.AnonFunction $arity" ) -> arity).toMap
2848
+ val allAnonFunctionClasses : Set [ClassName ] =
2849
+ anonFunctionArities.keySet + AnonFunctionXXLClass
2850
+
2851
+ object AnonFunctionClass {
2852
+ def unapply (cls : ClassName ): Option [Int ] =
2853
+ anonFunctionArities.get(cls)
2854
+ }
2855
+
2856
+ lazy val anonFunctionDescriptors : IndexedSeq [NewLambda .Descriptor ] = {
2857
+ anonFunctionArities.toIndexedSeq.sortBy(_._2).map { case (className, arity) =>
2858
+ NewLambda .Descriptor (
2859
+ superClass = className,
2860
+ interfaces = Nil ,
2861
+ methodName = MethodName (applySimpleName, List .fill(arity)(ObjectRef ), ObjectRef ),
2862
+ paramTypes = List .fill(arity)(AnyType ),
2863
+ resultType = AnyType
2864
+ )
2865
+ }
2866
+ }
2867
+
2868
+ lazy val anonFunctionXXLDescriptor : NewLambda .Descriptor = {
2869
+ NewLambda .Descriptor (
2870
+ superClass = AnonFunctionXXLClass ,
2871
+ interfaces = Nil ,
2872
+ methodName = MethodName (applySimpleName, List (ObjectArrayType .arrayTypeRef), ObjectRef ),
2873
+ paramTypes = List (ObjectArrayType ),
2874
+ resultType = AnyType
2875
+ )
2876
+ }
2622
2877
}
2623
2878
2624
2879
private class OptionBuilder [T ] {
0 commit comments