8000 Merge pull request #4981 from gzm0/less-mutable · scala-js/scala-js@24a7f0c · GitHub
[go: up one dir, main page]

Skip to content

Commit 24a7f0c

Browse files
authored
Merge pull request #4981 from gzm0/less-mutable
Optimize Infos for caching in InfoLoader
2 parents 28366ea + 9264732 commit 24a7f0c

File tree

3 files changed

+149
-257
lines changed

3 files changed

+149
-257
lines changed

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

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -677,16 +677,15 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
677677
*/
678678
private val _instantiatedSubclasses = new GrowingList[ClassInfo]
679679

680-
private val nsMethodInfos = {
681-
val nsMethodInfos = Array.fill(MemberNamespace.Count) {
682-
emptyThreadSafeMap[MethodName, MethodInfo]
683-
}
684-
for (methodData <- data.methods) {
685-
// TODO It would be good to report duplicates as errors at this point
686-
val relevantMap = nsMethodInfos(methodData.namespace.ordinal)
687-
relevantMap(methodData.methodName) = new MethodInfo(this, methodData)
688-
}
689-
nsMethodInfos
680+
private val nsMethodInfos = Array.tabulate(MemberNamespace.Count) { nsOrdinal =>
681+
val namespace = MemberNamespace.fromOrdinal(nsOrdinal)
682+
683+
val m = emptyThreadSafeMap[MethodName, MethodInfo]
684+
685+
for ((name, data) <- data.methods(nsOrdinal))
686+
m.put(name, new MethodInfo(this, namespace, name, data))
687+
688+
m
690689
}
691690

692691
def methodInfos(
@@ -724,8 +723,8 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
724723
* method exists.
725724
*/
726725
publicMethodInfos.getOrElseUpdate(methodName, {
727-
val syntheticData = makeSyntheticMethodInfo(methodName, MemberNamespace.Public)
728-
new MethodInfo(this, syntheticData, nonExistent = true)
726+
val syntheticData = makeSyntheticMethodInfo()
727+
new MethodInfo(this, MemberNamespace.Public, methodName, syntheticData, nonExistent = true)
729728
})
730729
}
731730

@@ -812,11 +811,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
812811
val targetOwner = target.owner
813812

814813
val syntheticInfo = makeSyntheticMethodInfo(
815-
methodName = methodName,
816-
namespace = MemberNamespace.Public,
817814
methodsCalledStatically = List(
818815
targetOwner.className -> NamespacedMethodName(MemberNamespace.Public, methodName)))
819-
new MethodInfo(this, syntheticInfo,
816+
new MethodInfo(this, MemberNamespace.Public, methodName, syntheticInfo,
820817
syntheticKind = MethodSyntheticKind.DefaultBridge(targetOwner.className))
821818
})
822819
}
@@ -1011,10 +1008,8 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
10111008

10121009
publicMethodInfos.getOrElseUpdate(proxyName, {
10131010
val syntheticInfo = makeSyntheticMethodInfo(
1014-
methodName = proxyName,
1015-
namespace = MemberNamespace.Public,
10161011
methodsCalled = List(this.className -> targetName))
1017-
new MethodInfo(this, syntheticInfo,
1012+
new MethodInfo(this, MemberNamespace.Public, proxyName, syntheticInfo,
10181013
syntheticKind = MethodSyntheticKind.ReflectiveProxy(targetName))
10191014
})
10201015
}
@@ -1024,8 +1019,8 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
10241019
assert(namespace != MemberNamespace.Public)
10251020

10261021
methodInfos(namespace).getOrElseUpdate(methodName, {
1027-
val syntheticData = makeSyntheticMethodInfo(methodName, namespace)
1028-
new MethodInfo(this, syntheticData, nonExistent = true)
1022+
val syntheticData = makeSyntheticMethodInfo()
1023+
new MethodInfo(this, namespace, methodName, syntheticData, nonExistent = true)
10291024
})
10301025
}
10311026

@@ -1273,13 +1268,13 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
12731268

12741269
private class MethodInfo(
12751270
val owner: ClassInfo,
1271+
val namespace: MemberNamespace,
1272+
val methodName: MethodName,
12761273
data: Infos.MethodInfo,
12771274
val nonExistent: Boolean = false,
12781275
val syntheticKind: MethodSyntheticKind = MethodSyntheticKind.None
12791276
) extends Analysis.MethodInfo {
12801277

1281-
val methodName = data.methodName
1282-
val namespace = data.namespace
12831278
val isAbstract = data.isAbstract
12841279

12851280
private[this] val _isAbstractReachable = new AtomicBoolean(false)
@@ -1357,7 +1352,7 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
13571352
}
13581353

13591354
private[this] def doReach(): Unit =
1360-
followReachabilityInfo(data.reachabilityInfo, owner)(FromMethod(this))
1355+
followReachabilityInfo(data, owner)(FromMethod(this))
13611356
}
13621357

