10000 Merge pull request #4978 from gzm0/single-info · scala-js/scala-js@eb160f1 · GitHub
[go: up one dir, main page]

Skip to content

Commit eb160f1

Browse files
authored
Merge pull request #4978 from gzm0/single-info
Optimize memory consumption of Infos
2 parents 95c38be + 0d81e85 commit eb160f1

File tree

2 files changed

+106
-101
lines changed

2 files changed

+106
-101
lines changed

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

Lines changed: 31 additions & 65 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)(_ => ())
@@ -1432,61 +1423,36 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
14321423
if ((flags & ReachabilityInfoInClass.FlagStaticallyReferenced) != 0) {
14331424
moduleUnit.addStaticDependency(className)
14341425
}
1435-
}
1436-
1437-
/* Since many of the lists below are likely to be empty, we always
1438-
* test `!list.isEmpty` before calling `foreach` or any other
1439-
* processing, avoiding closure allocations.
1440-
*/
1441-
1442-
if (!dataInClass.fieldsRead.isEmpty) {
1443-
clazz.readFields(dataInClass.fieldsRead)
1444-
}
14451426

1446-
if (!dataInClass.fieldsWritten.isEmpty) {
1447-
clazz.writeFields(dataInClass.fieldsWritten)
1427+
if ((flags & ReachabilityInfoInClass.FlagDynamicallyReferenced) != 0) {
1428+
if (isNoModule)
1429+
_errors ::= DynamicImportWithoutModuleSupport(from)
1430+
else
1431+
moduleUnit.addDynamicDependency(className)
1432+
}
14481433
}
14491434

1450-
if (!dataInClass.staticFieldsRead.isEmpty) {
1451-
moduleUnit.addStaticDependency(className)
1452-
dataInClass.staticFieldsRead.foreach(
1453-
clazz._staticFieldsRead.update(_, ()))
1454-
}
1435+
if (dataInClass.memberInfos != null) {
1436+
dataInClass.memberInfos.foreach {
1437+
case field: Infos.FieldReachable =>
1438+
clazz.reachField(field)
14551439

1456-
if (!dataInClass.staticFieldsWritten.isEmpty) {
1457-
moduleUnit.addStaticDependency(className)
1458-
dataInClass.staticFieldsWritten.foreach(
1459-
clazz._staticFieldsWritten.update(_, ()))
1460-
}
1440+
case Infos.StaticFieldReachable(fieldName, read, written) =>
1441+
if (read)
1442+
clazz._staticFieldsRead.update(fieldName, ())
1443+
if (written)
1444+
clazz._staticFieldsWritten.update(fieldName, ())
14611445

1462-
if (!dataInClass.methodsCalled.isEmpty) {
1463-
// Do not add to staticDependencies: We call these on the object.
1464-
for (methodName <- dataInClass.methodsCalled)
1465-
clazz.callMethod(methodName)
1466-
}
1446+
case Infos.MethodReachable(methodName) =>
1447+
clazz.callMethod(methodName)
14671448

1468-
if (!dataInClass.methodsCalledStatically.isEmpty) {
1469-
moduleUnit.addStaticDependency(className)
1470-
for (methodName <- dataInClass.methodsCalledStatically)
1471-
clazz.callMethodStatically(methodName)
1472-
}
1449+
case Infos.MethodStaticallyReachable(namespace, methodName) =>
1450+
clazz.callMethodStatically(namespace, methodName)
14731451

1474-
if (!dataInClass.methodsCalledDynamicImport.isEmpty) {
1475-
if (isNoModule) {
1476-
_errors ::= DynamicImportWithoutModuleSupport(from)
1477-
} else {
1478-
moduleUnit.addDynamicDependency(className)
1479-
// In terms of reachability, a dynamic import call is just a static call.
1480-
for (methodName <- dataInClass.methodsCalledDynamicImport)
1481-
clazz.callMethodStatically(methodName)
1452+
case Infos.JSNativeMemberReachable(methodName) =>
1453+
clazz.useJSNativeMember(methodName).foreach(addLoadSpec(moduleUnit, _))
14821454
}
14831455
}
1484-
1485-
if (!dataInClass.jsNativeMembersUsed.isEmpty) {
1486-
for (member <- dataInClass.jsNativeMembersUsed)
1487-
clazz.useJSNativeMember(member)
1488-
.foreach(addLoadSpec(moduleUnit, _))
1489-
}
14901456
}
14911457
}
14921458

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

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ object Infos {
9393
)
9494

