Skip to content

Commit f20b840

Browse files
committed
Port Scala#6057 - Pipeline code gen and post-processing
1 parent 952cc14 commit f20b840

14 files changed

+214
-210
lines changed

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ trait BCodeHelpers extends BCodeIdiomatic {
5050
import coreBTypes._
5151
import int.{_, given}
5252
import DottyBackendInterface._
53-
54-
protected def backendUtils = genBCodePhase match {
55-
case genBCode: GenBCode => genBCode.postProcessor.backendUtils
56-
case _ => null
57-
}
53+
54+
// We need to access GenBCode phase to get access to post-processor components.
55+
// At this point it should always be initialized already.
56+
protected lazy val backendUtils = genBCodePhase.asInstanceOf[GenBCode].postProcessor.backendUtils
5857

5958
def ScalaATTRName: String = "Scala"
6059
def ScalaSignatureATTRName: String = "ScalaSig"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
3737
import bTypes._
3838
import coreBTypes._
3939
import bCodeAsmCommon._
40-
40+
4141
lazy val NativeAttr: Symbol = requiredClass[scala.native]
4242

4343
/** The destination of a value generated by `genLoadTo`. */

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,6 @@ abstract class BTypes { self =>
860860
* Just a named pair, used in CoreBTypes.asmBoxTo/asmUnboxTo.
861861
*/
862862
/*final*/ case class MethodNameAndType(name: String, methodType: MethodBType)
863-
864863
}
865864

866865
object BTypes {
@@ -870,6 +869,4 @@ object BTypes {
870869
* But that would create overhead in a Collection[InternalName].
871870
*/
872871
type InternalName = String
873-
874-
875-
}
872+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I, val frontendAcce
3636
}
3737
import coreBTypes._
3838

39-
@threadUnsafe protected lazy val classBTypeFromInternalNameMap =
39+
@threadUnsafe protected lazy val classBTypeFromInternalNameMap =
4040
collection.concurrent.TrieMap.empty[String, ClassBType]
4141

4242
/**

compiler/src/dotty/tools/backend/jvm/BackendUtils.scala

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class BackendUtils(val postProcessor: PostProcessor) {
3535
case "18" => asm.Opcodes.V18
3636
case "19" => asm.Opcodes.V19
3737
}
38-
38+
3939
lazy val extraProc: Int = {
4040
import GenBCodeOps.addFlagIf
4141
val majorVersion: Int = (classfileVersion & 0xFF)
@@ -67,27 +67,27 @@ class BackendUtils(val postProcessor: PostProcessor) {
6767
}
6868

6969
/*
70-
* Add:
71-
*
72-
* private static Object $deserializeLambda$(SerializedLambda l) {
73-
* try return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup$0](l)
74-
* catch {
75-
* case i: IllegalArgumentException =>
76-
* try return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup$1](l)
77-
* catch {
78-
* case i: IllegalArgumentException =>
79-
* ...
80-
* return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup${NUM_GROUPS-1}](l)
81-
* }
82-
*
83-
* We use invokedynamic here to enable caching within the deserializer without needing to
84-
* host a static field in the enclosing class. This allows us to add this method to interfaces
85-
* that define lambdas in default methods.
86-
*
87-
* SI-10232 we can't pass arbitrary number of method handles to the final varargs parameter of the bootstrap
88-
* method due to a limitation in the JVM. Instead, we emit a separate invokedynamic bytecode for each group of target
89-
* methods.
90-
*/
70+
* Add:
71+
*
72+
* private static Object $deserializeLambda$(SerializedLambda l) {
73+
* try return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup$0](l)
74+
* catch {
75+
* case i: IllegalArgumentException =>
76+
* try return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup$1](l)
77+
* catch {
78+
* case i: IllegalArgumentException =>
79+
* ...
80+
* return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup${NUM_GROUPS-1}](l)
81+
* }
82+
*
83+
* We use invokedynamic here to enable caching within the deserializer without needing to
84+
* host a static field in the enclosing class. This allows us to add this method to interfaces
85+
* that define lambdas in default methods.
86+
*
87+
* SI-10232 we can't pass arbitrary number of method handles to the final varargs parameter of the bootstrap
88+
* method due to a limitation in the JVM. Instead, we emit a separate invokedynamic bytecode for each group of target
89+
* methods.
90+
*/
9191
def addLambdaDeserialize(classNode: ClassNode, implMethodsArray: Array[Handle]): Unit = {
9292
import asm.Opcodes._
9393
import bTypes._
@@ -160,7 +160,7 @@ class BackendUtils(val postProcessor: PostProcessor) {
160160
* `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of
161161
* each inner class it lists (those are looked up and included).
162162
*
163-
* This method serializes in the InnerClasses JVM attribute in an appropriate order,
163+
* This method serializes in the InnerClasses JVM attribute in an appropriate order,
164164
* not necessarily that given by `refedInnerClasses`.
165165
*
166166
* can-multi-thread

compiler/src/dotty/tools/backend/jvm/ClassfileWriter.scala

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,39 @@ import scala.language.unsafeNulls
1515

1616
class ClassfileWriter(frontendAccess: PostProcessorFrontendAccess) {
1717
import frontendAccess.{backendReporting, compilerSettings}
18-
18+
1919
// if non-null, classfiles are additionally written to this directory
2020
private val dumpOutputDir: AbstractFile = getDirectoryOrNull(compilerSettings.dumpClassesDirectory)
2121

2222
// if non-null, classfiles are written to a jar instead of the output directory
23-
private val jarWriter: JarWriter =
24-
val f = compilerSettings.outputDirectory
25-
if f.hasExtension("jar") then
26-
// If no main class was specified, see if there's only one
27-
// entry point among the classes going into the jar.
28-
val mainClass = compilerSettings.mainClass match {
29-
case c @ Some(m) =>
30-
backendReporting.log(s"Main-Class was specified: ${m}")
31-
c
32-
case None => frontendAccess.getEntryPoints match {
33-
case Nil =>
34-
backendReporting.log("No Main-Class designated or discovered.")
35-
None
23+
private val jarWriter: JarWriter | Null = compilerSettings.outputDirectory match {
24+
case jar: JarArchive =>
25+
val mainClass = compilerSettings.mainClass.orElse {
26+
// If no main class was specified, see if there's only one
27+
// entry point among the classes going into the jar.
28+
frontendAccess.getEntryPoints match {
3629
case name :: Nil =>
3730
backendReporting.log(s"Unique entry point: setting Main-Class to $name")
3831
Some(name)
3932
case names =>
40-
backendReporting.log(s"No Main-Class due to multiple entry points:\n ${names.mkString("\n ")}")
33+
if names.isEmpty then backendReporting.warning("No Main-Class designated or discovered.")
34+
else backendReporting.warning(s"No Main-Class due to multiple entry points:\n ${names.mkString("\n ")}")
4135
None
4236
}
4337
}
38+
jar.underlyingSource.map{ source =>
39+
if jar.isEmpty then
40+
val jarMainAttrs = mainClass.map(Name.MAIN_CLASS -> _).toList
41+
new Jar(source.file).jarWriter(jarMainAttrs: _*)
42+
else
43+
// Writing to non-empty JAR might be an undefined behaviour, e.g. in case if other files where
44+
// created using `AbstractFile.bufferedOutputStream`instead of JarWritter
45+
backendReporting.warning(s"Tried to write to non-empty JAR: $source")
46+
null
47+
}.orNull
4448

45-
val jarMainAttrs = mainClass.map(c => Name.MAIN_CLASS -> c).toList
46-
new Jar(f.file).jarWriter(jarMainAttrs: _*)
47-
else null
49+
case _ => null
50+
}
4851

4952
private def getDirectoryOrNull(dir: Option[String]): AbstractFile =
5053
dir.map(d => new PlainDirectory(Directory(d))).orNull
@@ -88,20 +91,9 @@ class ClassfileWriter(frontendAccess: PostProcessorFrontendAccess) {
8891
}
8992
}
9093

91-
def write(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile | Null = try {
94+
def writeClass(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile | Null = try {
9295
// val writeStart = Statistics.startTimer(BackendStats.bcodeWriteTimer)
93-
val outFile = if (jarWriter == null) {
94-
val outFolder = compilerSettings.outputDirectory
95-
val outFile = getFile(outFolder, className, ".class")
96-
writeBytes(outFile, bytes)
97-
outFile
98-
} else {
99-
val path = className + ".class"
100-
val out = jarWriter.newOutputStream(path)
101-
try out.write(bytes, 0, bytes.length)
102-
finally out.flush()
103-
null
104-
}
96+
val outFile = writeToJarOrFile(className, bytes, ".class")
10597
// Statistics.stopTimer(BackendStats.bcodeWriteTimer, writeStart)
10698

10799
if (dumpOutputDir != null) {
@@ -111,27 +103,39 @@ class ClassfileWriter(frontendAccess: PostProcessorFrontendAccess) {
111103
outFile
112104
} catch {
113105
case e: FileConflictException =>
114-
backendReporting.error(s"error writing $className: ${e.getMessage}", NoSourcePosition)
106+
backendReporting.error(s"error writing $className: ${e.getMessage}")
115107
null
116108
case e: java.nio.file.FileSystemException =>
117109
if compilerSettings.debug then e.printStackTrace()
118-
backendReporting.error(s"error writing $className: ${e.getClass.getName} ${e.getMessage}", NoSourcePosition)
110+
backendReporting.error(s"error writing $className: ${e.getClass.getName} ${e.getMessage}")
119111
null
120112
}
121113

122-
def writeTasty(className: InternalName, bytes: Array[Byte]): Unit =
123-
val outFolder = compilerSettings.outputDirectory
124-
val outFile = getFile(outFolder, className, ".tasty")
125-
try writeBytes(outFile, bytes)
126-
catch case ex: ClosedByInterruptException =>
127-
try outFile.delete() // don't leave an empty or half-written tastyfile around after an interrupt
128-
catch case _: Throwable => ()
129-
finally throw ex
114+
def writeTasty(className: InternalName, bytes: Array[Byte]): Unit =
115+
writeToJarOrFile(className, bytes, ".tasty")
116+
117+
private def writeToJarOrFile(className: InternalName, bytes: Array[Byte], suffix: String): AbstractFile | Null = {
118+
if jarWriter == null then
119+
val outFolder = compilerSettings.outputDirectory
120+
val outFile = getFile(outFolder, className, suffix)
121+
try writeBytes(outFile, bytes)
122+
catch case ex: ClosedByInterruptException =>
123+
try outFile.delete() // don't leave an empty or half-written files around after an interrupt
124+
catch case _: Throwable => ()
125+
finally throw ex
126+
outFile
127+
else
128+
val path = className + suffix
129+
val out = jarWriter.newOutputStream(path)
130+
try out.write(bytes, 0, bytes.length)
131+
finally out.flush()
132+
null
133+
}
130134

131135
def close(): Unit = {
132136
if (jarWriter != null) jarWriter.close()
133137
}
134138
}
135139

136140
/** Can't output a file due to the state of the file system. */
137-
class FileConflictException(msg: String, val file: AbstractFile) extends IOException(msg)
141+
class FileConflictException(msg: String, val file: AbstractFile) extends IOException(msg)

compiler/src/dotty/tools/backend/jvm/CodeGen.scala

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -40,51 +40,72 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)(
4040

4141
private lazy val mirrorCodeGen = Impl.JMirrorBuilder()
4242

43-
def gen(unit: CompilationUnit): Unit = {
44-
val postProcessor = Phases.genBCodePhase match {
45-
case genBCode: GenBCode => genBCode.postProcessor
46-
case _ => null
47-
}
43+
def genUnit(unit: CompilationUnit): GeneratedDefs = {
44+
val generatedClasses = mutable.ListBuffer.empty[GeneratedClass]
45+
val generatedTasty = mutable.ListBuffer.empty[GeneratedTasty]
4846

4947
def genClassDef(cd: TypeDef): Unit =
5048
try
5149
val sym = cd.symbol
5250
val sourceFile = unit.source.file
5351

54-
def registerGeneratedClass(classNode: ClassNode, isArtifact: Boolean) = if (classNode != null){
55-
postProcessor.generatedClasses += GeneratedClass(classNode, sourceFile, isArtifact, onFileCreated(classNode, sym, unit.source))
56-
}
52+
def registerGeneratedClass(classNode: ClassNode, isArtifact: Boolean): Unit =
53+
generatedClasses += GeneratedClass(classNode, sourceFile, isArtifact, onFileCreated(classNode, sym, unit.source))
5754

5855
val plainC = genClass(cd, unit)
5956
registerGeneratedClass(plainC, isArtifact = false)
6057

61-
val mirrorC =
62-
if !sym.isTopLevelModuleClass then null
63-
else if sym.companionClass == NoSymbol then genMirrorClass(sym, unit)
58+
val attrNode =
59+
if !sym.isTopLevelModuleClass then plainC
60+
else if sym.companionClass == NoSymbol then
61+
val mirrorC = genMirrorClass(sym, unit)
62+
registerGeneratedClass(mirrorC, isArtifact = true)
63+
mirrorC
6464
else
6565
report.log(s"No mirror class for module with linked class: ${sym.fullName}", NoSourcePosition)
66-
null
67-
registerGeneratedClass(mirrorC, isArtifact = true)
66+
plainC
6867

6968
if sym.isClass then
70-
val attrNode = if (mirrorC ne null) mirrorC else plainC
71-
setTastyAttributes(sym, attrNode, postProcessor, unit)
69+
genTastyAndSetAttributes(sym, attrNode)
7270
catch
7371
case ex: Throwable =>
7472
ex.printStackTrace()
7573
report.error(s"Error while emitting ${unit.source}\n${ex.getMessage}", NoSourcePosition)
7674

75+
76+
def genTastyAndSetAttributes(claszSymbol: Symbol, store: ClassNode): Unit =
77+
import Impl.createJAttribute
78+
for (binary <- unit.pickled.get(claszSymbol.asClass)) {
79+
generatedTasty += GeneratedTasty(store, binary)
80+
val tasty =
81+
val uuid = new TastyHeaderUnpickler(binary()).readHeader()
82+
val lo = uuid.getMostSignificantBits
83+
val hi = uuid.getLeastSignificantBits
84+
85+
// TASTY attribute is created but only the UUID bytes are stored in it.
86+
// A TASTY attribute has length 16 if and only if the .tasty file exists.
87+
val buffer = new TastyBuffer(16)
88+
buffer.writeUncompressedLong(lo)
89+
buffer.writeUncompressedLong(hi)
90+
buffer.bytes
91+
92+
val dataAttr = createJAttribute(nme.TASTYATTR.mangledString, tasty, 0, tasty.length)
93+
store.visitAttribute(dataAttr)
94+
}
95+
7796
def genClassDefs(tree: Tree): Unit =
7897
tree match {
7998
case EmptyTree => ()
8099
case PackageDef(_, stats) => stats foreach genClassDefs
81-
case ValDef(_,_,_) => () // module val not emmited
100+
case ValDef(_, _, _) => () // module val not emitted
82101
case td: TypeDef => genClassDef(td)
83102
}
84103

85104
genClassDefs(unit.tpdTree)
105+
GeneratedDefs(generatedClasses.toList, generatedTasty.toList)
86106
}
87107

108+
// Creates a callback that will be evaluated in PostProcessor after creating a file
88109
private def onFileCreated(cls: ClassNode, claszSymbol: Symbol, sourceFile: interfaces.SourceFile): AbstractFile => Unit = clsFile => {
89110
val (fullClassName, isLocal) = atPhase(sbtExtractDependenciesPhase) {
90111
(ExtractDependencies.classNameAsString(claszSymbol), claszSymbol.isLocal)
@@ -112,26 +133,6 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)(
112133
override def jfile = Optional.ofNullable(absfile.file)
113134
}
114135

115-
private def setTastyAttributes(claszSymbol: Symbol, store: ClassNode, postProcessor: PostProcessor, unit: CompilationUnit): Unit =
116-
import Impl.createJAttribute
117-
for (binary <- unit.pickled.get(claszSymbol.asClass)) {
118-
postProcessor.generatedTasty += GeneratedTasty(store, binary)
119-
val tasty =
120-
val uuid = new TastyHeaderUnpickler(binary()).readHeader()
121-
val lo = uuid.getMostSignificantBits
122-
val hi = uuid.getLeastSignificantBits
123-
124-
// TASTY attribute is created but only the UUID bytes are stored in it.
125-
// A TASTY attribute has length 16 if and only if the .tasty file exists.
126-
val buffer = new TastyBuffer(16)
127-
buffer.writeUncompressedLong(lo)
128-
buffer.writeUncompressedLong(hi)
129-
buffer.bytes
130-
131-
val dataAttr = createJAttribute(nme.TASTYATTR.mangledString, tasty, 0, tasty.length)
132-
store.visitAttribute(dataAttr)
133-
}
134-
135136
private def genClass(cd: TypeDef, unit: CompilationUnit): ClassNode = {
136137
val b = new Impl.SyncAndTryBuilder(unit) {}
137138
b.genPlainClass(cd)
@@ -175,4 +176,4 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)(
175176
protected val primitives: DottyPrimitives = self.primitives
176177
}
177178
object Impl extends ImplEarlyInit with BCodeSyncAndTry
178-
}
179+
}

0 commit comments

Comments
 (0)