Skip to content

Commit 3e52000

Browse files
committed
Classpath infrastracture to support pipelining, caching.
1 parent d904ee7 commit 3e52000

File tree

15 files changed

+655
-24
lines changed

15 files changed

+655
-24
lines changed

src/compiler/scala/tools/nsc/PipelineMain.scala

Lines changed: 456 additions & 0 deletions
Large diffs are not rendered by default.

src/compiler/scala/tools/nsc/backend/JavaPlatform.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ trait JavaPlatform extends Platform {
2727
private[nsc] var currentClassPath: Option[ClassPath] = None
2828

2929
private[nsc] def classPath: ClassPath = {
30-
if (currentClassPath.isEmpty) currentClassPath = Some(new PathResolver(settings).result)
30+
if (currentClassPath.isEmpty) currentClassPath = Some(applyClassPathPlugins(new PathResolver(settings).result))
3131
currentClassPath.get
3232
}
3333

src/compiler/scala/tools/nsc/backend/Platform.scala

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
package scala.tools.nsc
1414
package backend
1515

16+
import java.nio.ByteBuffer
17+
1618
import io.AbstractFile
19+
import scala.tools.nsc.classpath.AggregateClassPath
1720
import scala.tools.nsc.util.ClassPath
1821

1922
/** The platform dependent pieces of Global.
@@ -44,5 +47,57 @@ trait Platform {
4447
* a re-compile is triggered. On .NET by contrast classfiles always take precedence.
4548
*/
4649
def needCompile(bin: AbstractFile, src: AbstractFile): Boolean
50+
51+
/**
52+
* A class path plugin can modify the classpath before it is used by the compiler, and can
53+
* customize the way that the compiler reads the contents of class files.
54+
*
55+
* Applications could include:
56+
*
57+
* - Caching the ScalaSignature annotation contents, to avoid the cost of decompressing
58+
* and parsing the classfile, akin to the OpenJDK's .sig format for stripped class files.
59+
* - Starting a downstream compilation job immediately after the upstream job has completed
60+
* the pickler phase ("Build Pipelineing")
61+
*/
62+
abstract class ClassPathPlugin {
63+
def info(file: AbstractFile, clazz: ClassSymbol): Option[ClassfileInfo]
64+
def parsed(file: AbstractFile, clazz: ClassSymbol, info: ClassfileInfo): Unit = ()
65+
def modifyClassPath(classPath: Seq[ClassPath]): Seq[ClassPath] = classPath
66+
}
67+
68+
/** A list of registered classpath plugins */
69+
private var classPathPlugins: List[ClassPathPlugin] = Nil
70+
71+
protected final def applyClassPathPlugins(original: ClassPath): ClassPath = {
72+
val entries = original match {
73+
case AggregateClassPath(entries) => entries
74+
case single => single :: Nil
75+
}
76+
val entries1 = classPathPlugins.foldLeft(entries) {
77+
(entries, plugin) => plugin.modifyClassPath(entries)
78+
}
79+
AggregateClassPath(entries1)
80+
}
81+
82+
83+
/** Registers a new classpath plugin */
84+
final def addClassPathPlugin(plugin: ClassPathPlugin): Unit = {
85+
if (!classPathPlugins.contains(plugin))
86+
classPathPlugins = plugin :: classPathPlugins
87+
}
88+
final def classFileInfo(file: AbstractFile, clazz: ClassSymbol): Option[ClassfileInfo] = if (classPathPlugins eq Nil) None else {
89+
classPathPlugins.foldLeft(Option.empty[ClassfileInfo]) {
90+
case (Some(info), _) => Some(info)
91+
case (None, plugin) => plugin.info(file, clazz)
92+
}
93+
}
94+
final def classFileInfoParsed(file: AbstractFile, clazz: ClassSymbol, info: ClassfileInfo): Unit = if (classPathPlugins eq Nil) None else {
95+
classPathPlugins.foreach(_.parsed(file, clazz, info))
96+
}
4797
}
4898

99+
sealed abstract class ClassfileInfo {}
100+
final case class ClassBytes(data: ByteBuffer) extends ClassfileInfo
101+
final case class ScalaRawClass(className: String) extends ClassfileInfo
102+
final case class ScalaClass(className: String, pickle: ByteBuffer) extends ClassfileInfo
103+

src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi
3535
def isPackage(f: AbstractFile): Boolean = f.isPackage
3636

