@@ -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
1012A
- * 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