Skip to content

Commit 5abd5b8

Browse files
committed
Make ClassBType components lazy, prevent forcing symbols unnecessarily
Make the `nestedClasses` and `nestedInfo` fields lazy to ensure the corresponding symbol infos are only forced if necessary.
1 parent 67c151f commit 5abd5b8

File tree

6 files changed

+109
-23
lines changed

6 files changed

+109
-23
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
7979
def tpeTK(tree: Tree): BType = typeToBType(tree.tpe)
8080

8181
def log(msg: => AnyRef) {
82-
global synchronized { global.log(msg) }
82+
frontendLock synchronized { global.log(msg) }
8383
}
8484

8585
/* ---------------- helper utils for generating classes and fields ---------------- */

src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ import scala.tools.nsc.settings.ScalaSettings
3232
abstract class BTypes {
3333
import BTypes.InternalName
3434

35+
// Stages after code generation in the backend (optimizations, classfile writing) are prepared
36+
// to run in parallel on multiple classes. This object should be used for synchronizing operations
37+
// that may access the compiler frontend during these late stages.
38+
val frontendLock: AnyRef = new Object()
39+
3540
val backendUtils: BackendUtils[this.type]
3641

3742
// Some core BTypes are required here, in class BType, where no Global instance is available.
@@ -223,13 +228,13 @@ abstract class BTypes {
223228
})
224229
}
225230

