8000 WiP Squash: Introduce NewLambda to synthesize instances of SAM types. · scala-js/scala-js@58fe5f1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 58fe5f1

Browse files
committed
WiP Squash: Introduce NewLambda to synthesize instances of SAM types.
The `NewLambda` node creates an instance of an anonymous class from a `Descriptor` and a `fun` of a closure type. The `Descriptor` specifies the shape of the anonymous class: a super class, a list of interfaces to implement, and the name of a single non-constructor method to provide. The body of the method calls the `fun` closure. At link time, the Analyzer and BaseLinker synthesize a unique such anonymous class per `Descriptor`. In practice, all the lambdas for a given target type share a common `Descriptor`. This is notably for all the Scala functions of arity N. `NewLambda` replaces the need for special `AnonFunctionN` or `TypedFunctionN` classes in the library. Instead, classes of the right shape are synthesized at link-time. The scheme can also be used for most LambdaMetaFactory-style lambdas, although our `NewLambda` does not support bridge generation. We do not yet exploit this capability. --- This is *very* WiP. It is good enough to discuss the IR and compiler changes. The implementation on the linker side is currently butchered at the moment.
1 parent 62bff46 commit 58fe5f1

File tree

21 files changed

+491
-240
lines changed

21 files changed

+491
-240
lines changed

compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6370,11 +6370,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
63706370
// Wrap the closure in the appropriate box for the SAM type
63716371
val funSym = originalFunction.tpe.typeSymbolDirect
63726372
if (isFunctionSymbol(funSym)) {
6373-
/* This is a scala.FunctionN. We use the existing AnonFunctionN
6374-
* wrapper.
6373+
/* This is a scala.FunctionN. We use a NewLambda.
63756374
*/
6376-
js.New(ir.Names.ClassName("scala.scalajs.runtime.TypedFunction" + arity),
6377-
js.MethodIdent(ctorName), List(closure))
6375+
val superClass = ir.Names.ClassName("scala.runtime.AbstractFunction" + arity)
6376+
val objectClassRef = jstpe.ClassRef(ir.Names.ObjectClass)
6377+
val applyMethodName = ir.Names.MethodName(ir.Names.SimpleMethodName("apply"),
6378+
List.fill(arity)(objectClassRef), objectClassRef)
6379+
val descriptor = js.NewLambda.Descriptor(superClass, Nil, applyMethodName)
6380+
js.NewLambda(descriptor, closure)(jstpe.ClassType(superClass, nullable = false))
63786381
} else {
63796382
/* This is an arbitrary SAM type (can only happen in 2.12).
63806383
* We have to synthesize a class like LambdaMetaFactory would do on

examples/helloworld/src/main/scala/helloworld/HelloWorld.scala

Lines changed: 3 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -10,86 +10,8 @@ import js.annotation._
1010

1111
object HelloWorld {
1212
def main(args: Array[String]): Unit = {
13-
import js.DynamicImplicits.truthValue
14-
15-
if (js.typeOf(js.Dynamic.global.document) != "undefined" &&
16-
js.Dynamic.global.document &&
17-
js.Dynamic.global.document.getElementById("playground")) {
18-
sayHelloFromDOM()
19-
sayHelloFromTypedDOM()
20-
sayHelloFromJQuery()
21-
sayHelloFromTypedJQuery()
22-
} else {
23-
println("Hello world!")
24-
}
25-
}
26-
27-
def sayHelloFromDOM(): Unit = {
28-
val document = js.Dynamic.global.document
29-
val playground = document.getElementById("playground")
30-
31-
val newP = document.createElement("p")
32-
newP.innerHTML = "Hello world! <i>-- DOM</i>"
33-
playground.appendChild(newP)
34-
}
35-
36-
def sayHelloFromTypedDOM(): Unit = {
37-
val document = window.document
38-
val playground = document.getElementById("playground")
39-
40-
val newP = document.createElement("p")
41-
newP.innerHTML = "Hello world! <i>-- typed DOM</i>"
42-
playground.appendChild(newP)
13+
val list = 1 :: 3 :: 6 :: Nil
14+
val xs = list.map(i => i * 2)
15+
xs.foreach(println(_))
4316
}
44-
45-
def sayHelloFromJQuery(): Unit = {
46-
// val $ is fine too, but not very recommended in Scala code
47-
val jQuery = js.Dynamic.global.jQuery
48-
val newP = jQuery("<p>").html("Hello world! <i>-- jQuery</i>")
49-
newP.appendTo(jQuery("#playground"))
50-
}
51-
52-
def sayHelloFromTypedJQuery(): Unit = {
53-
val jQuery = helloworld.JQuery
54-
val newP = jQuery("<p>").html("Hello world! <i>-- typed jQuery</i>")
55-
newP.appendTo(jQuery("#playground"))
56-
}
57-
}
58-
59-
@js.native
60-
@JSGlobalScope
61-
object window extends js.Object {
62-
val document: DOMDocument = js.native
63-
64-
def alert(msg: String): Unit = js.native
65-
}
66-
67-
@js.native
68-
trait DOMDocument extends js.Object {
69-
def getElementById(id: String): DOMElement = js.native
70-
def createElement(tag: String): DOMElement = js.native
71-
}
72-
73-
@js.native
74-
trait DOMElement extends js.Object {
75-
var innerHTML: String = js.native
76-
77-
def appendChild(child: DOMElement): Unit = js.native
78-
}
79-
80-
@js.native
81-
@JSGlobal("jQuery")
82-
object JQuery extends js.Object {
83-
def apply(selector: String): JQuery = js.native
84-
}
85-
86-
@js.native
87-
trait JQuery extends js.Object {
88-
def text(value: String): JQuery = js.native
89-
def text(): String = js.native
90-
91-
def html(value: String): JQuery = js.native
92-
def html(): String = js.native
93-
94-
def appendTo(parent: JQuery): JQuery = js.native
9517
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,15 @@ object Hashers {
316316
mixTree(fun)
317317
mixTrees(args)
318318

319+
case NewLambda(descriptor, fun) =>
320+
import descriptor._
321+
mixTag(TagNewLambda)
322+
mixName(superClass)
323+
mixNames(interfaces)
324+
mixMethodName(method)
325+
mixTree(fun)
326+
mixType(tree.tpe)
327+
319328
case UnaryOp(op, lhs) =>
320329
mixTag(TagUnaryOp)
321330
mixInt(op)
@@ -706,6 +715,11 @@ object Hashers {
706715
def mixName(name: Name): Unit =
707716
mixBytes(name.encoded.bytes)
708717

718+
def mixNames(names: List[Name]): Unit = {
719+
mixInt(names.size)
720+
names.foreach(mixName(_))
721+
}
722+
709723
def mixMethodName(name: MethodName): Unit = {
710724
mixName(name.simpleName)
711725
mixInt(name.paramTypeRefs.size)

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,20 @@ object Printers {
347347
print(fun)
348348
printArgs(args)
349349

350+
case NewLambda(descriptor, fun) =>
351+
import descriptor._
352+
print("<newLambda>(")
353+
print(superClass)
354+
for (intf <- interfaces) {
355+
print(", ")
356+
print(intf)
357+
}
358+
print(", ")
359+
print(method)
360+
print(", ")
361+
print(fun)
362+
print(")")
363+
350364
case UnaryOp(op, lhs) =>
351365
import UnaryOp._
352366

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,15 @@ object Serializers {
371371
writeTagAndPos(TagApplyTypedClosure)
372372
writeApplyFlags(flags); writeTree(fun); writeTrees(args)
373373

374+
case NewLambda(descriptor, fun) =>
375+
import descriptor._
376+
writeTagAndPos(TagNewLambda)
377+
writeName(superClass)
378+
writeNames(interfaces)
379+
writeMethodName(method)
380+
writeTree(fun)
381+
writeType(tree.tpe)
382+
374383
case UnaryOp(op, lhs) =>
375384
writeTagAndPos(TagUnaryOp)
376385
writeByte(op); writeTree(lhs)
@@ -850,6 +859,11 @@ object Serializers {
850859
def writeName(name: Name): Unit =
851860
buffer.writeInt(encodedNameToIndex(name.encoded))
852861

862+
def writeNames(names: List[Name]): Unit = {
863+
buffer.writeInt(names.size)
864+
names.foreach(writeName(_))
865+
}
866+
853867
def writeMethodName(name: MethodName): Unit =
854868
buffer.writeInt(methodNameToIndex(name))
855869

@@ -1275,6 +1289,9 @@ object Serializers {
12751289
readMethodIdent(), readTrees())
12761290
case TagApplyTypedClosure =>
12771291
ApplyTypedClosure(readApplyFlags(), readTree(), readTrees())
1292+
case TagNewLambda =>
1293+
val descriptor = NewLambda.Descriptor(readClassName(), readClassNames(), readMethodName())
1294+
NewLambda(descriptor, readTree())(readType())
12781295

12791296
case TagUnaryOp => UnaryOp(readByte(), readTree())
12801297
case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree())
@@ -2440,6 +2457,9 @@ object Serializers {
24402457
}
24412458
}
24422459

2460+
private def readClassNames(): List[ClassName] =
2461+
List.fill(readInt())(readClassName())
2462+
24432463
private def readMethodName(): MethodName =
24442464
methodNames(readInt())
24452465

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ private[ir] object Tags {
131131
final val TagLinkTimeProperty = TagUnwrapFromThrowable + 1
132132
final val TagApplyTypedClosure = TagLinkTimeProperty + 1
133133
final val TagTypedClosure = TagApplyTypedClosure + 1
134+
final val TagNewLambda = TagTypedClosure + 1
134135

135136
// Tags for member defs
136137

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ object Transformers {
111111
case ApplyTypedClosure(flags, fun, args) =>
112112
ApplyTypedClosure(flags, transformExpr(fun), args.map(transformExpr))
113113

114+
case NewLambda(descriptor, fun) =>
115+
NewLambda(descriptor, transformExpr(fun))(tree.tpe)
116+
114117
case UnaryOp(op, lhs) =>
115118
UnaryOp(op, transformExpr(lhs))
116119

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ object Traversers {
9898
traverse(fun)
9999
args.foreach(traverse)
100100

101+
case NewLambda(_, fun) =>
102+
traverse(fun)
103+
101104
case UnaryOp(op, lhs) =>
102105
traverse(lhs)
103106

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,75 @@ object Trees {
311311
}
312312
}
313313

314+
/** New lambda instance of a SAM class.
315+
*
316+
* The `fun` must have a non-nullable `ClosureType` whose signature matches
317+
* the signature of the given `method`.
318+
*
319+
* Functionally, a `NewLambda` is equivalent to an instance of an anonymous
320+
* class with the following shape:
321+
*
322+
* {{{
323+
* val funV: ((...Ts) => R)! = fun;
324+
* (new superClass with interfaces {
325+
* def <this>() = this.superClass::<init>()
326+
* def method(...args: Ts): R = funV(...args)
327+
* }): tpe
328+
* }}}
329+
*
330+
* where `superClass`, `interfaces` and `method` are taken from the
331+
* `descriptor`.
332+
*
333+
* There must exist `C ∈ { superClass } ∪ interfaces` such that `C!` is a
334+
* subtype of `tpe`.
335+
*
336+
* The uniqueness of the anonymous class and its run-time class name are
337+
* not guaranteed.
338+
*/
339+
sealed case class NewLambda(descriptor: NewLambda.Descriptor, fun: Tree)(
340+
val tpe: Type)(
341+
implicit val pos: Position)
342+
extends Tree
343+
344+
object NewLambda {
345+
final class Descriptor(val superClass: ClassName,
346+
val interfaces: List[ClassName], val method: MethodName) {
347+
348+
private val _hashCode: Int = {
349+
import scala.util.hashing.MurmurHash3._
350+
var acc = 1546348150 // "NewLambda.Descriptor".hashCode()
351+
acc = mix(acc, superClass.##)
352+
acc = mix(acc, interfaces.##)
353+
acc = mixLast(acc, method.##)
354+
finalizeHash(acc, 3)
355+
}
356+
357+
override def equals(that: Any): Boolean = {
358+
(this eq that.asInstanceOf[AnyRef]) || (that match {
359+
case that: Descriptor =>
360+
this._hashCode == that._hashCode && // fail fast on different hash codes
361+
this.superClass == that.superClass &&
362+
this.interfaces == that.interfaces &&
363+
this.method == that.method
364+
case _ =>
365+
false
366+
})
367+
}
368+
369+
override def hashCode(): Int = _hashCode
370+
371+
override def toString(): String =
372+
s"NewLambda.Descriptor($superClass, $interfaces, $method)"
373+
}
374+
375+
object Descriptor {
376+
def apply(superClass: ClassName, interfaces: List[ClassName],
377+
method: MethodName): Descriptor = {
378+
new Descriptor(superClass, interfaces, method)
379+
}
380+
}
381+
}
382+
314383
/** Unary operation.
315384
*
316385
* The `Class_x` operations take a `jl.Class!` argument, i.e., a

linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.scalajs.ir.ClassKind
2424
import org.scalajs.ir.Names._
2525
import org.scalajs.ir.Trees.MemberNamespace
2626
import org.scalajs.ir.Types._
27+
import org.scalajs.ir.Trees.NewLambda
2728

2829
/** Reachability graph produced by the [[Analyzer]].
2930
*
@@ -57,6 +58,7 @@ object Analysis {
5758
def interfaces: scala.collection.Seq[ClassInfo]
5859
def ancestors: scala.collection.Seq[ClassInfo]
5960
def nonExistent: Boolean
61+
def syntheticKind: ClassSyntheticKind
6062
/** For a Scala class, it is instantiated with a `New`; for a JS class,
6163
* its constructor is accessed with a `JSLoadConstructor` or because it
6264
* is needed for a subclass. For modules (Scala or JS), the module is
@@ -87,6 +89,14 @@ object Analysis {
8789
def displayName: String = className.nameString
8890
}
8991

92+
sealed abstract class ClassSyntheticKind
93+
94+
object ClassSyntheticKind {
95+
case object None extends ClassSyntheticKind
96+
97+
final case class Lambda(descriptor: NewLambda.Descriptor) extends ClassSyntheticKind
98+
}
99+
90100
/** Method node in a reachability graph produced by the [[Analyzer]].
91101
*
92102
* Warning: this trait is not meant to be extended by third-party libraries

0 commit comments

Comments
 (0)
0