9595
final class ReachabilityInfo private[Infos] (
96-
val byClass: List[ReachabilityInfoInClass],
96+
val byClass: Array[ReachabilityInfoInClass],
9797
val globalFlags: ReachabilityInfo.Flags
9898
)
9999

@@ -109,14 +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 methodsCalledDynamicImport: List[NamespacedMethodName],
119-
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!
120118
val flags: ReachabilityInfoInClass.Flags
121119
)
122120

@@ -132,8 +130,41 @@ object Infos {
132130
final val FlagInstanceTestsUsed = 1 << 2
133131
final val FlagClassDataAccessed = 1 << 3
134132
final val FlagStaticallyReferenced = 1 << 4
133+
final val FlagDynamicallyReferenced = 1 << 5
134+
}
135+
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)
135162
}
136163

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,
@@ -374,52 +405,63 @@ object Infos {
374405
setFlag(ReachabilityInfo.FlagUsedExponentOperator)
375406

376407
def result(): ReachabilityInfo =
377-
new ReachabilityInfo(byClass.valuesIterator.map(_.result()).toList, flags)
408+
new ReachabilityInfo(byClass.valuesIterator.map(_.result()).toArray, flags)
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]
387-
private val methodsCalledDynamicImport = mutable.Set.empty[NamespacedMethodName]
388416
private val jsNativeMembersUsed = mutable.Set.empty[MethodName]
389417
private var flags: ReachabilityInfoInClass.Flags = 0
390418

391419
def addFieldRead(field: FieldName): this.type = {
392-
fieldsRead += field
420+
fieldsUsed(field) = fieldsUsed
421+
.getOrElse(field, FieldReachable(field))
422+
.copy(read = true)
393423
this
394424
}
395425

396426
def addFieldWritten(field: FieldName): this.type = {
397-
fieldsWritten += field
427+
fieldsUsed(field) = fieldsUsed
428+
.getOrElse(field, FieldReachable(field))
429+
.copy(written = true)
398430
this
399431
}
400432

401433
def addStaticFieldRead(field: FieldName): this.type = {
402-
staticFieldsRead += field
434+
staticFieldsUsed(field) = staticFieldsUsed
435+
.getOrElse(field, StaticFieldReachable(field))
436+
.copy(read = true)
437+
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)
445+
setStaticallyReferenced()
408446
this
409447
}
410448

411449
def addMethodCalled(method: MethodName): this.type = {
412450
methodsCalled += method
451+
// Do not call setStaticallyReferenced: We call these methods on the object.
413452
this
414453
}
415454

416455
def addMethodCalledStatically(method: NamespacedMethodName): this.type = {
417456
methodsCalledStatically += method
457+
setStaticallyReferenced()
418458
this
419459
}
420460

421461
def addMethodCalledDynamicImport(method: NamespacedMethodName): this.type = {
422-
methodsCalledDynamicImport += method
462+
// In terms of reachability, a dynamic import call is just a static call.
463+
methodsCalledStatically += method
464+
setFlag(ReachabilityInfoInClass.FlagDynamicallyReferenced)
423465
this
424466
}
425467

@@ -449,23 +491,20 @@ object Infos {
449491
setFlag(ReachabilityInfoInClass.FlagStaticallyReferenced)
450492

451493
def result(): ReachabilityInfoInClass = {
452-
new ReachabilityInfoInClass(
453-
className,
454-
fieldsRead = toLikelyEmptyList(fieldsRead),
455-
fieldsWritten = toLikelyEmptyList(fieldsWritten),
456-
staticFieldsRead = toLikelyEmptyList(staticFieldsRead),
457-
staticFieldsWritten = toLikelyEmptyList(staticFieldsWritten),
458-
methodsCalled = toLikelyEmptyList(methodsCalled),
459-
methodsCalledStatically = toLikelyEmptyList(methodsCalledStatically),
460-
methodsCalledDynamicImport = toLikelyEmptyList(methodsCalledDynamicImport),
461-
jsNativeMembersUsed = toLikelyEmptyList(jsNativeMembersUsed),
462-
flags = flags
463-
)
464-
}
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
465505

466-
private def toLikelyEmptyList[A](set: mutable.Set[A]): List[A] =
467-
if (set.isEmpty) Nil
468-
else set.toList
506+
new ReachabilityInfoInClass(className, memberInfosOrNull, flags)
507+
}
469508
}
470509

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

0 commit comments

Comments
 (0)
0