8000 SyntheticClassKind is pure data; LambdaSynthesizer creates trees expl… · scala-js/scala-js@7846728 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7846728

Browse files
committed
SyntheticClassKind is pure data; LambdaSynthesizer creates trees explicitly.
1 parent b361a1d commit 7846728

File tree

6 files changed

+229
-191
lines changed

6 files changed

+229
-191
lines changed

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

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import org.scalajs.ir.WellKnownNames._
3131

3232
import org.scalajs.linker._
3333
import org.scalajs.linker.checker.CheckingPhase
34-
import org.scalajs.linker.frontend.{IRLoader, SyntheticClassKind}
34+
import org.scalajs.linker.frontend.{IRLoader, Lambda 8000 Synthesizer, SyntheticClassKind}
3535
import org.scalajs.linker.interface._
3636
import org.scalajs.linker.interface.unstable.ModuleInitializerImpl
3737
import org.scalajs.linker.standard._
@@ -115,12 +115,11 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
115115

116116
def classInfos: scala.collection.Map[ClassName, Analysis.ClassInfo] = _classInfos
117117

118-
/** Cache of synthetic kinds for lambda classes that are attached to this class.
119-
*
120-
* This is mostly important so that we do not recompute their `className`
121-
* every time. That computation includes a complex hash of the descriptor.
118+
/* Cache the names generated for lambda classes because computing their
119+
* `ClassName` is a bit expensive. The constructor names are not expensive,
120+
* but we might as well cache them together.
122121
*/
123-
private val _lambdaSyntheticKinds: mutable.Map[NewLambda.Descriptor, SyntheticClassKind.Lambda] =
122+
private val syntheticLambdaNamesCache: mutable.Map[NewLambda.Descriptor, (ClassName, MethodName)] =
124123
emptyThreadSafeMap
125124

126125
private val _classSuperClassUsed = new AtomicBoolean(false)
@@ -329,9 +328,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
329328
lookupOrSynthesizeClassCommon(className, None)(onSuccess)
330329
}
331330

332-
private def lookupOrSynthesizeClass(syntheticKind: SyntheticClassKind)(
331+
private def lookupOrSynthesizeClass(className: ClassName, syntheticKind: SyntheticClassKind)(
333332
onSuccess: ClassInfo => Unit)(implicit from: From): Unit = {
334-
lookupOrSynthesizeClassCommon(syntheticKind.className, Some(syntheticKind))(onSuccess)
333+
lookupOrSynthesizeClassCommon(className, Some(syntheticKind))(onSuccess)
335334
}
336335

337336
private def lookupOrSynthesizeClassCommon(className: ClassName,
@@ -399,8 +398,8 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
399398
doLoad(data, loading, syntheticKind, nonExistent = maybeInfo.isEmpty)
400399
}
401400

402-
case Some(kind) =>
403-
val data = kind.synthesizedInfo
401+
case Some(SyntheticClassKind.Lambda(descriptor)) =>
402+
val data = LambdaSynthesizer.makeClassInfo(descriptor, className)
404403
doLoad(data, loading, syntheticKind, nonExistent = false)
405404
}
406405

@@ -1482,13 +1481,13 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
14821481

