8000 Optimize Infos for caching in InfoLoader by gzm0 · Pull Request #4981 · scala-js/scala-js · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -677,16 +677,15 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
*/
private val _instantiatedSubclasses = new GrowingList[ClassInfo]

private val nsMethodInfos = {
val nsMethodInfos = Array.fill(MemberNamespace.Count) {
emptyThreadSafeMap[MethodName, MethodInfo]
}
for (methodData <- data.methods) {
// TODO It would be good to report duplicates as errors at this point
val relevantMap = nsMethodInfos(methodData.namespace.ordinal)
relevantMap(methodData.methodName) = new MethodInfo(this, methodData)
}
nsMethodInfos
private val nsMethodInfos = Array.tabulate(MemberNamespace.Count) { nsOrdinal =>
val namespace = MemberNamespace.fromOrdinal(nsOrdinal)

val m = emptyThreadSafeMap[MethodName, MethodInfo]

for ((name, data) <- data.methods(nsOrdinal))
m.put(name, new MethodInfo(this, namespace, name, data))

m
}

def methodInfos(
Expand Down Expand Up @@ -724,8 +723,8 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
* method exists.
*/
publicMethodInfos.getOrElseUpdate(methodName, {
val syntheticData = makeSyntheticMethodInfo(methodName, MemberNamespace.Public)
new MethodInfo(this, syntheticData, nonExistent = true)
val syntheticData = makeSyntheticMethodInfo()
new MethodInfo(this, MemberNamespace.Public, methodName, syntheticData, nonExistent = true)
})
}

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

val syntheticInfo = makeSyntheticMethodInfo(
methodName = methodName,
namespace = MemberNamespace.Public,
methodsCalledStatically = List(
targetOwner.className -> NamespacedMethodName(MemberNamespace.Public, methodName)))
new MethodInfo(this, syntheticInfo,
new MethodInfo(this, MemberNamespace.Public, methodName, syntheticInfo,
syntheticKind = MethodSyntheticKind.DefaultBridge(targetOwner.className))
})
}
Expand Down Expand Up @@ -1011,10 +1008,8 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,

publicMethodInfos.getOrElseUpdate(proxyName, {
val syntheticInfo = makeSyntheticMethodInfo(
methodName = proxyName,
namespace = MemberNamespace.Public,
methodsCalled = List(this.className -> targetName))
new MethodInfo(this, syntheticInfo,
new MethodInfo(this, MemberNamespace.Public, proxyName, syntheticInfo,
syntheticKind = MethodSyntheticKind.ReflectiveProxy(targetName))
})
}
Expand All @@ -1024,8 +1019,8 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
assert(namespace != MemberNamespace.Public)

methodInfos(namespace).getOrElseUpdate(methodName, {
val syntheticData = makeSyntheticMethodInfo(methodName, namespace)
new MethodInfo(this, syntheticData, nonExistent = true)
val syntheticData = makeSyntheticMethodInfo()
new MethodInfo(this, namespace, methodName, syntheticData, nonExistent = true)
})
}

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

private class MethodInfo(
val owner: ClassInfo,
val namespace: MemberNamespace,
val methodName: MethodName,
data: Infos.MethodInfo,
val nonExistent: Boolean = false,
val syntheticKind: MethodSyntheticKind = MethodSyntheticKind.None
) extends Analysis.MethodInfo {

val methodName = data.methodName
val namespace = data.namespace
val isAbstract = data.isAbstract

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

private[this] def doReach(): Unit =
followReachabilityInfo(data.reachabilityInfo, owner)(FromMethod(this))
followReachabilityInfo(data, owner)(FromMethod(this))
}

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

new Infos.ClassInfoBuilder(className, ClassKind.Class,
superClass = superClass, interfaces = Nil, jsNativeLoadSpec = None)
.addMethod(makeSyntheticMethodInfo(NoArgConstructorName, MemberNamespace.Constructor))
.result()
val methods = Array.tabulate[Map[MethodName, Infos.MethodInfo]](MemberNamespace.Count) { nsOrdinal =>
if (nsOrdinal == MemberNamespace.Constructor.ordinal)
Map(NoArgConstructorName -> makeSyntheticMethodInfo())
else
Map.empty
}

new Infos.ClassInfo(className, ClassKind.Class,
superClass = superClass, interfaces = Nil, jsNativeLoadSpec = None,
referencedFieldClasses = Map.empty, methods = methods,
jsNativeMembers = Map.empty, jsMethodProps = Nil, topLevelExports = Nil)
}