226-
val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({
231+
def nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({
227232
case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name)
228233
})(collection.breakOut)
229234

230235
// if classNode is a nested class, it has an innerClass attribute for itself. in this
231236
// case we build the NestedInfo.
232-
val nestedInfo = classNode.innerClasses.asScala.find(_.name == classNode.name) map {
237+
def nestedInfo = classNode.innerClasses.asScala.find(_.name == classNode.name) map {
233238
case innerEntry =>
234239
val enclosingClass =
235240
if (innerEntry.outerName != null) {
@@ -248,7 +253,7 @@ abstract class BTypes {
248253

249254
val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
250255

251-
classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
256+
classBType.info = Right(ClassInfo(superClass, interfaces, flags, Lazy(nestedClasses), Lazy(nestedInfo), inlineInfo))
252257
classBType
253258
}
254259

@@ -895,7 +900,9 @@ abstract class BTypes {
895900
s"Invalid interfaces in $this: ${info.get.interfaces}"
896901
)
897902

898-
assert(info.get.nestedClasses.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses)
903+
info.get.nestedClasses.onForce { cs =>
904+
assert(cs.forall(c => ifInit(c)(_.isNestedClass.get)), cs)
905+
}
899906
}
900907

901908
/**
@@ -923,17 +930,17 @@ abstract class BTypes {
923930

924931
def isPublic: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_PUBLIC) != 0)
925932

926-
def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.isDefined)
933+
def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.force.isDefined)
927934

928935
def enclosingNestedClassesChain: Either[NoClassBTypeInfo, List[ClassBType]] = {
929936
isNestedClass.flatMap(isNested => {
930937
// if isNested is true, we know that info.get is defined, and nestedInfo.get is also defined.
931-
if (isNested) info.get.nestedInfo.get.enclosingClass.enclosingNestedClassesChain.map(this :: _)
938+
if (isNested) info.get.nestedInfo.force.get.enclosingClass.enclosingNestedClassesChain.map(this :: _)
932939
else Right(Nil)
933940
})
934941
}
935942

936-
def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo map {
943+
def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo.force map {
937944
case NestedInfo(_, outerName, innerName, isStaticNestedClass) =>
938945
InnerClassEntry(
939946
internalName,
@@ -1066,9 +1073,49 @@ abstract class BTypes {
10661073
* @param inlineInfo Information about this class for the inliner.
10671074
*/
10681075
final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int,
1069-
nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo],
1076+
nestedClasses: Lazy[List[ClassBType]], nestedInfo: Lazy[Option[NestedInfo]],
10701077
inlineInfo: InlineInfo)
10711078

1079+
object Lazy {
1080+
def apply[T <: AnyRef](t: => T): Lazy[T] = new Lazy[T](() => t)
1081+
}
1082+
1083+
final class Lazy[T <: AnyRef](t: () => T) {
1084+
private var value: T = null.asInstanceOf[T]
1085+
1086+
private var function = {
1087+
val tt = t // prevent allocating a field for t
1088+
() => { value = tt() }
1089+
}
1090+
1091+
override def toString = if (value == null) "<?>" else value.toString
1092+
1093+
def onForce(f: T => Unit): Unit = {
1094+
if (value != null) f(value)
1095+
else frontendLock.synchronized {
1096+
if (value != null) f(value)
1097+
else {
1098+
val prev = function
1099+
function = () => {
1100+
prev()
1101+
f(value)
1102+
}
1103+
}
1104+
}
1105+
}
1106+
1107+
def force: T = {
1108+
if (value != null) value
1109+
else frontendLock.synchronized {
1110+
if (value == null) {
1111+
function()
1112+
function = null
1113+
}
1114+
value
1115+
}
1116+
}
1117+
}
1118+
10721119
/**
10731120
* Information required to add a class to an InnerClass table.
10741121
* The spec summary above explains what information is required for the InnerClass entry.

src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
360360
* declared but not otherwise referenced in C (from the bytecode or a method / field signature).
361361
* We collect them here.
362362
*/
363-
val nestedClassSymbols = {
363+
lazy val nestedClassSymbols = {
364364
val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases
365365

366366
// The lambdalift phase lifts all nested classes to the enclosing class, so if we collect
@@ -432,7 +432,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
432432
* for A contain both the class B and the module class B.
433433
* Here we get rid of the module class B, making sure that the class B is present.
434434
*/
435-
val nestedClassSymbolsNoJavaModuleClasses = nestedClassSymbols.filter(s => {
435+
def nestedClassSymbolsNoJavaModuleClasses = nestedClassSymbols.filter(s => {
436436
if (s.isJavaDefined && s.isModuleClass) {
437437
// We could also search in nestedClassSymbols for s.linkedClassOfClass, but sometimes that
438438
// returns NoSymbol, so it doesn't work.
@@ -442,9 +442,15 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
442442
} else true
443443
})
444444

445-
val nestedClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol)
445+
val nestedClasses = {
446+
val ph = phase
447+
Lazy(enteringPhase(ph)(nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol)))
448+
}
446449

447-
val nestedInfo = buildNestedInfo(classSym)
450+
val nestedInfo = {
451+
val ph = phase
452+
Lazy(enteringPhase(ph)(buildNestedInfo(classSym)))
453+
}
448454

449455
val inlineInfo = buildInlineInfo(classSym, classBType.internalName)
450456

@@ -634,13 +640,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
634640
cachedClassBType(internalName).getOrElse({
635641
val c = ClassBType(internalName)(classBTypeCacheFromSymbol)
636642
// class info consistent with BCodeHelpers.genMirrorClass
637-
val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol
643+
val nested = Lazy(exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol)
638644
c.info = Right(ClassInfo(
639645
superClass = Some(ObjectRef),
640646
interfaces = Nil,
641647
flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
642648
nestedClasses = nested,
643-
nestedInfo = None,
649+
nestedInfo = Lazy(None),
644650
inlineInfo = EmptyInlineInfo.copy(isEffectivelyFinal = true))) // no method inline infos needed, scala never invokes methods on the mirror class
645651
c
646652
})
@@ -654,8 +660,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
654660
superClass = Some(sbScalaBeanInfoRef),
655661
interfaces = Nil,
656662
flags = javaFlags(mainClass),
657-
nestedClasses = Nil,
658-
nestedInfo = None,
663+
nestedClasses = Lazy(Nil),
664+
nestedInfo = Lazy(None),
659665
inlineInfo = EmptyInlineInfo))
660666
c
661667
})

src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
324324
}
325325

326326
visitInternalName(classNode.name)
327-
innerClasses ++= classBTypeFromParsedClassfile(classNode.name).info.get.nestedClasses
327+
innerClasses ++= classBTypeFromParsedClassfile(classNode.name).info.get.nestedClasses.force
328328

329329
visitInternalName(classNode.superName)
330330
classNode.interfaces.asScala foreach visitInternalName

test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.junit.Test
66
import org.junit.runner.RunWith
77
import org.junit.runners.JUnit4
88

9+
import scala.collection.mutable
910
import scala.tools.asm.Opcodes
1011
import scala.tools.testing.BytecodeTesting
1112

@@ -80,6 +81,18 @@ class BTypesTest extends BytecodeTesting {
8081
}
8182
}
8283

