@@ -64,14 +64,16 @@ private[analyzer] object InfoLoader {
64
64
case object InitialIRCheck extends IRCheckMode
65
65
case object InternalIRCheck extends IRCheckMode
66
66
67
+ private type MethodInfos = Array [Map [MethodName , Infos .MethodInfo ]]
68
+
67
69
private class ClassInfoCache (className : ClassName , irLoader : IRLoader , irCheckMode : InfoLoader .IRCheckMode ) {
68
70
private var cacheUsed : Boolean = false
69
71
private var version : Version = Version .Unversioned
70
72
private var info : Future [Infos .ClassInfo ] = _
71
73
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
75
77
76
78
def loadInfo (logger : Logger )(implicit ec : ExecutionContext ): Future [Infos .ClassInfo ] = synchronized {
77
79
/* If the cache was already used in this run, the classDef and info are
@@ -116,12 +118,13 @@ private[analyzer] object InfoLoader {
116
118
117
119
private def generateInfos (classDef : ClassDef ): Infos .ClassInfo = {
118
120
val referencedFieldClasses = Infos .genReferencedFieldClasses(classDef.fields)
119
- val methods = classDef.methods.map(methodsInfoCaches.getInfo(_))
120
121
121
- val jsMethodProps = {
122
- classDef.jsConstructor.map(jsConstructorInfoCache.getInfo(_)).toList :::
123
- exportedMembersInfoCaches.getInfos(classDef.jsMethodProps)
124
- }
122
+ prevMethodInfos = genMethodInfos(classDef.methods, prevMethodInfos)
123
+ prevJSCtorInfo = genJSCtorInfo(classDef.jsConstructor, prevJSCtorInfo)
124
+ prevJSMethodPropDefInfos =
125
+ genJSMethodPropDefInfos(classDef.jsMethodProps, prevJSMethodPropDefInfos)
126
+
127
+ val exportedMembers = prevJSCtorInfo.toList ::: prevJSMethodPropDefInfos
125
128
126
129
/* We do not cache top-level exports, because they're quite rare,
127
130
* and usually quite small when they exist.
@@ -134,139 +137,66 @@ private[analyzer] object InfoLoader {
134
137
135
138
new Infos .ClassInfo (classDef.className, classDef.kind,
136
139
classDef.superClass.map(_.name), classDef.interfaces.map(_.name),
137
- classDef.jsNativeLoadSpec, referencedFieldClasses, methods, jsNativeMembers ,
138
- jsMethodProps , topLevelExports)
140
+ classDef.jsNativeLoadSpec, referencedFieldClasses, prevMethodInfos ,
141
+ jsNativeMembers, exportedMembers , topLevelExports)
139
142
}
140
143
141
144
/** Returns true if the cache has been used and should be kept. */
142
145
def cleanAfterRun (): Boolean = synchronized {
143
146
val result = cacheUsed
144
147
cacheUsed = false
145
- if (result) {
146
- // No point in cleaning the inner caches if the whole class disappears
147
- methodsInfoCaches.cleanAfterRun()
148
- jsConstructorInfoCache.cleanAfterRun()
149
- exportedMembersInfoCaches.cleanAfterRun()
150
- }
151
148
result
152
149
}
153
150
}
154
151
155
- private final class MethodDefsInfosCache private (
156
- val caches : Array [mutable.Map [MethodName , MethodDefInfoCache ]])
157
- extends AnyVal {
158
-
159
- def getInfo (methodDef : MethodDef ): Infos .MethodInfo = {
160
- val cache = caches(methodDef.flags.namespace.ordinal)
161
- .getOrElseUpdate(methodDef.methodName, new MethodDefInfoCache )
162
- cache.getInfo(methodDef)
163
- }
152
+ private def genMethodInfos (methods : List [MethodDef ],
153
+ prevMethodInfos : MethodInfos ): MethodInfos = {
164
154
165
- def cleanAfterRun (): Unit = {
166
- caches.foreach(_.filterInPlace((_, cache) => cache.cleanAfterRun()))
167
- }
168
- }
169
-
170
- private object MethodDefsInfosCache {
171
- def apply (): MethodDefsInfosCache = {
172
- new MethodDefsInfosCache (
173
- Array .fill(MemberNamespace .Count )(mutable.Map .empty))
174
- }
175
- }
155
+ val builders = Array .fill(MemberNamespace .Count )(Map .newBuilder[MethodName , Infos .MethodInfo ])
176
156
177
- /* For JS method and property definitions, we use their index in the list of
178
- * `linkedClass.exportedMembers` as their identity. We cannot use their name
179
- * because the name itself is a `Tree`.
180
- *
181
- * If there is a different number of exported members than in a previous run,
182
- * we always recompute everything. This is fine because, for any given class,
183
- * either all JS methods and properties are reachable, or none are. So we're
184
- * only missing opportunities for incrementality in the case where JS members
185
- * are added or removed in the original .sjsir, which is not a big deal.
186
- */
187
- private final class JSMethodPropDefsInfosCache private (
188
- private var caches : Array [JSMethodPropDefInfoCache ]) {
189
-
190
- def getInfos (members : List [JSMethodPropDef ]): List [Infos .ReachabilityInfo ] = {
191
- if (members.isEmpty) {
192
- caches = null
193
- Nil
194
- } else {
195
- val membersSize = members.size
196
- if (caches == null || membersSize != caches.size)
197
- caches = Array .fill(membersSize)(new JSMethodPropDefInfoCache )
198
-
199
- for ((member, i) <- members.zipWithIndex) yield {
200
- caches(i).getInfo(member)
201
- }
202
- }
203
- }
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))
204
162
205
- def cleanAfterRun (): Unit = {
206
- if (caches != null )
207
- caches.foreach(_.cleanAfterRun())
163
+ builders(method.flags.namespace.ordinal) += method.methodName -> info
208
164
}
209
- }
210
165
211
- private object JSMethodPropDefsInfosCache {
212
- def apply (): JSMethodPropDefsInfosCache =
213
- new JSMethodPropDefsInfosCache (null )
166
+ builders.map(_.result())
214
167
}
215
168
216
- private abstract class AbstractMemberInfoCache [Def <: VersionedMemberDef , Info ] {
217
- private var cacheUsed : Boolean = false
218
- private var lastVersion : Version = Version .Unversioned
219
- private var info : Info = _
220
-
221
- final def getInfo (member : Def ): Info = {
222
- update(member)
223
- info
224
- }
225
-
226
- private final def update (member : Def ): Unit = {
227
- if (! cacheUsed) {
228
- cacheUsed = true
229
- val newVersion = member.version
230
- if (! lastVersion.sameVersion(newVersion)) {
231
- info = computeInfo(member)
232
- lastVersion = newVersion
233
- }
234
- }
235
- }
236
-
237
- protected def computeInfo (member : Def ): Info
238
-
239
- /** Returns true if the cache has been used and should be kept. */
240
- final def cleanAfterRun (): Boolean = {
241
- val result = cacheUsed
242
- cacheUsed = false
243
- 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))
244
175
}
245
176
}
246
177
247
- private final class MethodDefInfoCache
248
- extends AbstractMemberInfoCache [MethodDef , Infos .MethodInfo ] {
249
-
250
- protected def computeInfo (member : MethodDef ): Infos .MethodInfo =
251
- Infos .generateMethodInfo(member)
252
- }
253
-
254
- private final class JSConstructorDefInfoCache
255
- extends AbstractMemberInfoCache [JSConstructorDef , Infos .ReachabilityInfo ] {
256
-
257
- protected def computeInfo (member : JSConstructorDef ): Infos .ReachabilityInfo =
258
- Infos .generateJSConstructorInfo(member)
259
- }
260
-
261
- private final class JSMethodPropDefInfoCache
262
- extends AbstractMemberInfoCache [JSMethodPropDef , Infos .ReachabilityInfo ] {
263
-
264
- protected def computeInfo (member : JSMethodPropDef ): Infos .ReachabilityInfo = {
265
- member match {
266
- case methodDef : JSMethodDef =>
267
- Infos .generateJSMethodInfo(methodDef)
268
- case propertyDef : JSPropertyDef =>
269
- 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)
270
200
}
271
201
}
272
202
}
0 commit comments