Skip to content
This repository was archived by the owner on Sep 1, 2020. It is now read-only.

Commit 92ffe04

Browse files
authored
Merge pull request scala#5739 from lrytz/lazyBTypes
Lazy ClassBTypes
2 parents 6bc6ea0 + 5abd5b8 commit 92ffe04

12 files changed

+182
-66
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,12 +276,14 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
276276
final class CClassWriter(flags: Int) extends asm.ClassWriter(flags) {
277277

278278
/**
279-
* This method is thread-safe: it depends only on the BTypes component, which does not depend
280-
* on global. TODO @lry move to a different place where no global is in scope, on bTypes.
279+
* This method is used by asm when computing stack map frames. It is thread-safe: it depends
280+
* only on the BTypes component, which does not depend on global.
281+
* TODO @lry move to a different place where no global is in scope, on bTypes.
281282
*/
282283
override def getCommonSuperClass(inameA: String, inameB: String): String = {
283-
val a = classBTypeFromInternalName(inameA)
284-
val b = classBTypeFromInternalName(inameB)
284+
// All types that appear in a class node need to have their ClassBType cached, see [[cachedClassBType]].
285+
val a = cachedClassBType(inameA).get
286+
val b = cachedClassBType(inameB).get
285287
val lub = a.jvmWiseLUB(b).get
286288
val lubName = lub.internalName
287289
assert(lubName != "scala/Any")

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: 71 additions & 22 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.
@@ -64,17 +69,19 @@ abstract class BTypes {
6469
def compilerSettings: ScalaSettings
6570

6671
/**
67-
* A map from internal names to ClassBTypes. Every ClassBType is added to this map on its
68-
* construction.
72+
* Every ClassBType is cached on construction and accessible through this method.
6973
*
70-
* This map is used when computing stack map frames. The asm.ClassWriter invokes the method
74+
* The cache is used when computing stack map frames. The asm.ClassWriter invokes the method
7175
* `getCommonSuperClass`. In this method we need to obtain the ClassBType for a given internal
72-
* name. The method assumes that every class type that appears in the bytecode exists in the map.
73-
*
74-
* Concurrent because stack map frames are computed when in the class writer, which might run
75-
* on multiple classes concurrently.
76+
* name. The method assumes that every class type that appears in the bytecode exists in the map
7677
*/
77-
val classBTypeFromInternalName: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
78+
def cachedClassBType(internalName: InternalName): Option[ClassBType] =
79+
classBTypeCacheFromSymbol.get(internalName).orElse(classBTypeCacheFromClassfile.get(internalName))
80+
81+
// Concurrent maps because stack map frames are computed when in the class writer, which
82+
// might run on multiple classes concurrently.
83+
val classBTypeCacheFromSymbol: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
84+
val classBTypeCacheFromClassfile: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
7885

7986
/**
8087
* Store the position of every MethodInsnNode during code generation. This allows each callsite
@@ -173,8 +180,8 @@ abstract class BTypes {
173180
* be found in the `byteCodeRepository`, the `info` of the resulting ClassBType is undefined.
174181
*/
175182
def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = {
176-
classBTypeFromInternalName.getOrElse(internalName, {
177-
val res = ClassBType(internalName)
183+
cachedClassBType(internalName).getOrElse({
184+
val res = ClassBType(internalName)(classBTypeCacheFromClassfile)
178185
byteCodeRepository.classNode(internalName) match {
179186
case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res
180187
case Right(c) => setClassInfoFromClassNode(c, res)
@@ -186,8 +193,8 @@ abstract class BTypes {
186193
* Construct the [[ClassBType]] for a parsed classfile.
187194
*/
188195
def classBTypeFromClassNode(classNode: ClassNode): ClassBType = {
189-
classBTypeFromInternalName.getOrElse(classNode.name, {
190-
setClassInfoFromClassNode(classNode, ClassBType(classNode.name))
196+
cachedClassBType(classNode.name).getOrElse({
197+
setClassInfoFromClassNode(classNode, ClassBType(classNode.name)(classBTypeCacheFromClassfile))
191198
})
192199
}
193200

@@ -221,13 +228,13 @@ abstract class BTypes {
221228
})
222229
}
223230

224-
val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({
231+
def nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({
225232
case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name)
226233
})(collection.breakOut)
227234

228235
// if classNode is a nested class, it has an innerClass attribute for itself. in this
229236
// case we build the NestedInfo.
230-
val nestedInfo = classNode.innerClasses.asScala.find(_.name == classNode.name) map {
237+
def nestedInfo = classNode.innerClasses.asScala.find(_.name == classNode.name) map {
231238
case innerEntry =>
232239
val enclosingClass =
233240
if (innerEntry.outerName != null) {
@@ -246,7 +253,7 @@ abstract class BTypes {
246253

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

249-
classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
256+
classBType.info = Right(ClassInfo(superClass, interfaces, flags, Lazy(nestedClasses), Lazy(nestedInfo), inlineInfo))
250257
classBType
251258
}
252259

@@ -857,7 +864,7 @@ abstract class BTypes {
857864
* a missing info. In order not to crash the compiler unnecessarily, the inliner does not force
858865
* infos using `get`, but it reports inliner warnings for missing infos that prevent inlining.
859866
*/
860-
final case class ClassBType(internalName: InternalName) extends RefBType {
867+
final case class ClassBType(internalName: InternalName)(cache: mutable.Map[InternalName, ClassBType]) extends RefBType {
861868
/**
862869
* Write-once variable allows initializing a cyclic graph of infos. This is required for
863870
* nested classes. Example: for the definition `class A { class B }` we have
@@ -878,7 +885,7 @@ abstract class BTypes {
878885
checkInfoConsistency()
879886
}
880887

881-
classBTypeFromInternalName(internalName) = this
888+
cache(internalName) = this
882889

883890
private def checkInfoConsistency(): Unit = {
884891
if (info.isLeft) return
@@ -903,7 +910,9 @@ abstract class BTypes {
903910
s"Invalid interfaces in $this: ${info.get.interfaces}"
904911
)
905912

906-
assert(info.get.nestedClasses.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses)
913+
info.get.nestedClasses.onForce { cs =>
914+
assert(cs.forall(c => ifInit(c)(_.isNestedClass.get)), cs)
915+
}
907916
}
908917

909918
/**
@@ -931,17 +940,17 @@ abstract class BTypes {
931940

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

934-
def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.isDefined)
943+
def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.force.isDefined)
935944

936945
def enclosingNestedClassesChain: Either[NoClassBTypeInfo, List[ClassBType]] = {
937946
isNestedClass.flatMap(isNested => {
938947
// if isNested is true, we know that info.get is defined, and nestedInfo.get is also defined.
939-
if (isNested) info.get.nestedInfo.get.enclosingClass.enclosingNestedClassesChain.map(this :: _)
948+
if (isNested) info.get.nestedInfo.force.get.enclosingClass.enclosingNestedClassesChain.map(this :: _)
940949
else Right(Nil)
941950
})
942951
}
943952

944-
def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo map {
953+
def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo.force map {
945954
case NestedInfo(_, outerName, innerName, isStaticNestedClass) =>
946955
InnerClassEntry(
947956
internalName,
@@ -1074,9 +1083,49 @@ abstract class BTypes {
10741083
* @param inlineInfo Information about this class for the inliner.
10751084
*/
10761085
final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int,
1077-
nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo],
1086+
nestedClasses: Lazy[List[ClassBType]], nestedInfo: Lazy[Option[NestedInfo]],
10781087
inlineInfo: InlineInfo)
10791088

1089+
object Lazy {
1090+
def apply[T <: AnyRef](t: => T): Lazy[T] = new Lazy[T](() => t)
1091+
}
1092+
1093+
final class Lazy[T <: AnyRef](t: () => T) {
1094+
private var value: T = null.asInstanceOf[T]
1095+
1096+
private var function = {
1097+
val tt = t // prevent allocating a field for t
1098+
() => { value = tt() }
1099+
}
1100+
1101+
override def toString = if (value == null) "<?>" else value.toString
1102+
1103+
def onForce(f: T => Unit): Unit = {
1104+
if (value != null) f(value)
1105+
else frontendLock.synchronized {
1106+
if (value != null) f(value)
1107+
else {
1108+
val prev = function
1109+
function = () => {
1110+
prev()
1111+
f(value)
1112+
}
1113+
}
1114+
}
1115+
}
1116+
1117+
def force: T = {
1118+
if (value != null) value
1119+
else frontendLock.synchronized {
1120+
if (value == null) {
1121+
function()
1122+
function = null
1123+
}
1124+
value
1125+
}
1126+
}
1127+
}
1128+
10801129
/**
10811130
* Information required to add a class to an InnerClass table.
10821131
* The spec summary above explains what information is required for the InnerClass entry.

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

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,22 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
118118
else if (classSym == NullClass) srNullRef
119119
else {
120120
val internalName = classSym.javaBinaryNameString
121-
classBTypeFromInternalName.getOrElse(internalName, {
122-
// The new ClassBType is added to the map in its constructor, before we set its info. This
123-
// allows initializing cyclic dependencies, see the comment on variable ClassBType._info.
124-
val res = ClassBType(internalName)
125-
if (completeSilentlyAndCheckErroneous(classSym)) {
126-
res.info = Left(NoClassBTypeInfoClassSymbolInfoFailedSI9111(classSym.fullName))
127-
res
128-
} else {
129-
setClassInfo(classSym, res)
130-
}
131-
})
121+
cachedClassBType(internalName) match {
122+
case Some(bType) =>
123+
if (currentRun.compiles(classSym))
124+
assert(classBTypeCacheFromSymbol.contains(internalName), s"ClassBType for class being compiled was already created from a classfile: ${classSym.fullName}")
125+
bType
126+
case None =>
127+
// The new ClassBType is added to the map in its constructor, before we set its info. This
128+
// allows initializing cyclic dependencies, see the comment on variable ClassBType._info.
129+
val res = ClassBType(internalName)(classBTypeCacheFromSymbol)
130+
if (completeSilentlyAndCheckErroneous(classSym)) {
131+
res.info = Left(NoClassBTypeInfoClassSymbolInfoFailedSI9111(classSym.fullName))
132+
res
133+
} else {
134+
setClassInfo(classSym, res)
135+
}
136+
}
132137
}
133138
}
134139

@@ -358,7 +363,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
358363
* declared but not otherwise referenced in C (from the bytecode or a method / field signature).
359364
* We collect them here.
360365
*/
361-
val nestedClassSymbols = {
366+
lazy val nestedClassSymbols = {
362367
val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases
363368

364369
// The lambdalift phase lifts all nested classes to the enclosing class, so if we collect
@@ -430,7 +435,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
430435
* for A contain both the class B and the module class B.
431436
* Here we get rid of the module class B, making sure that the class B is present.
432437
*/
433-
val nestedClassSymbolsNoJavaModuleClasses = nestedClassSymbols.filter(s => {
438+
def nestedClassSymbolsNoJavaModuleClasses = nestedClassSymbols.filter(s => {
434439
if (s.isJavaDefined && s.isModuleClass) {
435440
// We could also search in nestedClassSymbols for s.linkedClassOfClass, but sometimes that
436441
// returns NoSymbol, so it doesn't work.
@@ -440,9 +445,15 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
440445
} else true
441446
})
442447

443-
val nestedClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol)
448+
val nestedClasses = {
449+
val ph = phase
450+
Lazy(enteringPhase(ph)(nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol)))
451+
}
444452

445-
val nestedInfo = buildNestedInfo(classSym)
453+
val nestedInfo = {
454+
val ph = phase
455+
Lazy(enteringPhase(ph)(buildNestedInfo(classSym)))
456+
}
446457

447458
val inlineInfo = buildInlineInfo(classSym, classBType.internalName)
448459

@@ -632,31 +643,31 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
632643
def mirrorClassClassBType(moduleClassSym: Symbol): ClassBType = {
633644
assert(isTopLevelModuleClass(moduleClassSym), s"not a top-level module class: $moduleClassSym")
634645
val internalName = moduleClassSym.javaBinaryNameString.stripSuffix(nme.MODULE_SUFFIX_STRING)
635-
classBTypeFromInternalName.getOrElse(internalName, {
636-
val c = ClassBType(internalName)
646+
cachedClassBType(internalName).getOrElse({
647+
val c = ClassBType(internalName)(classBTypeCacheFromSymbol)
637648
// class info consistent with BCodeHelpers.genMirrorClass
638-
val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol
649+
val nested = Lazy(exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol)
639650
c.info = Right(ClassInfo(
640651
superClass = Some(ObjectRef),
641652
interfaces = Nil,
642653
flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
643654
nestedClasses = nested,
644-
nestedInfo = None,
655+
nestedInfo = Lazy(None),
645656
inlineInfo = EmptyInlineInfo.copy(isEffectivelyFinal = true))) // no method inline infos needed, scala never invokes methods on the mirror class
646657
c
647658
})
648659
}
649660

650661
def beanInfoClassClassBType(mainClass: Symbol): ClassBType = {
651662
val internalName = mainClass.javaBinaryNameString + "BeanInfo"
652-
classBTypeFromInternalName.getOrElse(internalName, {
653-
val c = ClassBType(internalName)
663+
cachedClassBType(internalName).getOrElse({
664+
val c = ClassBType(internalName)(classBTypeCacheFromSymbol)
654665
c.info = Right(ClassInfo(
655666
superClass = Some(sbScalaBeanInfoRef),
656667
interfaces = Nil,
657668
flags = javaFlags(mainClass),
658-
nestedClasses = Nil,
659-
nestedInfo = None,
669+
nestedClasses = Lazy(Nil),
670+
nestedInfo = Lazy(None),
660671
inlineInfo = EmptyInlineInfo))
661672
c
662673
})

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import scala.tools.nsc.backend.jvm.BTypes.InternalName
2222
* constructor will actually go through the proxy. The lazy vals make sure the instance is assigned
2323
* in the proxy before the fields are initialized.
2424
*
25-
* Note: if we did not re-create the core BTypes on each compiler run, BType.classBTypeFromInternalNameMap
25+
* Note: if we did not re-create the core BTypes on each compiler run, BType.classBTypeCacheFromSymbol
2626
* could not be a perRunCache anymore: the classes defined here need to be in that map, they are
2727
* added when the ClassBTypes are created. The per run cache removes them, so they would be missing
2828
* in the second run.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
9393

9494
// Make sure to reference the ClassBTypes of all types that are used in the code generated
9595
// here (e.g. java/util/Map) are initialized. Initializing a ClassBType adds it to the
96-
// `classBTypeFromInternalName` map. When writing the classfile, the asm ClassWriter computes
96+
// `cachedClassBType` maps. When writing the classfile, the asm ClassWriter computes
9797
// stack map frames and invokes the `getCommonSuperClass` method. This method expects all
9898
// ClassBTypes mentioned in the source code to exist in the map.
9999

@@ -355,7 +355,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
355355
}
356356

357357
visitInternalName(classNode.name)
358-
innerClasses ++= classBTypeFromParsedClassfile(classNode.name).info.get.nestedClasses
358+
innerClasses ++= classBTypeFromParsedClassfile(classNode.name).info.get.nestedClasses.force
359359

360360
visitInternalName(classNode.superName)
361361
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() {

0 commit comments

Comments
 (0)