13631358
private class TopLevelExportInfo(val owningClass: ClassName, data: Infos.TopLevelExportInfo)
@@ -1507,26 +1502,30 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
15071502
if (className == ObjectClass) None
15081503
else Some(ObjectClass)
15091504

1510-
new Infos.ClassInfoBuilder(className, ClassKind.Class,
1511-
superClass = superClass, interfaces = Nil, jsNativeLoadSpec = None)
1512-
.addMethod(makeSyntheticMethodInfo(NoArgConstructorName, MemberNamespace.Constructor))
1513-
.result()
1505+
val methods = Array.tabulate[Map[MethodName, Infos.MethodInfo]](MemberNamespace.Count) { nsOrdinal =>
1506+
if (nsOrdinal == MemberNamespace.Constructor.ordinal)
1507+
Map(NoArgConstructorName -> makeSyntheticMethodInfo())
1508+
else
1509+
Map.empty
1510+
}
1511+
1512+
new Infos.ClassInfo(className, ClassKind.Class,
1513+
superClass = superClass, interfaces = Nil, jsNativeLoadSpec = None,
1514+
referencedFieldClasses = Map.empty, methods = methods,
1515+
jsNativeMembers = Map.empty, jsMethodProps = Nil, topLevelExports = Nil)
15141516
}
15151517

15161518
private def makeSyntheticMethodInfo(
1517-
methodName: MethodName,
1518-
namespace: MemberNamespace,
15191519
methodsCalled: List[(ClassName, MethodName)] = Nil,
1520-
methodsCalledStatically: List[(ClassName, NamespacedMethodName)] = Nil,
1521-
instantiatedClasses: List[ClassName] = Nil
1520+
methodsCalledStatically: List[(ClassName, NamespacedMethodName)] = Nil
15221521
): Infos.MethodInfo = {
1523-
val reachabilityInfoBuilder = new Infos.ReachabilityInfoBuilder()
1522+
val reachabilityInfoBuilder = new Infos.ReachabilityInfoBuilder(ir.Version.Unversioned)
1523+
15241524
for ((className, methodName) <- methodsCalled)
15251525
reachabilityInfoBuilder.addMethodCalled(className, methodName)
15261526
for ((className, methodName) <- methodsCalledStatically)
15271527
reachabilityInfoBuilder.addMethodCalledStatically(className, methodName)
1528-
Infos.MethodInfo(methodName, namespace, isAbstract = false,
1529-
reachabilityInfoBuilder.result())
1528+
Infos.MethodInfo(isAbstract = false, reachabilityInfoBuilder.result())
15301529
}
15311530

15321531
}

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