3737
// mimic the behavior of the old nsc.util.DirectoryClassPath
38-
def asURLs: Seq[URL] = Seq(new URL(dir.name))
38+
def asURLs: Seq[URL] = Seq(new URL("file://_VIRTUAL_/" + dir.name))
3939
def asClassPathStrings: Seq[String] = Seq(dir.path)
4040

4141
override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl

src/compiler/scala/tools/nsc/settings/AbsSettings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ trait AbsSettings extends scala.reflect.internal.settings.AbsSettings {
2525
protected def allSettings: scala.collection.Set[Setting]
2626

2727
// settings minus internal usage settings
28-
def visibleSettings = allSettings filterNot (_.isInternalOnly)
28+
def visibleSettings = allSettings.iterator filterNot (_.isInternalOnly)
2929

3030
// only settings which differ from default
3131
def userSetSettings = visibleSettings filterNot (_.isDefault)

src/compiler/scala/tools/nsc/settings/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ trait ScalaSettings extends AbsScalaSettings
243243
val YcacheMacroClassLoader = CachePolicy.setting("macro", "macros")
244244
val YpartialUnification = BooleanSetting ("-Ypartial-unification", "Enable partial unification in type constructor inference")
245245
val Yvirtpatmat = BooleanSetting ("-Yvirtpatmat", "Enable pattern matcher virtualization")
246+
val Youtline = BooleanSetting ("-Youtline", "Don't compile method bodies. Use together with `-Ystop-afer:pickler to generate the pickled signatures for all source files.")
246247

247248
val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
248249
val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "method")