private def makeSyntheticMethodInfo(
methodName: MethodName,
namespace: MemberNamespace,
methodsCalled: List[(ClassName, MethodName)] = Nil,
methodsCalledStatically: List[(ClassName, NamespacedMethodName)] = Nil,
instantiatedClasses: List[ClassName] = Nil
methodsCalledStatically: List[(ClassName, NamespacedMethodName)] = Nil
): Infos.MethodInfo = {
val reachabilityInfoBuilder = new Infos.ReachabilityInfoBuilder()
val reachabilityInfoBuilder = new Infos.ReachabilityInfoBuilder(ir.Version.Unversioned)

for ((className, methodName) <- methodsCalled)
reachabilityInfoBuilder.addMethodCalled(className, methodName)
for ((className, methodName) <- methodsCalledStatically)
reachabilityInfoBuilder.addMethodCalledStatically(className, methodName)
Infos.MethodInfo(methodName, namespace, isAbstract = false,
reachabilityInfoBuilder.result())
Infos.MethodInfo(isAbstract = false, reachabilityInfoBuilder.result())
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,16 @@ private[analyzer] object InfoLoader {
case object InitialIRCheck extends IRCheckMode
case object InternalIRCheck extends IRCheckMode

private type MethodInfos = Array[Map[MethodName, Infos.MethodInfo]]

private class ClassInfoCache(className: ClassName, irLoader: IRLoader, irCheckMode: InfoLoader.IRCheckMode) {
private var cacheUsed: Boolean = false
private var version: Version = Version.Unversioned
private var info: Future[Infos.ClassInfo] = _

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

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

private def generateInfos(classDef: ClassDef): Infos.ClassInfo = {
val builder = new Infos.ClassInfoBuilder(classDef.className,
classDef.kind, classDef.superClass.map(_.name),
classDef.interfaces.map(_.name), classDef.jsNativeLoadSpec)

classDef.fields.foreach {
case FieldDef(flags, FieldIdent(name), _, ftpe) =>
if (!flags.namespace.isStatic)
builder.maybeAddReferencedFieldClass(name, ftpe)

case _: JSFieldDef =>
// Nothing to do.
}
val referencedFieldClasses = Infos.genReferencedFieldClasses(classDef.fields)

classDef.methods.foreach { method =>
builder.addMethod(methodsInfoCaches.getInfo(method))
}
prevMethodInfos = genMethodInfos(classDef.methods, prevMethodInfos)
prevJSCtorInfo = genJSCtorInfo(classDef.jsConstructor, prevJSCtorInfo)
prevJSMethodPropDefInfos =
genJSMethodPropDefInfos(classDef.jsMethodProps, prevJSMethodPropDefInfos)

classDef.jsConstructor.foreach { jsConstructor =>
builder.addExportedMember(jsConstructorInfoCache.getInfo(jsConstructor))
}

for (info <- exportedMembersInfoCaches.getInfos(classDef.jsMethodProps))
builder.addExportedMember(info)
val exportedMembers = prevJSCtorInfo.toList ::: prevJSMethodPropDefInfos

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

classDef.jsNativeMembers.foreach(builder.addJSNativeMember(_))
val jsNativeMembers = classDef.jsNativeMembers
.map(m => m.name.name -> m.jsNativeLoadSpec).toMap

builder.result()
new Infos.ClassInfo(classDef.className, classDef.kind,
classDef.superClass.map(_.name), classDef.interfaces.map(_.name),
classDef.jsNativeLoadSpec, referencedFieldClasses, prevMethodInfos,
jsNativeMembers, exportedMembers, topLevelExports)
}

/** Returns true if the cache has been used and should be kept. */
def cleanAfterRun(): Boolean = synchronized {
val result = cacheUsed
cacheUsed = false
if (result) {
// No point in clean 6DAF ing the inner caches if the whole class disappears
methodsInfoCaches.cleanAfterRun()
jsConstructorInfoCache.cleanAfterRun()
exportedMembersInfoCaches.cleanAfterRun()
}
result
}
}

private final class MethodDefsInfosCache private (
val caches: Array[mutable.Map[MethodName, MethodDefInfoCache]])
extends AnyVal {

def getInfo(methodDef: MethodDef): Infos.MethodInfo = {
val cache = caches(methodDef.flags.namespace.ordinal)
.getOrElseUpdate(methodDef.methodName, new MethodDefInfoCache)
cache.getInfo(methodDef)
}

def cleanAfterRun(): Unit = {
caches.foreach(_.filterInPlace((_, cache) => cache.cleanAfterRun()))
}
}
private def genMethodInfos(methods: List[MethodDef],
prevMethodInfos: MethodInfos): MethodInfos = {

private object MethodDefsInfosCache {
def apply(): MethodDefsInfosCache = {
new MethodDefsInfosCache(
Array.fill(MemberNamespace.Count)(mutable.Map.empty))
}
}
val builders = Array.fill(MemberNamespace.Count)(Map.newBuilder[MethodName, Infos.MethodInfo])

/* For JS method and property definitions, we use their index in the list of
* `linkedClass.exportedMembers` as their identity. We cannot use their name
* because the name itself is a `Tree`.
*
* If there is a different number of exported members than in a previous run,
* we always recompute everything. This is fine because, for any given class,
* either all JS methods and properties are reachable, or none are. So we're
* only missing opportunities for incrementality in the case where JS members
* are added or removed in the original .sjsir, which is not a big deal.
*/
private final class JSMethodPropDefsInfosCache private (
private var caches: Array[JSMethodPropDefInfoCache]) {

def getInfos(members: List[JSMethodPropDef]): List[Infos.ReachabilityInfo] = {
if (members.isEmpty) {
caches = null
Nil
} else {
val membersSize = members.size
if (caches == null || membersSize != caches.size)
caches = Array.fill(membersSize)(new JSMethodPropDefInfoCache)

for ((member, i) <- members.zipWithIndex) yield {
caches(i).getInfo(member)
}
}
}
methods.foreach { method =>
val info = prevMethodInfos(method.flags.namespace.ordinal)
.get(method.methodName)
.filter(_.version.sameVersion(method.version))
.getOrElse(Infos.generateMethodInfo(method))

def cleanAfterRun(): Unit = {
if (caches != null)
caches.foreach(_.cleanAfterRun())
builders(method.flags.namespace.ordinal) += method.methodName -> info
}
}

private object JSMethodPropDefsInfosCache {
def apply(): JSMethodPropDefsInfosCache =
new JSMethodPropDefsInfosCache(null)
builders.map(_.result())
}

private abstract class AbstractMemberInfoCache[Def <: VersionedMemberDef, Info] {
private var cacheUsed: Boolean = false
private var lastVersion: Version = Version.Unversioned
private var info: Info = _

final def getInfo(member: Def): Info = {
update(member)
info
}

private final def update(member: Def): Unit = {
if (!cacheUsed) {
cacheUsed = true
val newVersion = member.version
if (!lastVersion.sameVersion(newVersion)) {
info = computeInfo(member)
lastVersion = newVersion
}
}
}

protected def computeInfo(member: Def): Info

/** Returns true if the cache has been used and should be kept. */
final def cleanAfterRun(): Boolean = {
val result = cacheUsed
cacheUsed = false
result
private def genJSCtorInfo(jsCtor: Option[JSConstructorDef],
prevJSCtorInfo: Option[Infos.ReachabilityInfo]): Option[Infos.ReachabilityInfo] = {
jsCtor.map { ctor =>
prevJSCtorInfo
.filter(_.version.sameVersion(ctor.version))
.getOrElse(Infos.generateJSConstructorInfo(ctor))
}
}

private final class MethodDefInfoCache
extends AbstractMemberInfoCache[MethodDef, Infos.MethodInfo] {

protected def computeInfo(member: MethodDef): Infos.MethodInfo =
Infos.generateMethodInfo(member)
}

private final class JSConstructorDefInfoCache
extends AbstractMemberInfoCache[JSConstructorDef, Infos.ReachabilityInfo] {

protected def computeInfo(member: JSConstructorDef): Infos.ReachabilityInfo =
Infos.generateJSConstructorInfo(member)
}

private final class JSMethodPropDefInfoCache
extends AbstractMemberInfoCache[JSMethodPropDef, Infos.ReachabilityInfo] {

protected def computeInfo(member: JSMethodPropDef): Infos.ReachabilityInfo = {
member match {
case methodDef: JSMethodDef =>
Infos.generateJSMethodInfo(methodDef)
case propertyDef: JSPropertyDef =>
Infos.generateJSPropertyInfo(propertyDef)
private def genJSMethodPropDefInfos(jsMethodProps: List[JSMethodPropDef],
prevJSMethodPropDefInfos: List[Infos.ReachabilityInfo]): List[Infos.ReachabilityInfo] = {
/* For JS method and property definitions, we use their index in the list of
* `linkedClass.exportedMembers` as their identity. We cannot use their name
* because the name itself is a `Tree`.
*
* If there is a different number of exported members than in a previous run,
* we always recompute everything. This is fine because, for any given class,
* either all JS methods and properties are reachable, or none are. So we're
* only missing opportunities for incrementality in the case where JS members
* are added or removed in the original .sjsir, which is not a big deal.
*/

if (prevJSMethodPropDefInfos.size != jsMethodProps.size) {
// Regenerate everything.
jsMethodProps.map(Infos.generateJSMethodPropDefInfo(_))
} else {
for {
(prevInfo, member) <- prevJSMethodPropDefInfos.zip(jsMethodProps)
} yield {
if (prevInfo.version.sameVersion(member.version)) prevInfo
else Infos.generateJSMethodPropDefInfo(member)
}
}
}
Expand Down
Loading
0