Lines changed: 57 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,16 @@ private[analyzer] object InfoLoader {
6464
case object InitialIRCheck extends IRCheckMode
6565
case object InternalIRCheck extends IRCheckMode
6666

67+
private type MethodInfos = Array[Map[MethodName, Infos.MethodInfo]]
68+
6769
private class ClassInfoCache(className: ClassName, irLoader: IRLoader, irCheckMode: InfoLoader.IRCheckMode) {
6870
private var cacheUsed: Boolean = false
6971
private var version: Version = Version.Unversioned
7072
private var info: Future[Infos.ClassInfo] = _
7173

72-
private val methodsInfoCaches = MethodDefsInfosCache()
73-
private val jsConstructorInfoCache = new JSConstructorDefInfoCache()
74-
private val exportedMembersInfoCaches = JSMethodPropDefsInfosCache()
74+
private var prevMethodInfos: MethodInfos = Array.fill(MemberNamespace.Count)(Map.empty)
75+
private var prevJSCtorInfo: Option[Infos.ReachabilityInfo] = None
76+
private var prevJSMethodPropDefInfos: List[Infos.ReachabilityInfo] = Nil
7577

7678
def loadInfo(logger: Logger)(implicit ec: ExecutionContext): Future[Infos.ClassInfo] = synchronized {
7779
/* If the cache was already used in this run, the classDef and info are
@@ -115,171 +117,86 @@ private[analyzer] object InfoLoader {
115117
}
116118

117119
private def generateInfos(classDef: ClassDef): Infos.ClassInfo = {
118-
val builder = new Infos.ClassInfoBuilder(classDef.className,
119-
classDef.kind, classDef.superClass.map(_.name),
120-
classDef.interfaces.map(_.name), classDef.jsNativeLoadSpec)
121-
122-
classDef.fields.foreach {
123-
case FieldDef(flags, FieldIdent(name), _, ftpe) =>
124-
if (!flags.namespace.isStatic)
125-
builder.maybeAddReferencedFieldClass(name, ftpe)
126-
127-
case _: JSFieldDef =>
128-
// Nothing to do.
129-
}
120+
val referencedFieldClasses = Infos.genReferencedFieldClasses(classDef.fields)
130121

131-
classDef.methods.foreach { method =>
132-
builder.addMethod(methodsInfoCaches.getInfo(method))
133-
}
122+
prevMethodInfos = genMethodInfos(classDef.methods, prevMethodInfos)
123+
prevJSCtorInfo = genJSCtorInfo(classDef.jsConstructor, prevJSCtorInfo)
124+
prevJSMethodPropDefInfos =
125+
genJSMethodPropDefInfos(classDef.jsMethodProps, prevJSMethodPropDefInfos)
134126

135-
classDef.jsConstructor.foreach { jsConstructor =>
136-
builder.addExportedMember(jsConstructorInfoCache.getInfo(jsConstructor))
137-
}
138-
139-
for (info <- exportedMembersInfoCaches.getInfos(classDef.jsMethodProps))
140-
builder.addExportedMember(info)
127+
val exportedMembers = prevJSCtorInfo.toList ::: prevJSMethodPropDefInfos
141128

142129
/* We do not cache top-level exports, because they're quite rare,
143130
* and usually quite small when they exist.
144131
*/
145-
classDef.topLevelExportDefs.foreach { topLevelExportDef =>
146-
builder.addTopLevelExport(Infos.generateTopLevelExportInfo(classDef.name.name, topLevelExportDef))
147-
}
132+
val topLevelExports = classDef.topLevelExportDefs
133+
.map(Infos.generateTopLevelExportInfo(classDef.name.name, _))
148134

149-
classDef.jsNativeMembers.foreach(builder.addJSNativeMember(_))
135+
val jsNativeMembers = classDef.jsNativeMembers
136+
.map(m => m.name.name -> m.jsNativeLoadSpec).toMap
150137

151-
builder.result()
138+
new Infos.ClassInfo(classDef.className, classDef.kind,
139+
classDef.superClass.map(_.name), classDef.interfaces.map(_.name),
140+
classDef.jsNativeLoadSpec, referencedFieldClasses, prevMethodInfos,
141+
jsNativeMembers, exportedMembers, topLevelExports)
152142
}
153143

154144
/** Returns true if the cache has been used and should be kept. */
155145
def cleanAfterRun(): Boolean = synchronized {
156146
val result = cacheUsed
157147
cacheUsed = false
158-
if (result) {
159-
// No point in cleaning the inner caches if the whole class disappears
160-
methodsInfoCaches.cleanAfterRun()
161-
jsConstructorInfoCache.cleanAfterRun()
162-
exportedMembersInfoCaches.cleanAfterRun()
163-
}
164148
result
165149
}
166150
}
167151

168-
private final class MethodDefsInfosCache private (
169-
val caches: Array[mutable.Map[MethodName, MethodDefInfoCache]])
170-
extends AnyVal {
171-
172-
def getInfo(methodDef: MethodDef): Infos.MethodInfo = {
173-
val cache = caches(methodDef.flags.namespace.ordinal)
174-
.getOrElseUpdate(methodDef.methodName, new MethodDefInfoCache)
175-
cache.getInfo(methodDef)
176-
}
177-
178-
def cleanAfterRun(): Unit = {
179-
caches.foreach(_.filterInPlace((_, cache) => cache.cleanAfterRun()))
180-
}
181-
}
152+
private def genMethodInfos(methods: List[MethodDef],
153+
prevMethodInfos: MethodInfos): MethodInfos = {
182154

183-
private object MethodDefsInfosCache {
184-
def apply(): MethodDefsInfosCache = {
185-
new MethodDefsInfosCache(
186-
Array.fill(MemberNamespace.Count)(mutable.Map.empty))
187-
}
188-
}
155+
val builders = Array.fill(MemberNamespace.Count)(Map.newBuilder[MethodName, Infos.MethodInfo])
189156

190-
/* For JS method and property definitions, we use their index in the list of
191-
* `linkedClass.exportedMembers` as their identity. We cannot use their name
192-
* because the name itself is a `Tree`.
193-
*
194-
* If there is a different number of exported members than in a previous run,
195-
* we always recompute everything. This is fine because, for any given class,
196-
* either all JS methods and properties are reachable, or none are. So we're
197-
* only missing opportunities for incrementality in the case where JS members
198-
* are added or removed in the original .sjsir, which is not a big deal.
199-
*/
200-
private final class JSMethodPropDefsInfosCache private (
201-
private var caches: Array[JSMethodPropDefInfoCache]) {
202-
203-
def getInfos(members: List[JSMethodPropDef]): List[Infos.ReachabilityInfo] = {
204-
if (members.isEmpty) {
205-
caches = null
206-
Nil
207-
} else {
208-
val membersSize = members.size
209-
if (caches == null || membersSize != caches.size)
210-
caches = Array.fill(membersSize)(new JSMethodPropDefInfoCache)
211-
212-
for ((member, i) <- members.zipWithIndex) yield {
213-
caches(i).getInfo(member)
214-
}
215-
}
216-
}
157+
methods.foreach { method =>
158+
val info = prevMethodInfos(method.flags.namespace.ordinal)
159+
.get(method.methodName)
160+
.filter(_.version.sameVersion(method.version))
161+
.getOrElse(Infos.generateMethodInfo(method))
217162

218-
def cleanAfterRun(): Unit = {
219-
if (caches != null)
220-
caches.foreach(_.cleanAfterRun())
163+
builders(method.flags.namespace.ordinal) += method.methodName -> info
221164
}
222-
}
223165

224-
private object JSMethodPropDefsInfosCache {
225-
def apply(): JSMethodPropDefsInfosCache =
226-
new JSMethodPropDefsInfosCache(null)
166+
builders.map(_.result())
227167
}
228168

229-
private abstract class AbstractMemberInfoCache[Def <: VersionedMemberDef, Info] {
230-
private var cacheUsed: Boolean = false
231-
private var lastVersion: Version = Version.Unversioned
232-
private var info: Info = _
233-
234-
final def getInfo(member: Def): Info = {
235-
update(member)
236-
info
237-
}
238-
239-
private final def update(member: Def): Unit = {
240-
if (!cacheUsed) {
241-
cacheUsed = true
242-
val newVersion = member.version
243-
if (!lastVersion.sameVersion(newVersion)) {
244-
info = computeInfo(member)
245-
lastVersion = newVersion
246-
}
247-
}
248-
}
249-
250-
protected def computeInfo(member: Def): Info
251-
252-
/** Returns true if the cache has been used and should be kept. */
253-
final def cleanAfterRun(): Boolean = {
254-
val result = cacheUsed
255-
cacheUsed = false
256-
result
169+
private def genJSCtorInfo(jsCtor: Option[JSConstructorDef],
170+
prevJSCtorInfo: Option[Infos.ReachabilityInfo]): Option[Infos.ReachabilityInfo] = {
171+
jsCtor.map { ctor =>
172+
prevJSCtorInfo
173+
.filter(_.version.sameVersion(ctor.version))
174+
.getOrElse(Infos.generateJSConstructorInfo(ctor))
257175
}
258176
}
259177

260-
private final class MethodDefInfoCache
261-
extends AbstractMemberInfoCache[MethodDef, Infos.MethodInfo] {
262-
263-
protected def computeInfo(member: MethodDef): Infos.MethodInfo =
264-
Infos.generateMethodInfo(member)
265-
}
266-
267-
private final class JSConstructorDefInfoCache
268-
extends AbstractMemberInfoCache[JSConstructorDef, Infos.ReachabilityInfo] {
269-
270-
protected def computeInfo(member: JSConstructorDef): Infos.ReachabilityInfo =
271-
Infos.generateJSConstructorInfo(member)
272-
}
273-
274-
private final class JSMethodPropDefInfoCache
275-
extends AbstractMemberInfoCache[JSMethodPropDef, Infos.ReachabilityInfo] {
276-
277-
protected def computeInfo(member: JSMethodPropDef): Infos.ReachabilityInfo = {
278-
member match {
279-
case methodDef: JSMethodDef =>
280-
Infos.generateJSMethodInfo(methodDef)
281-
case propertyDef: JSPropertyDef =>
282-
Infos.generateJSPropertyInfo(propertyDef)
178+
private def genJSMethodPropDefInfos(jsMethodProps: List[JSMethodPropDef],
179+
prevJSMethodPropDefInfos: List[Infos.ReachabilityInfo]): List[Infos.ReachabilityInfo] = {
180+
/* For JS method and property definitions, we use their index in the list of
181+
* `linkedClass.exportedMembers` as their identity. We cannot use their name
182+
* because the name itself is a `Tree`.
183+
*
184+
* If there is a different number of exported members than in a previous run,
185+
* we always recompute everything. This is fine because, for any given class,
186+
* either all JS methods and properties are reachable, or none are. So we're
187+
* only missing opportunities for incrementality in the case where JS members
188+
* are added or removed in the original .sjsir, which is not a big deal.
189+
*/
190+
191+
if (prevJSMethodPropDefInfos.size != jsMethodProps.size) {
192+
// Regenerate everything.
193+
jsMethodProps.map(Infos.generateJSMethodPropDefInfo(_))
194+
} else {
195+
for {
196+
(prevInfo, member) <- prevJSMethodPropDefInfos.zip(jsMethodProps)
197+
} yield {
198+
if (prevInfo.version.sameVersion(member.version)) prevInfo
199+
else Infos.generateJSMethodPropDefInfo(member)
283200
}
284201
}
285202
}

0 commit comments

Comments
 (0)
0