84+
@Test
85+
def lazyForceTest(): Unit = {
86+
val res = new mutable.StringBuilder()
87+
val l = Lazy({res append "1"; "hi"})
88+
l.onForce(v => res append s"-2:$v")
89+
l.onForce(v => res append s"-3:$v:${l.force}") // `force` within `onForce` returns the value
90+
assertEquals("<?>", l.toString)
91+
assertEquals("hi", l.force)
92+
assertEquals("hi", l.toString)
93+
assertEquals("1-2:hi-3:hi:hi", res.toString)
94+
}
95+
8396
// TODO @lry do more tests
8497
@Test
8598
def maxTypeTest() {

test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.junit.Test
66
import org.junit.runner.RunWith
77
import org.junit.runners.JUnit4
88

9+
import scala.collection.mutable
910
import scala.tools.asm.Opcodes._
1011
import scala.tools.nsc.backend.jvm.BTypes.InternalName
1112
import scala.tools.nsc.backend.jvm.BackendReporting._
@@ -52,7 +53,7 @@ class BTypesFromClassfileTest extends BytecodeTesting {
5253
// Nested class symbols can undergo makeNotPrivate (ExplicitOuter). But this is only applied
5354
// for symbols of class symbols that are being compiled, not those read from a pickle.
5455
// So a class may be public in bytecode, but the symbol still says private.
55-
if (fromSym.nestedInfo.isEmpty) fromSym.flags == fromClassfile.flags
56+
if (fromSym.nestedInfo.force.isEmpty) fromSym.flags == fromClassfile.flags
5657
else (fromSym.flags | ACC_PRIVATE | ACC_PUBLIC) == (fromClassfile.flags | ACC_PRIVATE | ACC_PUBLIC)
5758
}, s"class flags differ\n$fromSym\n$fromClassfile")
5859

@@ -70,19 +71,38 @@ class BTypesFromClassfileTest extends BytecodeTesting {
7071
// and anonymous classes as members of the outer class. But not for unpickled symbols).
7172
// The fromClassfile info has all nested classes, including anonymous and local. So we filter
7273
// them out: member classes are identified by having the `outerName` defined.
73-
val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.get.nestedInfo.get.outerName.isDefined)
74+
val memberClassesFromClassfile = fromClassfile.nestedClasses.force.filter(_.info.get.nestedInfo.force.get.outerName.isDefined)
7475
// Sorting is required: the backend sorts all InnerClass entries by internalName before writing
7576
// them to the classfile (to make it deterministic: the entries are collected in a Set during
7677
// code generation).
77-
val chk3 = sameBTypes(fromSym.nestedClasses.sortBy(_.internalName), memberClassesFromClassfile.sortBy(_.internalName), chk2)
78-
sameBTypes(fromSym.nestedInfo.map(_.enclosingClass), fromClassfile.nestedInfo.map(_.enclosingClass), chk3)
78+
val chk3 = sameBTypes(fromSym.nestedClasses.force.sortBy(_.internalName), memberClassesFromClassfile.sortBy(_.internalName), chk2)
79+
sameBTypes(fromSym.nestedInfo.force.map(_.enclosingClass), fromClassfile.nestedInfo.force.map(_.enclosingClass), chk3)
80+
}
81+
82+
// Force all lazy components of a `ClassBType`.
83+
def forceLazy(classBType: ClassBType, done: mutable.Set[ClassBType] = mutable.Set.empty): Unit = {
84+
if (!done(classBType)) {
85+
done += classBType
86+
val i = classBType.info.get
87+
i.superClass.foreach(forceLazy(_, done))
88+
i.interfaces.foreach(forceLazy(_, done))
89+
i.nestedClasses.force.foreach(forceLazy(_, done))
90+
i.nestedInfo.force.foreach(n => forceLazy(n.enclosingClass, done))
91+
}
7992
}
8093

8194
def check(classSym: Symbol): Unit = duringBackend {
8295
clearCache()
8396
val fromSymbol = classBTypeFromSymbol(classSym)
97+
// We need to force all lazy components to ensure that all referenced `ClassBTypes` are
98+
// constructed from symbols. If we keep them lazy, a `ClassBType` might be constructed from a
99+
// classfile first and then picked up cachedClassBType and added to the `fromSymbol` type.
100+
forceLazy(fromSymbol)
101+
84102
clearCache()
85-
val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName)
103+
val fromClassfile = classBTypeFromParsedClassfile(fromSymbol.internalName)
104+
forceLazy(fromClassfile)
105+
86106
sameBType(fromSymbol, fromClassfile)
87107
}
88108

0 commit comments

Comments
 (0)