Skip to content

Default to emitting .tasty files (not .hasTasty) #4467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/backend/jvm/GenBCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter
for (binary <- ctx.compilationUnit.pickled.get(claszSymbol.asClass)) {
val store = if (mirrorC ne null) mirrorC else plainC
val tasty =
if (ctx.settings.YemitTasty.value) {
if (!ctx.settings.YemitTastyInClass.value) {
val outTastyFile = getFileForClassfile(outF, store.name, ".tasty")
val outstream = new DataOutputStream(outTastyFile.bufferedOutput)
try outstream.write(binary)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class ScalaSettings extends Settings.SettingGroup {
val YdebugNames = BooleanSetting("-Ydebug-names", "Show internal representation of names")
val YtermConflict = ChoiceSetting("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error")
val Ylog = PhasesSetting("-Ylog", "Log operations during")
val YemitTasty = BooleanSetting("-Yemit-tasty", "Generate tasty in separate *.tasty file.")
val YemitTastyInClass = BooleanSetting("-Yemit-tasty-in-class", "Generate tasty in the .class file and add an empty *.hasTasty file.")
val YlogClasspath = BooleanSetting("-Ylog-classpath", "Output information about what classpath is being applied.")
val YdisableFlatCpCaching = BooleanSetting("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")

Expand Down
43 changes: 28 additions & 15 deletions compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import Contexts._, Symbols._, Types._, Names._, StdNames._, NameOps._, Scopes._,
import SymDenotations._, unpickleScala2.Scala2Unpickler._, Constants._, Annotations._, util.Positions._
import NameKinds.{ModuleClassName, DefaultGetterName}
import ast.tpd._
import java.io.{ ByteArrayInputStream, DataInputStream, File, IOException }
import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, DataInputStream, File, IOException }
import java.nio
import java.lang.Integer.toHexString
import java.net.URLClassLoader

import scala.collection.{ mutable, immutable }
import scala.collection.mutable.{ ListBuffer, ArrayBuffer }
import scala.annotation.switch
Expand Down Expand Up @@ -784,24 +786,35 @@ class ClassfileParser(
if (scan(tpnme.TASTYATTR)) {
val attrLen = in.nextInt
if (attrLen == 0) { // A tasty attribute implies the existence of the .tasty file
def readTastyForClass(jpath: nio.file.Path): Array[Byte] = {
val plainFile = new PlainFile(io.File(jpath).changeExtension("tasty"))
if (plainFile.exists) plainFile.toByteArray
else {
ctx.error("Could not find " + plainFile)
Array.empty
}
}
val tastyBytes = classfile.underlyingSource match { // TODO: simplify when #3552 is fixed
val tastyBytes: Array[Byte] = classfile.underlyingSource match { // TODO: simplify when #3552 is fixed
case None =>
ctx.error("Could not load TASTY from .tasty for virtual file " + classfile)
Array.empty[Byte]
Array.empty
case Some(jar: ZipArchive) => // We are in a jar
val jarFile = JarArchive.open(io.File(jar.jpath))
try readTastyForClass(jarFile.jpath.resolve(classfile.path))
finally jarFile.close()
val cl = new URLClassLoader(Array(jar.jpath.toUri.toURL))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure we won't have to deal with the same issues using a URLClassLoader?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say no because the classloader never instanciates an class with a reference to it. Hence the classloader should be GCed at some point. I will experiment a bit with the profiler to see if I can observe any leaks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not only leaks. Can the jar be read using an URLClassLoader while at the same time it is read using nio.file.FileSystems. What if someone close the FileSystems while you're reading the jar with URLClassLoader?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it and it does not affect the class loader. I closed the file system in the middle of reading from the stream and some other places.

val path = classfile.path.stripSuffix(".class") + ".tasty"
val stream = cl.getResourceAsStream(path)
if (stream != null) {
val tastyOutStream = new ByteArrayOutputStream()
val buffer = new Array[Byte](1024)
var read = stream.read(buffer, 0, buffer.length)
while (read != -1) {
tastyOutStream.write(buffer, 0, read)
read = stream.read(buffer, 0, buffer.length)
}
tastyOutStream.flush()
tastyOutStream.toByteArray
} else {
ctx.error(s"Could not find $path in $jar")
Array.empty
}
case _ =>
readTastyForClass(classfile.jpath)
val plainFile = new PlainFile(io.File(classfile.jpath).changeExtension("tasty"))
if (plainFile.exists) plainFile.toByteArray
else {
ctx.error("Could not find " + plainFile)
Array.empty
}
}
if (tastyBytes.nonEmpty)
return unpickleTASTY(tastyBytes)
Expand Down
12 changes: 8 additions & 4 deletions compiler/src/dotty/tools/io/JarArchive.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dotty.tools.io

import java.nio.file.{Files, FileSystem, FileSystems}
import java.nio.file.{FileSystemAlreadyExistsException, FileSystems}

import scala.collection.JavaConverters._

Expand All @@ -9,7 +9,7 @@ import scala.collection.JavaConverters._
* that be can used as the compiler's output directory.
*/
class JarArchive private (root: Directory) extends PlainDirectory(root) {
def close() = jpath.getFileSystem().close()
def close(): Unit = jpath.getFileSystem().close()
}

object JarArchive {
Expand All @@ -28,8 +28,12 @@ object JarArchive {
// https://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html
val env = Map("create" -> create.toString).asJava
val uri = java.net.URI.create("jar:file:" + path.toAbsolute.path)
val fs = FileSystems.newFileSystem(uri, env)

val fs = {
try FileSystems.newFileSystem(uri, env)
catch {
case _: FileSystemAlreadyExistsException => FileSystems.getFileSystem(uri)
}
}
val root = fs.getRootDirectories().iterator.next()
new JarArchive(Directory(root))
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class CompilationTests extends ParallelTesting {
compileFilesInDir("tests/pos", defaultOptions) +
compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes) +
compileFilesInDir("tests/pos-kind-polymorphism", defaultOptions and "-Ykind-polymorphism") +
compileDir("tests/pos/i1137-1", defaultOptions and "-Yemit-tasty") +
compileDir("tests/pos/i1137-1", defaultOptions) +
compileFile(
// succeeds despite -Xfatal-warnings because of -nowarn
"tests/neg-custom-args/fatal-warnings/xfatalWarnings.scala",
Expand Down Expand Up @@ -254,7 +254,7 @@ class CompilationTests extends ParallelTesting {
defaultOutputDir + dotty1Group + "/dotty/:" +
// and the other compiler dependecies:
Jars.dottyInterfaces + ":" + Jars.jline,
Array("-Ycheck-reentrant")
Array("-Ycheck-reentrant", "-Yemit-tasty-in-class")
)

val lib =
Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/dotc/IdempotencyTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class IdempotencyTests extends ParallelTesting {
@Category(Array(classOf[SlowTests]))
@Test def idempotency: Unit = {
implicit val testGroup: TestGroup = TestGroup("idempotency")
val opt = defaultOptions.and("-Yemit-tasty")
val opt = defaultOptions

def sourcesFrom(dir: Path) = CompilationTests.sources(Files.walk(dir))

Expand Down
16 changes: 9 additions & 7 deletions compiler/test/dotty/tools/vulpix/ParallelTesting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,11 @@ trait ParallelTesting extends RunnerOrchestration { self =>
tastyOutput.mkdir()
val flags = flags0 and ("-d", tastyOutput.getAbsolutePath) and "-from-tasty"

def hasTastyFileToClassName(f: JFile): String =
targetDir.toPath.relativize(f.toPath).toString.dropRight(".hasTasty".length).replace('/', '.')
val classes = flattenFiles(targetDir).filter(isHasTastyFile).map(hasTastyFileToClassName)
def tastyFileToClassName(f: JFile): String = {
val pathStr = targetDir.toPath.relativize(f.toPath).toString.replace('/', '.')
pathStr.stripSuffix(".tasty").stripSuffix(".hasTasty")
}
val classes = flattenFiles(targetDir).filter(isTastyFile).map(tastyFileToClassName)

val reporter =
TestReporter.reporter(realStdout, logLevel =
Expand Down Expand Up @@ -434,8 +436,8 @@ trait ParallelTesting extends RunnerOrchestration { self =>
"-decompile" and "-pagewidth" and "80"

def hasTastyFileToClassName(f: JFile): String =
targetDir0.toPath.relativize(f.toPath).toString.dropRight(".hasTasty".length).replace('/', '.')
val classes = flattenFiles(targetDir0).filter(isHasTastyFile).map(hasTastyFileToClassName).sorted
targetDir0.toPath.relativize(f.toPath).toString.stripSuffix(".hasTasty").stripSuffix(".tasty").replace('/', '.')
val classes = flattenFiles(targetDir0).filter(isTastyFile).map(hasTastyFileToClassName).sorted

val reporter =
TestReporter.reporter(realStdout, logLevel =
Expand Down Expand Up @@ -1368,6 +1370,6 @@ object ParallelTesting {
name.endsWith(".scala") || name.endsWith(".java")
}

def isHasTastyFile(f: JFile): Boolean =
f.getName.endsWith(".hasTasty")
def isTastyFile(f: JFile): Boolean =
f.getName.endsWith(".hasTasty") || f.getName.endsWith(".tasty")
}
2 changes: 1 addition & 1 deletion project/scripts/cmdTests
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ grep -qe "$EXPECTED_OUTPUT" "$tmp"

echo "testing loading tasty from .tasty file in jar"
clear_out "$OUT"
"$SBT" ";dotc -d $OUT/out.jar -Yemit-tasty $SOURCE; dotc -decompile -classpath $OUT/out.jar -color:never $MAIN" > "$tmp"
"$SBT" ";dotc -d $OUT/out.jar $SOURCE; dotc -decompile -classpath $OUT/out.jar -color:never $MAIN" > "$tmp"
grep -qe "def main(args: scala.Array\[scala.Predef.String\]): scala.Unit =" "$tmp"

echo "testing scala.quoted.Expr.run from sbt dotr"
Expand Down