8000 Merge pull request #5122 from sjrd/ir-patch-old-anon-functions · scala-js/scala-js@d504303 · GitHub
[go: up one dir, main page]

Skip to content

Commit d504303

Browse files
authored
Merge pull request #5122 from sjrd/ir-patch-old-anon-functions
Rewrite old IR with AnonFunctionN references to use NewLambda.
2 parents e507300 + 9481522 commit <
8000
span class="fgColor-default">d504303

File tree

11 files changed

+455
-99
lines changed

11 files changed

+455
-99
lines changed

ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala

Lines changed: 262 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,8 +1226,15 @@ object Serializers {
12261226

12271227
case TagDebugger => Debugger()
12281228

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())
12311238

12321239
case TagStoreModule =>
12331240
if (hacks.useBelow(16)) {
@@ -1542,6 +1549,129 @@ object Serializers {
15421549
UnaryOp(UnaryOp.CheckNotNull, expr)
15431550
}
15441551

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+
15451675
def readTrees(): List[Tree] =
15461676
List.fill(readInt())(readTree())
15471677

@@ -1641,10 +1771,15 @@ object Serializers {
16411771

16421772
val jsNativeMembers = jsNativeMembersBuilder.result()
16431773

1644-
ClassDef(name, originalName, kind, jsClassCaptures, superClass, parents,
1774+
val classDef = ClassDef(name, originalName, kind, jsClassCaptures, superClass, parents,
16451775
jsSuperClass, jsNativeLoadSpec, fields, methods, jsConstructor,
16461776
jsMethodProps, jsNativeMembers, topLevelExportDefs)(
16471777
optimizerHints)
1778+
1779+
if (hacks.useBelow(19))
1780+
anonFunctionClassDefHackBelow19(classDef)
1781+
else
1782+
classDef
16481783
}
16491784

16501785
private def jlClassMethodsHackBelow17(methods: List[MethodDef]): List[MethodDef] = {
@@ -1931,6 +2066,88 @@ object Serializers {
19312066
(jsConstructorBuilder.result(), jsMethodPropsBuilder.result())
19322067
}
19332068

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+
19342151
private def readFieldDef()(implicit pos: Position): FieldDef = {
19352152
val flags = MemberFlags.fromBits(readInt())
19362153
val name = readFieldIdentForEnclosingClass()
@@ -2602,6 +2819,8 @@ object Serializers {
26022819

26032820
/** Names needed for hacks. */
26042821
private object HackNames {
2822+
val AnonFunctionXXLClass =
2823+
ClassName("scala.scalajs.runtime.AnonFunctionXXL") // from the Scala 3 library
26052824
val CloneNotSupportedExceptionClass =
26062825
ClassName("java.lang.CloneNotSupportedException")
26072826
val SystemModule: ClassName =
@@ -2611,14 +2830,50 @@ object Serializers {
26112830
val ReflectArrayModClass =
26122831
ClassName("java.lang.reflect.Array$")
26132832

2833+
val ObjectArrayType = ArrayType(ArrayTypeRef(ObjectRef, 1), nullable = true)
2834+
2835+
private val applySimpleName = SimpleMethodName("apply")
2836+
26142837
val cloneName: MethodName =
2615-
MethodName("clone", Nil, ClassRef(ObjectClass))
2838+
MethodName("clone", Nil, ObjectRef)
26162839
val identityHashCodeName: MethodName =
2617-
MethodName("identityHashCode", List(ClassRef(ObjectClass)), IntRef)
2840+
MethodName("identityHashCode", List(ObjectRef), IntRef)
26182841
val newInstanceSingleName: MethodName =
2619-
MethodName("newInstance", List(ClassRef(ClassClass), IntRef), ClassRef(ObjectClass))
2842+
MethodName("newInstance", List(ClassRef(ClassClass), IntRef), ObjectRef)
26202843
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+
}
26222877
}
26232878

26242879
private class OptionBuilder[T] {

0 commit comments

Comments
 (0)
0