8000 Store member reachability in a single field · scala-js/scala-js@0d81e85 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0d81e85

Browse files
committed
Store member reachability in a single field
This reduces the retained size on the test suite for the infos as follows: BaseLinker: 23MB -> 20MB Refiner: 20MB -> 17MB
1 parent a54c022 commit 0d81e85

File tree

2 files changed

+91
-82
lines changed

2 files changed

+91
-82
lines changed

linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala

Lines changed: 26 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,7 +1079,8 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
10791079
} else if (!_isInstantiated.getAndSet(true)) {
10801080

10811081
// TODO: Why is this not in subclassInstantiated()?
1082-
referenceFieldClasses(fieldsRead ++ fieldsWritten)
1082+
fieldsRead.foreach(referenceFieldClasses(_))
1083+
fieldsWritten.foreach(referenceFieldClasses(_))
10831084

10841085
if (isScalaClass) {
10851086
accessData()
@@ -1208,12 +1209,6 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
12081209
}
12091210
}
12101211

1211-
def callMethodStatically(namespacedMethodName: NamespacedMethodName)(
1212-
implicit from: From): Unit = {
1213-
callMethodStatically(namespacedMethodName.namespace,
1214-
namespacedMethodName.methodName)
1215-
}
1216-
12171212
def callMethodStatically(namespace: MemberNamespace,
12181213
methodName: MethodName)(
12191214
implicit from: From): Unit = {
@@ -1225,16 +1220,14 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
12251220
lookupMethod(methodName).reachStatic()
12261221
}
12271222

