@@ -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
@@ -116,12 +118,13 @@ private[analyzer] object InfoLoader {
116118
117119 private def generateInfos (classDef : ClassDef ): Infos .ClassInfo = {
118120 val referencedFieldClasses = Infos .genReferencedFieldClasses(classDef.fields)
119- val methods = classDef.methods.map(methodsInfoCaches.getInfo(_))
120121
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
125128
126129 /* We do not cache top-level exports, because they're quite rare,
127130 * and usually quite small when they exist.
@@ -134,139 +137,66 @@ private[analyzer] object InfoLoader {
134137
135138 new Infos .ClassInfo (classDef.className, classDef.kind,
136139 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)
139142 }
140143
141144 /** Returns true if the cache has been used and should be kept. */
142145 def cleanAfterRun (): Boolean = synchronized {
143146 val result = cacheUsed
144147 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- }
151148 result
152149 }
153150 }
154151
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 = {
164154
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 ])
176156
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))
204162
205- def cleanAfterRun (): Unit = {
206- if (caches != null )
207- caches.foreach(_.cleanAfterRun())
163+ builders(method.flags.namespace.ordinal) += method.methodName -> info
208164 }
209- }
210165
211- private object JSMethodPropDefsInfosCache {
212- def apply (): JSMethodPropDefsInfosCache =
213- new JSMethodPropDefsInfosCache (null )
166+ builders.map(_.result())
214167 }
215168
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))
244175 }
245176 }
246177
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)
270200 }
271201 }
272202 }
0 commit comments