src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ abstract class SymbolLoaders {
277277

278278
val classPathEntries = classPath.list(packageName)
279279

280+
if (root.name.string_==("immutable"))
281+
getClass
280282
if (!root.isRoot)
281283
for (entry <- classPathEntries.classesAndSources) initializeFromClassPath(root, entry)
282284
if (!root.isEmptyPackageClass) {

src/compiler/scala/tools/nsc/symtab/classfile/AbstractFileReader.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,8 @@ import scala.tools.nsc.io.AbstractFile
2525
* @author Philippe Altherr
2626
* @version 1.0, 23/03/2004
2727
*/
28-
class AbstractFileReader(val file: AbstractFile) {
29-
30-
/** the buffer containing the file
31-
*/
32-
val buf: Array[Byte] = file.toByteArray
28+
class AbstractFileReader(val file: AbstractFile, val buf: Array[Byte]) {
29+
def this(file: AbstractFile) = this(file, file.toByteArray)
3330

3431
/** the current input pointer
3532
*/

src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@ package tools.nsc
1515
package symtab
1616
package classfile
1717

18-
import java.io.{ByteArrayInputStream, DataInputStream, File, IOException}
18+
import java.io._
1919
import java.lang.Integer.toHexString
20+
import java.nio.ByteBuffer
2021

2122
import scala.collection.{immutable, mutable}
2223
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
2324
import scala.annotation.switch
2425
import scala.reflect.internal.JavaAccFlags
2526
import scala.reflect.internal.pickling.{ByteCodecs, PickleBuffer}
26-
import scala.reflect.io.NoAbstractFile
27+
import scala.reflect.io.{NoAbstractFile, VirtualFile}
2728
import scala.reflect.internal.util.Collections._
29+
import scala.tools.nsc.backend.{ClassBytes, ScalaClass, ScalaRawClass}
2830
import scala.tools.nsc.util.ClassPath
2931
import scala.tools.nsc.io.AbstractFile
3032
import scala.util.control.NonFatal
@@ -152,14 +154,56 @@ abstract class ClassfileParser {
152154
def parse(file: AbstractFile, clazz: ClassSymbol, module: ModuleSymbol): Unit = {
153155
this.file = file
154156
pushBusy(clazz) {
155-
this.in = new AbstractFileReader(file)
156157
this.clazz = clazz
157158
this.staticModule = module
158159
this.isScala = false
159160

160-
parseHeader()
161-
this.pool = newConstantPool
162-
parseClass()
161+
import loaders.platform._
162+
classFileInfo(file, clazz) match {
163+
case Some(info) =>
164+
info match {
165+
case ScalaRawClass(className) =>
166+
isScalaRaw = true
167+
currentClass = TermName(className)
168+
case ScalaClass(className, pickle) =>
169+
val pickle1 = pickle
170+
isScala = true
171+
currentClass = TermName(className)
172+
if (pickle1.hasArray) {
173+
unpickler.unpickle(pickle1.array, pickle1.arrayOffset + pickle1.position(), clazz, staticModule, file.name)
174+
} else {
175+
val array = new Array[Byte](pickle1.remaining)
176+
pickle1.get(array)
177+
unpickler.unpickle(array, 0, clazz, staticModule, file.name)
178+
}
179+
case ClassBytes(data) =>
180+
val data1 = data.duplicate()
181+
val array = new Array[Byte](data1.remaining)
182+
data1.get(array)
183+
this.in = new AbstractFileReader(file, array)
184+
parseHeader()
185+
this.pool = newConstantPool
186+
parseClass()
187+
}
188+
case None =>
189+
this.in = new AbstractFileReader(file)
190+
parseHeader()
191+
this.pool = newConstantPool
192+
parseClass()
193+
if (!(isScala || isScalaRaw))
194+
loaders.platform.classFileInfoParsed(file, clazz, ClassBytes(ByteBuffer.wrap(in.buf)))
195+
}
196+
if (isScalaRaw && !isNothingOrNull) {
197+
unlinkRaw()
198+
}
199+
}
200+
}
201+
202+
private def unlinkRaw(): Unit = {
203+
val decls = clazz.enclosingPackage.info.decls
204+
for (c <- List(clazz, staticModule, staticModule.moduleClass)) {
205+
c.setInfo(NoType)
206+
decls.unlink(c)
163207
}
164208
}
165209

@@ -441,6 +485,15 @@ abstract class ClassfileParser {
441485
lookupClass(name)
442486
}
443487

488+
// TODO: remove after the next 2.13 milestone
489+
// A bug in the backend caused classes ending in `$` do get only a Scala marker attribute
490+
// instead of a ScalaSig and a Signature annotaiton. This went unnoticed because isScalaRaw
491+
// classes were parsed like Java classes. The below covers the cases in the std lib.
492+
private def isNothingOrNull = {
493+
val n = clazz.fullName.toString
494+
n == "scala.runtime.Nothing$" || n == "scala.runtime.Null$"
495+
}
496+
444497
def parseClass() {
445498
val jflags = readClassFlags()
446499
val sflags = jflags.toScalaFlags
@@ -890,8 +943,8 @@ abstract class ClassfileParser {
890943
case Some(san: AnnotationInfo) =>
891944
val bytes =
892945
san.assocs.find({ _._1 == nme.bytes }).get._2.asInstanceOf[ScalaSigBytes].bytes
893-
894946
unpickler.unpickle(bytes, 0, clazz, staticModule, in.file.name)
947+
loaders.platform.classFileInfoParsed(file, clazz, ScalaClass(this.currentClass.toString, ByteBuffer.wrap(bytes)))
895948
case None =>
896949
throw new RuntimeException("Scala class file does not contain Scala annotation")
897950
}
@@ -1216,6 +1269,7 @@ abstract class ClassfileParser {
12161269
in.skip(attrLen)
12171270
case tpnme.ScalaATTR =>
12181271
isScalaRaw = true
1272+
loaders.platform.classFileInfoParsed(file, clazz, ScalaRawClass(this.currentClass.toString))
12191273
case tpnme.InnerClassesATTR if !isScala =>
12201274
val entries = u2
12211275
for (i <- 0 until entries) {

src/compiler/scala/tools/nsc/typechecker/Analyzer.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,13 @@ trait Analyzer extends AnyRef
112112
try {
113113
val typer = newTyper(rootContext(unit))
114114
unit.body = typer.typed(unit.body)
115-
for (workItem <- unit.toCheck) workItem()
116-
if (settings.warnUnusedImport)
117-
warnUnusedImports(unit)
118-
if (settings.warnUnused.isSetByUser)
119-
new checkUnused(typer).apply(unit)
115+
if (!settings.Youtline.value) {
116+
for (workItem <- unit.toCheck) workItem()
117+
if (settings.warnUnusedImport)
118+
warnUnusedImports(unit)
119+
if (settings.warnUnused.isSetByUser)
120+
new checkUnused(typer).apply(unit)
121+
}
120122
}
121123
finally {
122124
unit.toCheck.clear()

0 commit comments

Comments
 (0)