1228-
def readFields(names: List[FieldName])(implicit from: From): Unit = {
1229-
names.foreach(_fieldsRead.update(_, ()))
1230-
if (isInstantiated)
1231-
referenceFieldClasses(names)
1232-
}
1233-
1234-
def writeFields(names: List[FieldName])(implicit from: From): Unit = {
1235-
names.foreach(_fieldsWritten.update(_, ()))
1223+
def reachField(info: Infos.FieldReachable)(implicit from: From): Unit = {
1224+
val fieldName = info.fieldName
1225+
if (info.read)
1226+
_fieldsRead.update(fieldName, ())
1227+
if (info.written)
1228+
_fieldsWritten.update(fieldName, ())
12361229
if (isInstantiated)
1237-
referenceFieldClasses(names)
1230+
referenceFieldClasses(fieldName)
12381231
}
12391232

12401233
def useJSNativeMember(name: MethodName)(
@@ -1251,8 +1244,7 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
12511244
maybeJSNativeLoadSpec
12521245
}
12531246

1254-
private def referenceFieldClasses(fieldNames: Iterable[FieldName])(
1255-
implicit from: From): Unit = {
1247+
private def referenceFieldClasses(fieldName: FieldName)(implicit from: From): Unit = {
12561248
assert(isInstantiated)
12571249

12581250
/* Reach referenced classes of non-static fields
@@ -1261,7 +1253,6 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
12611253
* site will not reference the classes in the final JS code.
12621254
*/
12631255
for {
1264-
fieldName <- fieldNames
12651256
className <- data.referencedFieldClasses.get(fieldName)
12661257
} {
12671258
lookupClass(className)(_ => ())
@@ -1441,43 +1432,26 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
14411432
}
14421433
}
14431434

1444-
/* Since many of the lists below are likely to be empty, we always
1445-
* test `!list.isEmpty` before calling `foreach` or any other
1446-
* processing, avoiding closure allocations.
1447-
*/
1435+
if (dataInClass.memberInfos != null) {
1436+
dataInClass.memberInfos.foreach {
1437+
case field: Infos.FieldReachable =>
1438+
clazz.reachField(field)
14481439

1449-
if (!dataInClass.fieldsRead.isEmpty) {
1450-
clazz.readFields(dataInClass.fieldsRead)
1451-
}
1440+
case Infos.StaticFieldReachable(fieldName, read, written) =>
1441+
if (read)
1442+
clazz._staticFieldsRead.update(fieldName, ())
1443+
if (written)
1444+
clazz._staticFieldsWritten.update(fieldName, ())
14521445

1453-
if (!dataInClass.fieldsWritten.isEmpty) {
1454-
clazz.writeFields(dataInClass.fieldsWritten)
1455-
}
1446+
case Infos.MethodReachable(methodName) =>
1447+
clazz.callMethod(methodName)
14561448

1457-
if (!dataInClass.staticFieldsRead.isEmpty) {
1458-
dataInClass.staticFieldsRead.foreach(
1459-
clazz._staticFieldsRead.update(_, ()))
1460-
}
1461-
1462-
if (!dataInClass.staticFieldsWritten.isEmpty) {
1463-
dataInClass.staticFieldsWritten.foreach(
1464-
clazz._staticFieldsWritten.update(_, ()))
1465-
}
1449+
case Infos.MethodStaticallyReachable(namespace, methodName) =>
1450+
clazz.callMethodStatically(namespace, methodName)
14661451

1467-
if (!dataInClass.methodsCalled.isEmpty) {
1468-
for (methodName <- dataInClass.methodsCalled)
1469-
clazz.callMethod(methodName)
1470-
}
1471-
1472-
if (!dataInClass.methodsCalledStatically.isEmpty) {
1473-
for (methodName <- dataInClass.methodsCalledStatically)
1474-
clazz.callMethodStatically(methodName)
1475-
}
1476-
1477-
if (!dataInClass.jsNativeMembersUsed.isEmpty) {
1478-
for (member <- dataInClass.jsNativeMembersUsed)
1479-
clazz.useJSNativeMember(member)
1480-
.foreach(addLoadSpec(moduleUnit, _))
1452+
case Infos.JSNativeMemberReachable(methodName) =>
1453+
clazz.useJSNativeMember(methodName).foreach(addLoadSpec(moduleUnit, _))
1454+
}
14811455
}
14821456
}
14831457
}

linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,12 @@ object Infos {
109109
/** Things from a given class that are reached by one method. */
110110
final class ReachabilityInfoInClass private[Infos] (
111111
val className: ClassName,
112-
val fieldsRead: List[FieldName],
113-
val fieldsWritten: List[FieldName],
114-
val staticFieldsRead: List[FieldName],
115-
val staticFieldsWritten: List[FieldName],
116-
val methodsCalled: List[MethodName],
117-
val methodsCalledStatically: List[NamespacedMethodName],
118-
val jsNativeMembersUsed: List[MethodName],
112+
/* We use a single field for all members to reduce memory consumption:
113+
* Typically, there are very few members reached in a single
114+
* ReachabilityInfoInClass, so the overhead of having a field per type
115+
* becomes significant in terms of memory usage.
116+
*/
117+
val memberInfos: Array[MemberReachabilityInfo], // nullable!
119118
val flags: ReachabilityInfoInClass.Flags
120119
)
121120

@@ -134,6 +133,38 @@ object Infos {
134133
final val FlagDynamicallyReferenced = 1 << 5
135134
}
136135

136+
sealed trait MemberReachabilityInfo
137+
138+
final case class FieldReachable private[Infos] (
139+
val fieldName: FieldName,
140+
val read: Boolean = false,
141+
val written: Boolean = false
142+
) extends MemberReachabilityInfo
143+
144+
final case class StaticFieldReachable private[Infos] (
145+
val fieldName: FieldName,
146+
val read: Boolean = false,
147+
val written: Boolean = false
148+
) extends MemberReachabilityInfo
149+
150+
final case class MethodReachable private[Infos] (
151+
val methodName: MethodName
152+
) extends MemberReachabilityInfo
153+
154+
final case class MethodStaticallyReachable private[Infos] (
155+
val namespace: MemberNamespace,
156+
val methodName: MethodName
157+
) extends MemberReachabilityInfo
158+
159+
object MethodStaticallyReachable {
160+
private[Infos] def apply(m: NamespacedMethodName): MethodStaticallyReachable =
161+
MethodStaticallyReachable(m.namespace, m.methodName)
162+
}
163+
164+
final case class JSNativeMemberReachable private[Infos] (
165+
val methodName: MethodName
166+
) extends MemberReachabilityInfo
167+
137168
final class ClassInfoBuilder(
138169
private val className: ClassName,
139170
private val kind: ClassKind,
@@ -378,33 +409,39 @@ object Infos {
378409
}
379410

380411
final class ReachabilityInfoInClassBuilder(val className: ClassName) {
381-
private val fieldsRead = mutable.Set.empty[FieldName]
382-
private val fieldsWritten = mutable.Set.empty[FieldName]
383-
private val staticFieldsRead = mutable.Set.empty[FieldName]
384-
private val staticFieldsWritten = mutable.Set.empty[FieldName]
412+
private val fieldsUsed = mutable.Map.empty[FieldName, FieldReachable]
413+
private val staticFieldsUsed = mutable.Map.empty[FieldName, StaticFieldReachable]
385414
private val methodsCalled = mutable.Set.empty[MethodName]
386415
private val methodsCalledStatically = mutable.Set.empty[NamespacedMethodName]
387416
private val jsNativeMembersUsed = mutable.Set.empty[MethodName]
388417
private var flags: ReachabilityInfoInClass.Flags = 0
389418

390419
def addFieldRead(field: FieldName): this.type = {
391-
fieldsRead += field
420+
fieldsUsed(field) = fieldsUsed
421+
.getOrElse(field, FieldReachable(field))
422+
.copy(read = true)
392423
this
393424
}
394425

395426
def addFieldWritten(field: FieldName): this.type = {
396-
fieldsWritten += field
427+
fieldsUsed(field) = fieldsUsed
428+
.getOrElse(field, FieldReachable(field))
429+
.copy(written = true)
397430
this
398431
}
399432

400433
def addStaticFieldRead(field: FieldName): this.type = {
401-
staticFieldsRead += field
434+
staticFieldsUsed(field) = staticFieldsUsed
435+
.getOrElse(field, StaticFieldReachable(field))
436+
.copy(read = true)
402437
setStaticallyReferenced()
403438
this
404439
}
405440

406441
def addStaticFieldWritten(field: FieldName): this.type = {
407-
staticFieldsWritten += field
442+
staticFieldsUsed(field) = staticFieldsUsed
443+
.getOrElse(field, StaticFieldReachable(field))
444+
.copy(written = true)
408445
setStaticallyReferenced()
409446
this
410447
}
@@ -454,22 +491,20 @@ object Infos {
454491
setFlag(ReachabilityInfoInClass.FlagStaticallyReferenced)
455492

456493
def result(): ReachabilityInfoInClass = {
457-
new ReachabilityInfoInClass(
458-
className,
459-
fieldsRead = toLikelyEmptyList(fieldsRead),
460-
fieldsWritten = toLikelyEmptyList(fieldsWritten),
461-
staticFieldsRead = toLikelyEmptyList(staticFieldsRead),
462-
staticFieldsWritten = toLikelyEmptyList(staticFieldsWritten),
463-
methodsCalled = toLikelyEmptyList(methodsCalled),
464-
methodsCalledStatically = toLikelyEmptyList(methodsCalledStatically),
465-
jsNativeMembersUsed = toLikelyEmptyList(jsNativeMembersUsed),
466-
flags = flags
467-
)
468-
}
494+
val memberInfos: Array[MemberReachabilityInfo] = (
495+
fieldsUsed.valuesIterator ++
496+
staticFieldsUsed.valuesIterator ++
497+
methodsCalled.iterator.map(MethodReachable(_)) ++
498+
methodsCalledStatically.iterator.map(MethodStaticallyReachable(_)) ++
499+
jsNativeMembersUsed.iterator.map(JSNativeMemberReachable(_))
500+
).toArray
501+
502+
val memberInfosOrNull =
503+
if (memberInfos.isEmpty) null
504+
else memberInfos
469505

470-
private def toLikelyEmptyList[A](set: mutable.Set[A]): List[A] =
471-
if (set.isEmpty) Nil
472-
else set.toList
506+
new ReachabilityInfoInClass(className, memberInfosOrNull, flags)
507+
}
473508
}
474509

475510
/** Generates the [[MethodInfo]] of a

0 commit comments

Comments
 (0)
0