14831482
if (data.lambdaDescriptorsUsed.nonEmpty) {
14841483
for (descriptor <- data.lambdaDescriptorsUsed) {
1485-
val syntheticKind = _lambdaSyntheticKinds.getOrElseUpdate(descriptor, {
1486-
SyntheticClassKind.Lambda(descriptor)
1484+
val (className, ctorName) = syntheticLambdaNamesCache.getOrElseUpdate(descriptor, {
1485+
(LambdaSynthesizer.makeClassName(descriptor), LambdaSynthesizer.makeConstructorName(descriptor))
14871486
})
14881487

1489-
lookupOrSynthesizeClass(syntheticKind) { lambdaClassInfo =>
1488+
lookupOrSynthesizeClass(className, SyntheticClassKind.Lambda(descriptor)) { lambdaClassInfo =>
14901489
lambdaClassInfo.instantiated()
1491-
lambdaClassInfo.callMethodStatically(MemberNamespace.Constructor, syntheticKind.ctorName)
1490+
lambdaClassInfo.callMethodStatically(MemberNamespace.Constructor, ctorName)
14921491
}
14931492
}
14941493
}

linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ final class BaseLinker(config: CommonPhaseConfig, checkIR: Boolean) {
8080
val (version, classDefFuture) = info.syntheticKind match {
8181
case None =>
8282
(irLoader.irFileVersion(info.className), irLoader.loadClassDef(info.className))
83-
case Some(syntheticKind) =>
84-
(SyntheticClassKind.constantVersion, Future.successful(syntheticKind.synthesizedClassDef))
83+
case Some(SyntheticClassKind.Lambda(descriptor)) =>
84+
// Not cached; measurements suggest it takes only a few ms for all synthesized classes combined
85+
val classDef = LambdaSynthesizer.makeClassDef(descriptor, info.className)
86+
(LambdaSynthesizer.constantVersion, Future.successful(classDef))
8587
}
8688
val syntheticMethodsFuture = methodSynthesizer.synthesizeMembers(info, analysis)
8789

linker/shared/src/main/scala/org/scalajs/linker/frontend/Desugarer.scala

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,17 @@ private[linker] object Desugarer {
121121
private final class DesugarTransformer(coreSpec: CoreSpec)
122122
extends ClassTransformer {
123123

124-
// Cache SyntheticClassKinds because computing their `ClassName` is a bit expensive
125-
private val syntheticLambdaKindsCache =
126-
mutable.Map.empty[NewLambda.Descriptor, SyntheticClassKind.Lambda]
124+
/* Cache the names generated for lambda classes because computing their
125+
* `ClassName` is a bit expensive. The constructor names are not expensive,
126+
* but we might as well cache them together.
127+
*/
128+
private val syntheticLambdaNamesCache =
129+
mutable.Map.empty[NewLambda.Descriptor, (ClassName, MethodName)]
127130

128-
private def syntheticLambdaKindFor(descriptor: NewLambda.Descriptor): SyntheticClassKind.Lambda =
129-
syntheticLambdaKindsCache.getOrElseUpdate(descriptor, SyntheticClassKind.Lambda(descriptor))
131+
private def syntheticLambdaNamesFor(descriptor: NewLambda.Descriptor): (ClassName, MethodName) =
132+
syntheticLambdaNamesCache.getOrElseUpdate(descriptor, {
133+
(LambdaSynthesizer.makeClassName(descriptor), LambdaSynthesizer.makeConstructorName(descriptor))
134+
})
130135

131136
override def transform(tree: Tree): Tree = {
132137
tree match {
@@ -135,9 +140,8 @@ private[linker] object Desugarer {
135140

136141
case NewLambda(descriptor, fun) =>
137142
implicit val pos = tree.pos
138-
val syntheticKind = syntheticLambdaKindFor(descriptor)
139-
New(syntheticKind.className, MethodIdent(syntheticKind.ctorName),
140-
List(transform(fun)))
143+
val (className, ctorName) = syntheticLambdaNamesFor(descriptor)
144+
New(className, MethodIdent(ctorName), List(transform(fun)))
141145

142146
case _ =>
143147
super.transform(tree)
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.linker.frontend
14+
15+
import org.scalajs.ir.{ClassKind, Hashers, Position, SHA1, UTF8String, Version}
16+
import org.scalajs.ir.Names._
17+
import org.scalajs.ir.OriginalName.NoOriginalName
18+
import org.scalajs.ir.Trees._
19+
import org.scalajs.ir.Types._
20+
import org.scalajs.ir.WellKnownNames._
21+
22+
import org.scalajs.linker.analyzer.Infos._
23+
24+
private[linker] object LambdaSynthesizer {
25+
/* Everything we create has a constant version because the names are derived
26+
* from the descriptors themselves.
27+
*/
28+
val constantVersion = Version.fromByte(0)
29+
30+
private val ClosureTypeRefName = LabelName("c")
31+
32+
/** Deterministically makes a class name for the lambda class given its descriptor.
33+
*
34+
* This computation is mildly expensive. Callers should cache it if possible.
35+
*/
36+
def makeClassName(descriptor: NewLambda.Descriptor): ClassName = {
37+
// Choose a base class name that will "makes sense" for debugging purposes
38+
val baseClassName = {
39+
if (descriptor.superClass == ObjectClass && descriptor.interfaces.nonEmpty)
40+
descriptor.interfaces.head
41+
else
42+
descriptor.superClass
43+
}
44+
45+
val digestBuilder = new SHA1.DigestBuilder()
46+
digestBuilder.updateUTF8String(descriptor.superClass.encoded)
47+
for (intf <- descriptor.interfaces)
48+
digestBuilder.updateUTF8String(intf.encoded)
49+
50+
// FIXME This is not efficient
51+
digestBuilder.updateUTF8String(UTF8String(descriptor.methodName.nameString))
52+
53+
// No need the hash the paramTypes and resultType because they derive from the method name
54+
55+
val digest = digestBuilder.finalizeDigest()
56+
57+
/* The "$$Lambda" segment is meant to match the way LambdaMetaFactory
58+
* names generated classes. This is mostly for test compatibility
59+
* purposes (like partest's that test the class name to tell whether a
60+
* lambda was indeed encoded as an LMF).
61+
*/
62+
val suffixBuilder = new java.lang.StringBuilder(".$$Lambda$")
63+
for (b <- digest) {
64+
val i = b & 0xff
65+
suffixBuilder.append(Character.forDigit(i >> 4, 16)).append(Character.forDigit(i & 0x0f, 16))
66+
}
67+
68+
ClassName(baseClassName.encoded ++ UTF8String(suffixBuilder.toString()))
69+
}
70+
71+
/** Computes the constructor name for the lambda class of a descriptor. */
72+
def makeConstructorName(descriptor: NewLambda.Descriptor): MethodName = {
73+
val closureTypeNonNull =
74+
ClosureType(descriptor.paramTypes, descriptor.resultType, nullable = false)
75+
MethodName.constructor(TransientTypeRef(ClosureTypeRefName)(closureTypeNonNull) :: Nil)
76+
}
77+
78+
/** Computes the `ClassInfo` of a lambda class, for use by the `Analyzer`.
79+
*
80+
* The `className` must be the result of `makeClassName(descriptor)`.
81+
*/
82+
def makeClassInfo(descriptor: NewLambda.Descriptor, className: ClassName): ClassInfo = {
83+
val methodInfos = Array.fill(MemberNamespace.Count)(Map.empty[MethodName, MethodInfo])
84+
85+
val fFieldName = FieldName(className, SimpleFieldName("f"))
86+
val ctorName = makeConstructorName(descriptor)
87+
88+
val ctorInfo: MethodInfo = {
89+
val b = new ReachabilityInfoBuilder(constantVersion)
90+
b.addFieldWritten(fFieldName)
91+
b.addMethodCalledStatically(descriptor.superClass,
92+
NamespacedMethodName(MemberNamespace.Constructor, NoArgConstructorName))
93+
MethodInfo(isAbstract = false, b.result())
94+
}
95+
methodInfos(MemberNamespace.Constructor.ordinal) =
96+
Map(ctorName -> ctorInfo)
97+
98+
val implMethodInfo: MethodInfo = {
99+
val b = new ReachabilityInfoBuilder(constantVersion)
100+
b.addFieldRead(fFieldName)
101+
MethodInfo(isAbstract = false, b.result())
102+
}
103+
methodInfos(MemberNamespace.Public.ordinal) =
104+
Map(descriptor.methodName -> implMethodInfo)
105+
106+
new ClassInfo(className, ClassKind.Class,
107+
Some(descriptor.superClass), descriptor.interfaces,
108+
jsNativeLoadSpec = None, referencedFieldClasses = Map.empty, methodInfos,
109+
jsNativeMembers = Map.empty, jsMethodProps = Nil, topLevelExports = Nil)
110+
}
111+
112+
/** Synthesizes the `ClassDef` for a lambda class, for use by the `BaseLinker`.
113+
*
114+
* The `className` must be the result of `makeClassName(descriptor)`.
115+
*/
116+
def makeClassDef(descriptor: NewLambda.Descriptor, className: ClassName): ClassDef = {
117+
implicit val pos = Position.NoPosition
118+
119+
import descriptor._
120+
121+
val closureType = ClosureType(paramTypes, resultType, nullable = true)
122+
123+
val thiz = This()(ClassType(className, nullable = false))
124+
125+
val fFieldIdent = FieldIdent(FieldName(className, SimpleFieldName("f")))
126+
val fFieldDef = FieldDef(MemberFlags.empty, fFieldIdent, NoOriginalName, closureType)
127+
val fFieldSelect = Select(thiz, fFieldIdent)(closureType)
128+
129+
val ctorParamDef = ParamDef(LocalIdent(LocalName("f")), NoOriginalName,
130+
closureType.toNonNullable, mutable = false)
131+
val ctorDef = MethodDef(
132+
MemberFlags.empty.withNamespace(MemberNamespace.Constructor),
133+
MethodIdent(makeConstructorName(descriptor)),
134+
NoOriginalName,
135+
ctorParamDef :: Nil,
136+
VoidType,
137+
Some(
138+
Block(
139+
Assign(fFieldSelect, ctorParamDef.ref),
140+
ApplyStatically(ApplyFlags.empty.withConstructor(true), thiz,
141+
superClass, MethodIdent(NoArgConstructorName), Nil)(VoidType)
142+
)
143+
)
144+
)(OptimizerHints.empty, constantVersion)
145+
146+
val methodParamDefs = paramTypes.zipWithIndex.map { case (paramType, index) =>
147+
ParamDef(LocalIdent(LocalName("x" + index)), NoOriginalName, paramType, mutable = false)
148+
}
149+
val methodDef = MethodDef(
150+
MemberFlags.empty,
151+
MethodIdent(methodName),
152+
NoOriginalName,
153+
methodParamDefs,
154+
resultType,
155+
Some(
156+
ApplyTypedClosure(ApplyFlags.empty, fFieldSelect, methodParamDefs.map(_.ref))
157+
)
158+
)(OptimizerHints.empty, constantVersion)
159+
160+
val classDef = ClassDef(
161+
ClassIdent(className),
162+
NoOriginalName,
163+
ClassKind.Class,
164+
jsClassCaptures = None,
165+
superClass = Some(ClassIdent(superClass)),
166+
interfaces = interfaces.map(ClassIdent(_)),
167+
jsSuperClass = None,
168+
jsNativeLoadSpec = None,
169+
fields = List(fFieldDef),
170+
methods = List(ctorDef, methodDef),
171+
jsConstructor = None,
172+
jsMethodProps = Nil,
173+
jsNativeMembers = Nil,
174+
topLevelExportDefs = Nil
175+
)(OptimizerHints.empty.withInline(true))
176+
177+
//Hashers.hashClassDef(classDef)
178+
classDef
179+
}
180+
}

linker/shared/src/main/scala/org/scalajs/linker/frontend/MethodSynthesizer.scala

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,23 @@ private[frontend] final class MethodSynthesizer(
152152
private def findMethodDef(classInfo: ClassInfo, methodName: MethodName)(
153153
implicit ec: ExecutionContext): Future[MethodDef] = {
154154
val classDefFuture = classInfo.syntheticKind match {
155-
case None => inputProvider.loadClassDef(classInfo.className)
156-
case Some(syntheticKind) => Future.successful(syntheticKind.synthesizedClassDef)
155+
case None =>
156+
inputProvider.loadClassDef(classInfo.className)
157+
158+
case Some(SyntheticClassKind.Lambda(descriptor)) =>
159+
/* We are *re*-generating the full ClassDef, in addition to the
160+
* generation done in `BaseLinker`.
161+
*
162+
* This happens at most once per lambda class (hopefully never for
163+
* most of them), because:
164+
*
165+
* - lambda classes are never interfaces, so we must be generating a
166+
* reflective proxy, not a default bridge;
167+
* - lambda classes are never extended, so we don't get here through
168+
* an inheritance chain, only when synthesizing methods in the same class;
169+
* - lambda classes have a single non-constructor method.
170+
*/
171+
Future.successful(LambdaSynthesizer.makeClassDef(descriptor, classInfo.className))
157172
}
158173

159174
for {

0 commit comments

Comments
 (0)
0