Skip to content
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

Port JVM backend refactor from Scala 2 #15322

Merged
merged 1 commit into from
Mar 28, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -1297,7 +1297,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
.toList

// `StringConcatFactory` only got added in JDK 9, so use `StringBuilder` for lower
if (classfileVersion < asm.Opcodes.V9) {
if (backendUtils.classfileVersion < asm.Opcodes.V9) {

// Estimate capacity needed for the string builder
val approxBuilderSize = concatArguments.view.map {
Expand Down
95 changes: 6 additions & 89 deletions compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,19 @@ import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
* @version 1.0
*
*/
trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
trait BCodeHelpers extends BCodeIdiomatic {
// for some reason singleton types aren't allowed in constructor calls. will need several casts in code to enforce

//import global._
//import bTypes._
//import coreBTypes._
import bTypes._
import tpd._
import coreBTypes._
import int.{_, given}
import DottyBackendInterface._

// We need to access GenBCode phase to get access to post-processor components.
// At this point it should always be initialized already.
protected lazy val backendUtils = genBCodePhase.asInstanceOf[GenBCode].postProcessor.backendUtils

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

Expand All @@ -64,96 +65,12 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {

val bCodeAsmCommon: BCodeAsmCommon[int.type] = new BCodeAsmCommon(int)

/*
* must-single-thread
*/
def getFileForClassfile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = {
getFile(base, clsName, suffix)
}

/*
* must-single-thread
*/
def getOutFolder(csym: Symbol, cName: String): AbstractFile = {
try {
outputDirectory
} catch {
case ex: Throwable =>
report.error(em"Couldn't create file for class $cName\n${ex.getMessage}", ctx.source.atSpan(csym.span))
null
}
}

final def traitSuperAccessorName(sym: Symbol): String = {
val nameString = sym.javaSimpleName.toString
if (sym.name == nme.TRAIT_CONSTRUCTOR) nameString
else nameString + "$"
}

// -----------------------------------------------------------------------------------------
// finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM)
// Background:
// http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf
// http://comments.gmane.org/gmane.comp.java.vm.languages/2293
// https://issues.scala-lang.org/browse/SI-3872
// -----------------------------------------------------------------------------------------

/* An `asm.ClassWriter` that uses `jvmWiseLUB()`
* The internal name of the least common ancestor of the types given by inameA and inameB.
* It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow
*/
final class CClassWriter(flags: Int) extends asm.ClassWriter(flags) {

/**
* This method is thread-safe: it depends only on the BTypes component, which does not depend
* on global. TODO @lry move to a different place where no global is in scope, on bTypes.
*/
override def getCommonSuperClass(inameA: String, inameB: String): String = {
val a = classBTypeFromInternalName(inameA)
val b = classBTypeFromInternalName(inameB)
val lub = a.jvmWiseLUB(b)
val lubName = lub.internalName
assert(lubName != "scala/Any")
lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things.
}
}

/*
* must-single-thread
*/
def initBytecodeWriter(): BytecodeWriter = {
(None: Option[AbstractFile] /*getSingleOutput*/) match { // todo: implement
case Some(f) if f.hasExtension("jar") =>
new DirectToJarfileWriter(f.file)
case _ =>
factoryNonJarBytecodeWriter()
}
}

/*
* Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner
* classes in BTypes.scala.
*
* `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of
* each inner class it lists (those are looked up and included).
*
* This method serializes in the InnerClasses JVM attribute in an appropriate order,
* not necessarily that given by `refedInnerClasses`.
*
* can-multi-thread
*/
final def addInnerClasses(jclass: asm.ClassVisitor, declaredInnerClasses: List[ClassBType], refedInnerClasses: List[ClassBType]): Unit = {
// sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler
val allNestedClasses = new mutable.TreeSet[ClassBType]()(Ordering.by(_.internalName))
allNestedClasses ++= declaredInnerClasses
refedInnerClasses.foreach(allNestedClasses ++= _.enclosingNestedClassesChain)
for nestedClass <- allNestedClasses
do {
// Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes.
val Some(e) = nestedClass.innerClassAttributeEntry: @unchecked
jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags)
}
}

/*
* can-multi-thread
Expand Down Expand Up @@ -680,7 +597,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {

val mirrorClass = new asm.tree.ClassNode
mirrorClass.visit(
classfileVersion,
backendUtils.classfileVersion,
bType.info.flags,
mirrorName,
null /* no java-generic-signature */,
Expand Down
41 changes: 1 addition & 40 deletions compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,52 +19,13 @@ import dotty.tools.dotc.report
*/
trait BCodeIdiomatic {
val int: DottyBackendInterface
final lazy val bTypes = new BTypesFromSymbols[int.type](int)
val bTypes: BTypesFromSymbols[int.type]

import int.{_, given}
import bTypes._
import coreBTypes._



lazy val target =
val releaseValue = Option(ctx.settings.javaOutputVersion.value).filter(_.nonEmpty)
val targetValue = Option(ctx.settings.XuncheckedJavaOutputVersion.value).filter(_.nonEmpty)
val defaultTarget = "8"
(releaseValue, targetValue) match
case (Some(release), None) => release
case (None, Some(target)) => target
case (Some(release), Some(_)) =>
report.warning(s"The value of ${ctx.settings.XuncheckedJavaOutputVersion.name} was overridden by ${ctx.settings.javaOutputVersion.name}")
release
case (None, None) => "8" // least supported version by default


// Keep synchronized with `minTargetVersion` and `maxTargetVersion` in ScalaSettings
lazy val classfileVersion: Int = target match {
case "8" => asm.Opcodes.V1_8
case "9" => asm.Opcodes.V9
case "10" => asm.Opcodes.V10
case "11" => asm.Opcodes.V11
case "12" => asm.Opcodes.V12
case "13" => asm.Opcodes.V13
case "14" => asm.Opcodes.V14
case "15" => asm.Opcodes.V15
case "16" => asm.Opcodes.V16
case "17" => asm.Opcodes.V17
case "18" => asm.Opcodes.V18
case "19" => asm.Opcodes.V19
case "20" => asm.Opcodes.V20
}

lazy val majorVersion: Int = (classfileVersion & 0xFF)
lazy val emitStackMapFrame = (majorVersion >= 50)

val extraProc: Int =
import GenBCodeOps.addFlagIf
asm.ClassWriter.COMPUTE_MAXS
.addFlagIf(emitStackMapFrame, asm.ClassWriter.COMPUTE_FRAMES)

lazy val JavaStringBuilderClassName = jlStringBuilderRef.internalName

val CLASS_CONSTRUCTOR_NAME = "<clinit>"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
val flags = javaFlags(claszSymbol)

val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner)
cnode.visit(classfileVersion, flags,
cnode.visit(backendUtils.classfileVersion, flags,
thisName, thisSignature,
superClass, interfaceNames.toArray)

Expand Down
18 changes: 13 additions & 5 deletions compiler/src/dotty/tools/backend/jvm/BTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import scala.tools.asm
* This representation is immutable and independent of the compiler data structures, hence it can
* be queried by concurrent threads.
*/
abstract class BTypes {
abstract class BTypes { self =>
val frontendAccess: PostProcessorFrontendAccess
import frontendAccess.{frontendSynch}

val int: DottyBackendInterface
import int.given
Expand All @@ -37,10 +39,7 @@ abstract class BTypes {
*/
def classBTypeFromInternalName(internalName: String) = classBTypeFromInternalNameMap(internalName)

// Some core BTypes are required here, in class BType, where no Global instance is available.
// The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual
// implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol.
val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type]
val coreBTypes: CoreBTypes { val bTypes: self.type}
import coreBTypes._

/**
Expand Down Expand Up @@ -862,3 +861,12 @@ abstract class BTypes {
*/
/*final*/ case class MethodNameAndType(name: String, methodType: MethodBType)
}

object BTypes {
/**
* A marker for strings that represent class internal names.
* Ideally the type would be incompatible with String, for example by making it a value class.
* But that would create overhead in a Collection[InternalName].
*/
type InternalName = String
}
43 changes: 8 additions & 35 deletions compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,14 @@ import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.transform.SymUtils._
import dotty.tools.dotc.core.StdNames
import dotty.tools.dotc.core.Phases

/**
* This class mainly contains the method classBTypeFromSymbol, which extracts the necessary
* information from a symbol and its type to create the corresponding ClassBType. It requires
* access to the compiler (global parameter).
*
* The mixin CoreBTypes defines core BTypes that are used in the backend. Building these BTypes
* uses classBTypeFromSymbol, hence requires access to the compiler (global).
*
* BTypesFromSymbols extends BTypes because the implementation of BTypes requires access to some
* of the core btypes. They are declared in BTypes as abstract members. Note that BTypes does
* not have access to the compiler instance.
*/
class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
class BTypesFromSymbols[I <: DottyBackendInterface](val int: I, val frontendAccess: PostProcessorFrontendAccess) extends BTypes {
import int.{_, given}
import DottyBackendInterface.{symExtensions, _}

Expand All @@ -37,39 +31,18 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
val bCodeAsmCommon: BCodeAsmCommon[int.type ] = new BCodeAsmCommon(int)
import bCodeAsmCommon._

// Why the proxy, see documentation of class [[CoreBTypes]].
val coreBTypes: CoreBTypesProxy[this.type] = new CoreBTypesProxy[this.type](this)
import coreBTypes._

final def intializeCoreBTypes(): Unit = {
coreBTypes.setBTypes(new CoreBTypes[this.type](this))
}

private[this] val perRunCaches: Caches = new Caches {
def newAnyRefMap[K <: AnyRef, V](): mutable.AnyRefMap[K, V] = new mutable.AnyRefMap[K, V]()
def newWeakMap[K, V](): mutable.WeakHashMap[K, V] = new mutable.WeakHashMap[K, V]()
def recordCache[T <: Clearable](cache: T): T = cache
def newMap[K, V](): mutable.HashMap[K, V] = new mutable.HashMap[K, V]()
def newSet[K](): mutable.Set[K] = new mutable.HashSet[K]
}

// TODO remove abstraction
private abstract class Caches {
def recordCache[T <: Clearable](cache: T): T
def newWeakMap[K, V](): collection.mutable.WeakHashMap[K, V]
def newMap[K, V](): collection.mutable.HashMap[K, V]
def newSet[K](): collection.mutable.Set[K]
def newAnyRefMap[K <: AnyRef, V](): collection.mutable.AnyRefMap[K, V]
val coreBTypes = new CoreBTypesFromSymbols[I]{
val bTypes: BTypesFromSymbols.this.type = BTypesFromSymbols.this
}
import coreBTypes._

@threadUnsafe protected lazy val classBTypeFromInternalNameMap = {
perRunCaches.recordCache(collection.concurrent.TrieMap.empty[String, ClassBType])
}
@threadUnsafe protected lazy val classBTypeFromInternalNameMap =
collection.concurrent.TrieMap.empty[String, ClassBType]

/**
* Cache for the method classBTypeFromSymbol.
*/
@threadUnsafe private lazy val convertedClasses = perRunCaches.newMap[Symbol, ClassBType]()
@threadUnsafe private lazy val convertedClasses = collection.mutable.HashMap.empty[Symbol, ClassBType]

/**
* The ClassBType for a class symbol `sym`.
Expand Down
Loading