Skip to content

Commit 67c151f

Browse files
committed
Split up cache for ClassBTypes in two, according to origin
Split up the `classBTypeFromInternalName` map in two, depending if the type was created from a symbol or classfile. This allows adding an additional assertion: when getting a `ClassBType` for a symbol that's being compiled in the current run, we want to assert that an existing, cached type was created from that symbol, and not from a (matching, but old) classfile on the classpath. Note that creating `ClassBTypes` from symbols is the ordinary case. In particular, bytecode generation creats these types for every class being emitted. Types created from classfiles are only used for type references not present in the soruce but introduced by the inliner. At this later stage, all types for classes being compiled are already created (and cached).
1 parent 8e40bef commit 67c151f

File tree

10 files changed

+73
-43
lines changed

10 files changed

+73
-43
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/BTypes.scala

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,19 @@ abstract class BTypes {
6464
def compilerSettings: ScalaSettings
6565

6666
/**
67-
* A map from internal names to ClassBTypes. Every ClassBType is added to this map on its
68-
* construction.
67+
* Every ClassBType is cached on construction and accessible through this method.
6968
*
70-
* This map is used when computing stack map frames. The asm.ClassWriter invokes the method
69+
* The cache is used when computing stack map frames. The asm.ClassWriter invokes the method
7170
* `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.
71+
* name. The method assumes that every class type that appears in the bytecode exists in the map
7672
*/
77-
val classBTypeFromInternalName: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
73+
def cachedClassBType(internalName: InternalName): Option[ClassBType] =
74+
classBTypeCacheFromSymbol.get(internalName).orElse(classBTypeCacheFromClassfile.get(internalName))
75+
76+
// Concurrent maps because stack map frames are computed when in the class writer, which
77+
// might run on multiple classes concurrently.
78+
val classBTypeCacheFromSymbol: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
79+
val classBTypeCacheFromClassfile: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
7880

7981
/**
8082
* Store the position of every MethodInsnNode during code generation. This allows each callsite
@@ -173,8 +175,8 @@ abstract class BTypes {
173175
* be found in the `byteCodeRepository`, the `info` of the resulting ClassBType is undefined.
174176
*/
175177
def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = {
176-
classBTypeFromInternalName.getOrElse(internalName, {
177-
val res = ClassBType(internalName)
178+
cachedClassBType(internalName).getOrElse({
179+
val res = ClassBType(internalName)(classBTypeCacheFromClassfile)
178180
byteCodeRepository.classNode(internalName) match {
179181
case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res
180182
case Right(c) => setClassInfoFromClassNode(c, res)
@@ -186,8 +188,8 @@ abstract class BTypes {
186188
* Construct the [[ClassBType]] for a parsed classfile.
187189
*/
188190
def classBTypeFromClassNode(classNode: ClassNode): ClassBType = {
189-
classBTypeFromInternalName.getOrElse(classNode.name, {
190-
setClassInfoFromClassNode(classNode, ClassBType(classNode.name))
191+
cachedClassBType(classNode.name).getOrElse({
192+
setClassInfoFromClassNode(classNode, ClassBType(classNode.name)(classBTypeCacheFromClassfile))
191193
})
192194
}
193195

@@ -847,7 +849,7 @@ abstract class BTypes {
847849
* a missing info. In order not to crash the compiler unnecessarily, the inliner does not force
848850
* infos using `get`, but it reports inliner warnings for missing infos that prevent inlining.
849851
*/
850-
final case class ClassBType(internalName: InternalName) extends RefBType {
852+
final case class ClassBType(internalName: InternalName)(cache: mutable.Map[InternalName, ClassBType]) extends RefBType {
851853
/**
852854
* Write-once variable allows initializing a cyclic graph of infos. This is required for
853855
* nested classes. Example: for the definition `class A { class B }` we have
@@ -868,7 +870,7 @@ abstract class BTypes {
868870
checkInfoConsistency()
869871
}
870872

871-
classBTypeFromInternalName(internalName) = this
873+
cache(internalName) = this
872874

873875
private def checkInfoConsistency(): Unit = {
874876
if (info.isLeft) return

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

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

@@ -626,8 +631,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
626631
def mirrorClassClassBType(moduleClassSym: Symbol): ClassBType = {
627632
assert(isTopLevelModuleClass(moduleClassSym), s"not a top-level module class: $moduleClassSym")
628633
val internalName = moduleClassSym.javaBinaryNameString.stripSuffix(nme.MODULE_SUFFIX_STRING)
629-
classBTypeFromInternalName.getOrElse(internalName, {
630-
val c = ClassBType(internalName)
634+
cachedClassBType(internalName).getOrElse({
635+
val c = ClassBType(internalName)(classBTypeCacheFromSymbol)
631636
// class info consistent with BCodeHelpers.genMirrorClass
632637
val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol
633638
c.info = Right(ClassInfo(
@@ -643,8 +648,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
643648

644649
def beanInfoClassClassBType(mainClass: Symbol): ClassBType = {
645650
val internalName = mainClass.javaBinaryNameString + "BeanInfo"
646-
classBTypeFromInternalName.getOrElse(internalName, {
647-
val c = ClassBType(internalName)
651+
cachedClassBType(internalName).getOrElse({
652+
val c = ClassBType(internalName)(classBTypeCacheFromSymbol)
648653
c.info = Right(ClassInfo(
649654
superClass = Some(sbScalaBeanInfoRef),
650655
interfaces = Nil,

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
8181

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

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ class BTypesFromClassfileTest extends BytecodeTesting {
2727
duringBackend(global.scalaPrimitives.init()) // needed: it's only done when running the backend, and we don't actually run the compiler
2828
duringBackend(bTypes.initializeCoreBTypes())
2929

30-
def clearCache() = bTypes.classBTypeFromInternalName.clear()
30+
def clearCache() = {
31+
bTypes.classBTypeCacheFromSymbol.clear()
32+
bTypes.classBTypeCacheFromClassfile.clear()
33+
}
3134

3235
def sameBType(fromSym: ClassBType, fromClassfile: ClassBType, checked: Set[InternalName] = Set.empty): Set[InternalName] = {
3336
if (checked(fromSym.internalName)) checked

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class CallGraphTest extends BytecodeTesting {
2222
import compiler._
2323
import global.genBCode.bTypes
2424
val notPerRun: List[Clearable] = List(
25-
bTypes.classBTypeFromInternalName,
25+
bTypes.classBTypeCacheFromSymbol,
26+
bTypes.classBTypeCacheFromClassfile,
2627
bTypes.byteCodeRepository.compilingClasses,
2728
bTypes.byteCodeRepository.parsedClasses,
2829
bTypes.callGraph.callsites)
@@ -145,7 +146,7 @@ class CallGraphTest extends BytecodeTesting {
145146
val m = getAsmMethod(c, "m")
146147
val List(fn) = callsInMethod(m)
147148
val forNameMeth = byteCodeRepository.methodNode("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;").get._1
148-
val classTp = classBTypeFromInternalName("java/lang/Class")
149+
val classTp = cachedClassBType("java/lang/Class").get
149150
val r = callGraph.callsites(m)(fn)
150151
checkCallsite(fn, m, forNameMeth, classTp, safeToInline = false, atInline = false, atNoInline = false)
151152
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class InlineInfoTest extends BytecodeTesting {
2121
override def compilerArgs = "-opt:l:classpath"
2222

2323
def notPerRun: List[Clearable] = List(
24-
bTypes.classBTypeFromInternalName,
24+
bTypes.classBTypeCacheFromSymbol,
25+
bTypes.classBTypeCacheFromClassfile,
2526
bTypes.byteCodeRepository.compilingClasses,
2627
bTypes.byteCodeRepository.parsedClasses)
2728
notPerRun foreach global.perRunCaches.unrecordCache
@@ -52,7 +53,7 @@ class InlineInfoTest extends BytecodeTesting {
5253
""".stripMargin
5354
val classes = compile(code)
5455

55-
val fromSyms = classes.map(c => global.genBCode.bTypes.classBTypeFromInternalName(c.name).info.get.inlineInfo)
56+
val fromSyms = classes.map(c => global.genBCode.bTypes.cachedClassBType(c.name).get.info.get.inlineInfo)
5657

5758
val fromAttrs = classes.map(c => {
5859
assert(c.attrs.asScala.exists(_.isInstanceOf[InlineInfoAttribute]), c.attrs)
@@ -71,7 +72,7 @@ class InlineInfoTest extends BytecodeTesting {
7172
|}
7273
""".stripMargin
7374
compileClasses("class C { new A }", javaCode = List((jCode, "A.java")))
74-
val info = global.genBCode.bTypes.classBTypeFromInternalName("A").info.get.inlineInfo
75+
val info = global.genBCode.bTypes.cachedClassBType("A").get.info.get.inlineInfo
7576
assertEquals(info.methodInfos, Map(
7677
"bar()I" -> MethodInlineInfo(true,false,false),
7778
"<init>()V" -> MethodInlineInfo(false,false,false),

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ class InlinerIllegalAccessTest extends BytecodeTesting {
2323
def assertEmpty(ins: List[AbstractInsnNode]) = for (i <- ins)
2424
throw new AssertionError(textify(i))
2525

26+
def clearClassBTypeCaches(): Unit = {
27+
classBTypeCacheFromSymbol.clear()
28+
classBTypeCacheFromClassfile.clear()
29+
}
30+
2631
@Test
2732
def typeAccessible(): Unit = {
2833
val code =
@@ -56,7 +61,7 @@ class InlinerIllegalAccessTest extends BytecodeTesting {
5661
check(eClass, assertEmpty) // C is public, so accessible in E
5762

5863
byteCodeRepository.parsedClasses.clear()
59-
classBTypeFromInternalName.clear()
64+
clearClassBTypeCaches()
6065

6166
cClass.access &= ~ACC_PUBLIC // ftw
6267
addToRepo(allClasses)
@@ -72,6 +77,11 @@ class InlinerIllegalAccessTest extends BytecodeTesting {
7277
}
7378
// MatchError otherwise
7479
})
80+
81+
// ensure the caches are empty at the end for the next test to run (`check` caches types by
82+
// calling `classBTypeFromParsedClassfile`). note that per-run caches are cleared at the end
83+
// of a compilation, not the beginning.
84+
clearClassBTypeCaches()
7585
}
7686

7787
@Test
@@ -190,5 +200,10 @@ class InlinerIllegalAccessTest extends BytecodeTesting {
190200
// privated method accesses can only be inlined in the same class
191201
for (m <- Set(rdC, rhC)) check(m, cCl, cCl, assertEmpty)
192202
for (m <- Set(rdC, rhC); c <- allClasses.tail) check(m, cCl, c, cOrDOwner)
203+
204+
// ensure the caches are empty at the end for the next test to run (`check` caches types by
205+
// calling `classBTypeFromParsedClassfile`). note that per-run caches are cleared at the end
206+
// of a compilation, not the beginning.
207+
clearClassBTypeCaches()
193208
}
194209
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class InlinerTest extends BytecodeTesting {
2727
import global.genBCode.bTypes
2828
// allows inspecting the caches after a compilation run
2929
def notPerRun: List[Clearable] = List(
30-
bTypes.classBTypeFromInternalName,
30+
bTypes.classBTypeCacheFromSymbol,
31+
bTypes.classBTypeCacheFromClassfile,
3132
bTypes.byteCodeRepository.compilingClasses,
3233
bTypes.byteCodeRepository.parsedClasses,
3334
bTypes.callGraph.callsites)

0 commit comments

Comments
 (0)