@@ -1327,8 +1327,15 @@ object Serializers {
1327
1327
1328
1328
case TagDebugger => Debugger ()
1329
1329
1330
- case TagNew => New (readClassName(), readMethodIdent(), readTrees())
1331
- case TagLoadModule => LoadModule (readClassName())
1330
+ case TagNew =>
1331
+ val tree = New (readClassName(), readMethodIdent(), readTrees())
1332
+ if (hacks.useBelow(19 ))
1333
+ anonFunctionNewNodeHackBelow19(tree)
1334
+ else
1335
+ tree
1336
+
1337
+ case TagLoadModule =>
1338
+ LoadModule (readClassName())
1332
1339
1333
1340
case TagStoreModule =>
1334
1341
if (hacks.useBelow(16 )) {
@@ -1649,6 +1656,129 @@ object Serializers {
1649
1656
UnaryOp (UnaryOp .CheckNotNull , expr)
1650
1657
}
1651
1658
1659
+ /** Rewrites `New` nodes of `AnonFunctionN`s coming from before 1.19 into `NewLambda` nodes.
1660
+ *
1661
+ * Before 1.19, the codegen for `scala.FunctionN` lambda was of the following shape:
1662
+ * {{{
1663
+ * new scala.scalajs.runtime.AnonFunctionN(arrow-lambda<...captures>(...args: any): any = {
1664
+ * body
1665
+ * })
1666
+ * }}}
1667
+ *
1668
+ * This function rewrites such calls to `NewLambda` nodes, using the new
1669
+ * definition of these classes:
1670
+ * {{{
1671
+ * <newLambda>(scala.scalajs.runtime.AnonFunctionN,
1672
+ * apply;Ljava.lang.Object;...;Ljava.lang.Object,
1673
+ * any, any, (typed-lambda<...captures>(...args: any): any = {
1674
+ * body
1675
+ * }))
1676
+ * }}}
1677
+ *
1678
+ * The rewrite ensures that previously published lambdas get the same
1679
+ * optimizations on Wasm as those recompiled with 1.19+.
1680
+ *
1681
+ * The rewrite also applies to Scala 3's `AnonFunctionXXL` classes, with
1682
+ * an additional adaptation of the parameter's type. It rewrites
1683
+ * {{{
1684
+ * new scala.scalajs.runtime.AnonFunctionXXL(arrow-lambda<...captures>(argArray: any): any = {
1685
+ * body
1686
+ * })
1687
+ * }}}
1688
+ * to
1689
+ * {{{
1690
+ * <newLambda>(scala.scalajs.runtime.AnonFunctionXXL,
1691
+ * apply;Ljava.lang.Object[];Ljava.lang.Object,
1692
+ * any, any, (typed-lambda<...captures>(argArray: jl.Object[]): any = {
1693
+ * newBody
1694
+ * }))
1695
+ * }}}
1696
+ * where `newBody` is `body` transformed to adapt the type of `argArray`
1697
+ * everywhere.
1698
+ *
1699
+ * Tests are in `sbt-plugin/src/sbt-test/linker/anonfunction-compat/`.
1700
+ *
1701
+ * ---
1702
+ *
1703
+ * In case the argument is not an arrow-lambda of the expected shape, we
1704
+ * use a fallback. This never happens for our published codegens, but
1705
+ * could happen for other valid IR. We rewrite
1706
+ * {{{
1707
+ * new scala.scalajs.runtime.AnonFunctionN(jsFunctionArg)
1708
+ * }}}
1709
+ * to
1710
+ * {{{
1711
+ * <newLambda>(scala.scalajs.runtime.AnonFunctionN,
1712
+ * apply;Ljava.lang.Object;...;Ljava.lang.Object,
1713
+ * any, any, (typed-lambda<f: any = jsFunctionArg>(...args: any): any = {
1714
+ * f(...args)
1715
+ * }))
1716
+ * }}}
1717
+ *
1718
+ * This code path is not tested in the CI, but can be locally tested by
1719
+ * commenting out the `case Closure(...) =>`.
1720
+ */
1721
+ private def anonFunctionNewNodeHackBelow19 (tree : New ): Tree = {
1722
+ tree match {
1723
+ case New (cls, _, funArg :: Nil ) =>
1724
+ def makeFallbackTypedClosure (paramTypes : List [Type ]): Closure = {
1725
+ implicit val pos = funArg.pos
1726
+ val fParamDef = ParamDef (LocalIdent (LocalName (" f" )), NoOriginalName , AnyType , mutable = false )
1727
+ val xParamDefs = paramTypes.zipWithIndex.map { case (ptpe, i) =>
1728
+ ParamDef (LocalIdent (LocalName (s " x $i" )), NoOriginalName , ptpe, mutable = false )
1729
+ }
1730
+ Closure (ClosureFlags .typed, List (fParamDef), xParamDefs, None , AnyType ,
1731
+ JSFunctionApply (fParamDef.ref, xParamDefs.map(_.ref)),
1732
+ List (funArg))
1733
+ }
1734
+
1735
+ cls match {
1736
+ case HackNames .AnonFunctionClass (arity) =>
1737
+ val typedClosure = funArg match {
1738
+ // The shape produced by our earlier compilers, which we can optimally rewrite
1739
+ case Closure (ClosureFlags .arrow, captureParams, params, None , AnyType , body, captureValues)
1740
+ if params.lengthCompare(arity) == 0 =>
1741
+ Closure (ClosureFlags .typed, captureParams, params, None , AnyType ,
1742
+ body, captureValues)(funArg.pos)
1743
+
1744
+ // Fallback for other shapes (theoretically required; dead code in practice)
1745
+ case _ =>
1746
+ makeFallbackTypedClosure(List .fill(arity)(AnyType ))
1747
+ }
1748
+
1749
+ NewLambda (HackNames .anonFunctionDescriptors(arity), typedClosure)(tree.tpe)(tree.pos)
1750
+
1751
+ case HackNames .AnonFunctionXXLClass =>
1752
+ val typedClosure = funArg match {
1753
+ // The shape produced by our earlier compilers, which we can optimally rewrite
1754
+ case Closure (ClosureFlags .arrow, captureParams, oldParam :: Nil , None , AnyType , body, captureValues) =>
1755
+ // Here we need to adapt the type of the parameter from `any` to `jl.Object[]`.
1756
+ val newParam = oldParam.copy(ptpe = HackNames .ObjectArrayType )(oldParam.pos)
1757
+ val newBody = new Transformers .LocalScopeTransformer {
1758
+ override def transform (tree : Tree ): Tree = tree match {
1759
+ case tree @ VarRef (newParam.name.name) => tree.copy()(newParam.ptpe)(tree.pos)
1760
+ case _ => super .transform(tree)
1761
+ }
1762
+ }.transform(body)
1763
+ Closure (ClosureFlags .typed, captureParams, List (newParam), None , AnyType ,
1764
+ newBody, captureValues)(funArg.pos)
1765
+
1766
+ // Fallback for other shapes (theoretically required; dead code in practice)
1767
+ case _ =>
1768
+ makeFallbackTypedClosure(List (HackNames .ObjectArrayType ))
1769
+ }
1770
+
1771
+ NewLambda (HackNames .anonFunctionXXLDescriptor, typedClosure)(tree.tpe)(tree.pos)
1772
+
1773
+ case _ =>
1774
+ tree
1775
+ }
1776
+
1777
+ case _ =>
1778
+ tree
1779
+ }
1780
+ }
1781
+
1652
1782
def readTrees (): List [Tree ] =
1653
1783
List .fill(readInt())(readTree())
1654
1784
@@ -1751,10 +1881,15 @@ object Serializers {
1751
1881
val jsNativeMembers = jsNativeMembersBuilder.result()
1752
1882
val componentNativeMembers = componentNativeMembersBuilder.result()
1753
1883
1754
- ClassDef (name, originalName, kind, jsClassCaptures, superClass, parents,
1884
+ val classDef = ClassDef (name, originalName, kind, jsClassCaptures, superClass, parents,
1755
1885
jsSuperClass, jsNativeLoadSpec, fields, methods, jsConstructor,
1756
1886
jsMethodProps, jsNativeMembers, componentNativeMembers, topLevelExportDefs)(
1757
1887
optimizerHints)
1888
+
1889
+ if (hacks.useBelow(19 ))
1890
+ anonFunctionClassDefHackBelow19(classDef)
1891
+ else
1892
+ classDef
1758
1893
}
1759
1894
1760
1895
private def jlClassMethodsHackBelow17 (methods : List [MethodDef ]): List [MethodDef ] = {
@@ -2041,6 +2176,89 @@ object Serializers {
2041
2176
(jsConstructorBuilder.result(), jsMethodPropsBuilder.result())
2042
2177
}
2043
2178
2179
+ /** Rewrites `scala.scalajs.runtime.AnonFunctionN`s from before 1.19.
2180
+ *
2181
+ * Before 1.19, these classes were defined as
2182
+ * {{{
2183
+ * // final in source code
2184
+ * class AnonFunctionN extends AbstractFunctionN {
2185
+ * val f: any
2186
+ * def this(f: any) = {
2187
+ * this.f = f;
2188
+ * super()
2189
+ * }
2190
+ * def apply(...args: any): any = f(...args)
2191
+ * }
2192
+ * }}}
2193
+ *
2194
+ * Starting with 1.19, they were rewritten to be used as SAM classes for
2195
+ * `NewLambda` nodes. The new IR shape is
2196
+ * {{{
2197
+ * // sealed abstract in source code
2198
+ * class AnonFunctionN extends AbstractFunctionN {
2199
+ * def this() = super()
2200
+ * }
2201
+ * }}}
2202
+ *
2203
+ * This function rewrites those classes to the new shape.
2204
+ *
2205
+ * The rewrite also applies to Scala 3's `AnonFunctionXXL`.
2206
+ *
2207
+ * Tests are in `sbt-plugin/src/sbt-test/linker/anonfunction-compat/`.
2208
+ */
2209
+ private def anonFunctionClassDefHackBelow19 (classDef : ClassDef ): ClassDef = {
2210
+ import classDef ._
2211
+
2212
+ if (! HackNames .allAnonFunctionClasses.contains(className)) {
2213
+ classDef
2214
+ } else {
2215
+ val newCtor : MethodDef = {
2216
+ // Find the old constructor to get its position and version
2217
+ val oldCtor = methods.find(_.methodName.isConstructor).getOrElse {
2218
+ throw new InvalidIRException (classDef,
2219
+ s " Did not find a constructor in ${className.nameString}" )
2220
+ }
2221
+ implicit val pos = oldCtor.pos
2222
+
2223
+ // constructor def <init>() = this.superClass::<init>()
2224
+ MethodDef (
2225
+ MemberFlags .empty.withNamespace(MemberNamespace .Constructor ),
2226
+ MethodIdent (NoArgConstructorName ),
2227
+ NoOriginalName ,
2228
+ Nil ,
2229
+ VoidType ,
2230
+ Some {
2231
+ ApplyStatically (
2232
+ ApplyFlags .empty.withConstructor(true ),
2233
+ This ()(ClassType (className, nullable = false )),
2234
+ superClass.get.name,
2235
+ MethodIdent (NoArgConstructorName ),
2236
+ Nil
2237
+ )(VoidType )
2238
+ }
2239
+ )(OptimizerHints .empty, oldCtor.version)
2240
+ }
2241
+
2242
+ ClassDef (
2243
+ name,
2244
+ originalName,
2245
+ kind,
2246
+ jsClassCaptures,
2247
+ superClass,
2248
+ interfaces,
2249
+ jsSuperClass,
2250
+ jsNativeLoadSpec,
2251
+ fields = Nil , // throws away the `f` field
2252
+ methods = List (newCtor), // throws away the old constructor and `apply` method
2253
+ jsConstructor,
2254
+ jsMethodProps,
2255
+ jsNativeMembers,
2256
+ componentNativeMembers,
2257
+ topLevelExportDefs
2258
+ )(OptimizerHints .empty)(pos) // throws away the `@inline`
2259
+ }
2260
+ }
2261
+
2044
2262
private def readFieldDef ()(implicit pos : Position ): FieldDef = {
2045
2263
val flags = MemberFlags .fromBits(readInt())
2046
2264
val name = readFieldIdentForEnclosingClass()
@@ -2800,6 +3018,8 @@ object Serializers {
2800
3018
2801
3019
/** Names needed for hacks. */
2802
3020
private object HackNames {
3021
+ val AnonFunctionXXLClass =
3022
+ ClassName (" scala.scalajs.runtime.AnonFunctionXXL" ) // from the Scala 3 library
2803
3023
val CloneNotSupportedExceptionClass =
2804
3024
ClassName (" java.lang.CloneNotSupportedException" )
2805
3025
val SystemModule : ClassName =
@@ -2809,14 +3029,50 @@ object Serializers {
2809
3029
val ReflectArrayModClass =
2810
3030
ClassName (" java.lang.reflect.Array$" )
2811
3031
3032
+ val ObjectArrayType = ArrayType (ArrayTypeRef (ObjectRef , 1 ), nullable = true )
3033
+
3034
+ private val applySimpleName = SimpleMethodName (" apply" )
3035
+
2812
3036
val cloneName : MethodName =
2813
- MethodName (" clone" , Nil , ClassRef ( ObjectClass ) )
3037
+ MethodName (" clone" , Nil , ObjectRef )
2814
3038
val identityHashCodeName : MethodName =
2815
- MethodName (" identityHashCode" , List (ClassRef ( ObjectClass ) ), IntRef )
3039
+ MethodName (" identityHashCode" , List (ObjectRef ), IntRef )
2816
3040
val newInstanceSingleName : MethodName =
2817
- MethodName (" newInstance" , List (ClassRef (ClassClass ), IntRef ), ClassRef ( ObjectClass ) )
3041
+ MethodName (" newInstance" , List (ClassRef (ClassClass ), IntRef ), ObjectRef )
2818
3042
val newInstanceMultiName : MethodName =
2819
- MethodName (" newInstance" , List (ClassRef (ClassClass ), ArrayTypeRef (IntRef , 1 )), ClassRef (ObjectClass ))
3043
+ MethodName (" newInstance" , List (ClassRef (ClassClass ), ArrayTypeRef (IntRef , 1 )), ObjectRef )
3044
+
3045
+ private val anonFunctionArities : Map [ClassName , Int ] =
3046
+ (0 to 22 ).map(arity => ClassName (s " scala.scalajs.runtime.AnonFunction $arity" ) -> arity).toMap
3047
+ val allAnonFunctionClasses : Set [ClassName ] =
3048
+ anonFunctionArities.keySet + AnonFunctionXXLClass
3049
+
3050
+ object AnonFunctionClass {
3051
+ def unapply (cls : ClassName ): Option [Int ] =
3052
+ anonFunctionArities.get(cls)
3053
+ }
3054
+
3055
+ lazy val anonFunctionDescriptors : IndexedSeq [NewLambda .Descriptor ] = {
3056
+ anonFunctionArities.toIndexedSeq.sortBy(_._2).map { case (className, arity) =>
3057
+ NewLambda .Descriptor (
3058
+ superClass = className,
3059
+ interfaces = Nil ,
3060
+ methodName = MethodName (applySimpleName, List .fill(arity)(ObjectRef ), ObjectRef ),
3061
+ paramTypes = List .fill(arity)(AnyType ),
3062
+ resultType = AnyType
3063
+ )
3064
+ }
3065
+ }
3066
+
3067
+ lazy val anonFunctionXXLDescriptor : NewLambda .Descriptor = {
3068
+ NewLambda .Descriptor (
3069
+ superClass = AnonFunctionXXLClass ,
3070
+ interfaces = Nil ,
3071
+ methodName = MethodName (applySimpleName, List (ObjectArrayType .arrayTypeRef), ObjectRef ),
3072
+ paramTypes = List (ObjectArrayType ),
3073
+ resultType = AnyType
3074
+ )
3075
+ }
2820
3076
}
2821
3077
2822
3078
private class OptionBuilder [T ] {
0 commit comments