@@ -69,9 +69,9 @@ private[analyzer] object InfoLoader {
6969 private var version : Version = Version .Unversioned
7070 private var info : Future [Infos .ClassInfo ] = _
7171
72- private val methodsInfoCaches = MethodDefsInfosCache ( )
73- private val jsConstructorInfoCache = new JSConstructorDefInfoCache ()
74- private val exportedMembersInfoCaches = JSMethodPropDefsInfosCache ()
72+ private var prevMethodInfos = Array .fill( MemberNamespace . Count )( Map .empty[ MethodName , Infos . MethodInfo ] )
73+ private var prevJSCtorInfo : Option [ Infos . ReachabilityInfo ] = None
74+ private var prevJSMethodPropDefInfos : List [ Infos . ReachabilityInfo ] = Nil
7575
7676 def loadInfo (logger : Logger )(implicit ec : ExecutionContext ): Future [Infos .ClassInfo ] = synchronized {
7777 /* If the cache was already used in this run, the classDef and info are
@@ -116,13 +116,19 @@ private[analyzer] object InfoLoader {
116116
117117 private def generateInfos (classDef : ClassDef ): Infos .ClassInfo = {
118118 val referencedFieldClasses = Infos .genReferencedFieldClasses(classDef.fields)
119- val methods = classDef.methods.map(methodsInfoCaches.getInfo(_))
120119
121- val jsMethodProps = {
122- classDef.jsConstructor.map(jsConstructorInfoCache.getInfo(_)).toList :::
123- exportedMembersInfoCaches.getInfos(classDef.jsMethodProps)
120+ prevMethodInfos = genMethodInfos(classDef.methods)
121+
122+ prevJSCtorInfo = classDef.jsConstructor.map { ctor =>
123+ prevJSCtorInfo
124+ .filter(_.version.sameVersion(ctor.version))
125+ .getOrElse(Infos .generateJSConstructorInfo(ctor))
124126 }
125127
128+ prevJSMethodPropDefInfos = genJSMethodPropDefInfos(classDef.jsMethodProps)
129+
130+ val exportedMembers = prevJSCtorInfo.toList ::: prevJSMethodPropDefInfos
131+
126132 /* We do not cache top-level exports, because they're quite rare,
127133 * and usually quite small when they exist.
128134 */
@@ -134,140 +140,55 @@ private[analyzer] object InfoLoader {
134140
135141 new Infos .ClassInfo (classDef.className, classDef.kind,
136142 classDef.superClass.map(_.name), classDef.interfaces.map(_.name),
137- classDef.jsNativeLoadSpec, referencedFieldClasses, methods, jsNativeMembers,
138- jsMethodProps, topLevelExports)
139- }
140-
141- /** Returns true if the cache has been used and should be kept. */
142- def cleanAfterRun (): Boolean = synchronized {
143- val result = cacheUsed
144- 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- result
152- }
153- }
154-
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)
143+ classDef.jsNativeLoadSpec, referencedFieldClasses, prevMethodInfos,
144+ jsNativeMembers, exportedMembers, topLevelExports)
163145 }
164146
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- }
147+ private def genMethodInfos (methods : List [MethodDef ]) = {
148+ val builders = Array .fill(MemberNamespace .Count )(Map .newBuilder[MethodName , Infos .MethodInfo ])
176149
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 )
150+ methods.foreach { method =>
151+ val info = prevMethodInfos(method.flags.namespace.ordinal)
152+ .get(method.methodName)
153+ .filter(_.version.sameVersion(method.version))
154+ .getOrElse(Infos .generateMethodInfo(method))
198155
199- for ((member, i) <- members.zipWithIndex) yield {
200- caches(i).getInfo(member)
201- }
156+ builders(method.flags.namespace.ordinal) += method.methodName -> info
202157 }
203- }
204158
205- def cleanAfterRun (): Unit = {
206- if (caches != null )
207- caches.foreach(_.cleanAfterRun())
159+ builders.map(_.result())
208160 }
209- }
210161
211- private object JSMethodPropDefsInfosCache {
212- def apply (): JSMethodPropDefsInfosCache =
213- new JSMethodPropDefsInfosCache (null )
214- }
215-
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- }
162+ private def genJSMethodPropDefInfos (jsMethodProps : List [JSMethodPropDef ]) = {
163+ /* For JS method and property definitions, we use their index in the list of
164+ * `linkedClass.exportedMembers` as their identity. We cannot use their name
165+ * because the name itself is a `Tree`.
166+ *
167+ * If there is a different number of exported members than in a previous run,
168+ * we always recompute everything. This is fine because, for any given class,
169+ * either all JS methods and properties are reachable, or none are. So we're
170+ * only missing opportunities for incrementality in the case where JS members
171+ * are added or removed in the original .sjsir, which is not a big deal.
172+ */
225173
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
174+ if (prevJSMethodPropDefInfos.size != jsMethodProps.size) {
175+ // Regenerate everything.
176+ jsMethodProps.map(Infos .generateJSMethodPropDefInfo(_))
177+ } else {
178+ for {
179+ (prevInfo, member) <- prevJSMethodPropDefInfos.zip(jsMethodProps)
180+ } yield {
181+ if (prevInfo.version.sameVersion(member.version)) prevInfo
182+ else Infos .generateJSMethodPropDefInfo(member)
233183 }
234184 }
235185 }
236186
237- protected def computeInfo (member : Def ): Info
238-
239187 /** Returns true if the cache has been used and should be kept. */
240- final def cleanAfterRun (): Boolean = {
188+ def cleanAfterRun (): Boolean = synchronized {
241189 val result = cacheUsed
242190 cacheUsed = false
243191 result
244192 }
245193 }
246-
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)
270- }
271- }
272- }
273194}
0 commit comments