Skip to content

Generate generic java signatures #3234

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 13 commits into from
Oct 30, 2017
93 changes: 90 additions & 3 deletions compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import dotty.tools.dotc.ast.Trees
import dotty.tools.dotc
import dotty.tools.dotc.backend.jvm.DottyPrimitives
import dotty.tools.dotc.core.Flags.FlagSet
import dotty.tools.dotc.transform.Erasure
import dotty.tools.dotc.transform.{Erasure, GenericSignatures}
import dotty.tools.dotc.transform.SymUtils._
import java.io.{File => JFile}

Expand Down Expand Up @@ -489,10 +489,97 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma

def getSingleOutput: Option[AbstractFile] = None // todo: implement

// @M don't generate java generics sigs for (members of) implementation
// classes, as they are monomorphic (TODO: ok?)
private final def needsGenericSignature(sym: Symbol): Boolean = !(
// pp: this condition used to include sym.hasexpandedname, but this leads
// to the total loss of generic information if a private member is
// accessed from a closure: both the field and the accessor were generated
// without it. This is particularly bad because the availability of
// generic information could disappear as a consequence of a seemingly
// unrelated change.
ctx.base.settings.YnoGenericSig.value
|| sym.is(Flags.Artifact)
|| sym.is(Flags.allOf(Flags.Method, Flags.Lifted))
|| sym.is(Flags.Bridge)
)

private def verifySignature(sym: Symbol, sig: String)(implicit ctx: Context): Unit = {
import scala.tools.asm.util.CheckClassAdapter
def wrap(body: => Unit): Boolean =
try { body; true }
catch { case ex: Throwable => println(ex.getMessage); false }

val valid = wrap {
if (sym.is(Flags.Method)) {
CheckClassAdapter.checkMethodSignature(sig)
}
else if (sym.isTerm) {
CheckClassAdapter.checkFieldSignature(sig)
}
else {
CheckClassAdapter.checkClassSignature(sig)
}
}

if(!valid) {
ctx.warning(
i"""|compiler bug: created invalid generic signature for $sym in ${sym.denot.owner.showFullName}
|signature: $sig
|if this is reproducible, please report bug at https://github.com/lampepfl/dotty/issues
""".trim, sym.pos)
}
}

/**
* Generates the generic signature for `sym` before erasure.
*
* @param sym The symbol for which to generate a signature.
* @param owner The owner of `sym`.
* @return The generic signature of `sym` before erasure, as specified in the Java Virtual
* Machine Specification, §4.3.4, or `null` if `sym` doesn't need a generic signature.
* @see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4
*/
def getGenericSignature(sym: Symbol, owner: Symbol): String = {
ctx.atPhase(ctx.erasurePhase) { implicit ctx =>
val memberTpe =
if (sym.is(Flags.Method)) sym.denot.info
else owner.denot.thisType.memberInfo(sym)
getGenericSignature(sym, owner, memberTpe).orNull
}
}

def getGenericSignature(sym: Symbol, owner: Symbol): String = null // todo: implement
def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = {
// scala/bug#3452 Static forwarder generation uses the same erased signature as the method if forwards to.
// By rights, it should use the signature as-seen-from the module class, and add suitable
// primitive and value-class boxing/unboxing.
// But for now, just like we did in mixin, we just avoid writing a wrong generic signature
// (one that doesn't erase to the actual signature). See run/t3452b for a test case.

val memberTpe = ctx.atPhase(ctx.erasurePhase) { implicit ctx => moduleClass.denot.thisType.memberInfo(sym) }
val erasedMemberType = TypeErasure.erasure(memberTpe)
if (erasedMemberType =:= sym.denot.info)
getGenericSignature(sym, moduleClass, memberTpe).orNull
else null
}

private def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type)(implicit ctx: Context): Option[String] =
if (needsGenericSignature(sym)) {
val erasedTypeSym = sym.denot.info.typeSymbol
if (erasedTypeSym.isPrimitiveValueClass) {
None
} else {
val jsOpt = GenericSignatures.javaSig(sym, memberTpe)
if (ctx.settings.XverifySignatures.value) {
jsOpt.foreach(verifySignature(sym, _))
}

jsOpt
}
} else {
None
}

def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = null // todo: implement


def sourceFileFor(cu: CompilationUnit): String = cu.source.file.name
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class ScalaSettings extends Settings.SettingGroup {
val XnoValueClasses = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.")
val XreplLineWidth = IntSetting("-Xrepl-line-width", "Maximial number of columns per line for REPL output", 390)
val XfatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.")
val XverifySignatures = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.")

/** -Y "Private" settings */
val overrideVars = BooleanSetting("-Yoverride-vars", "Allow vars to be overridden.")
Expand All @@ -79,6 +80,7 @@ class ScalaSettings extends Settings.SettingGroup {
val YdisableFlatCpCaching = BooleanSetting("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")

val YnoImports = BooleanSetting("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.")
val YnoGenericSig = BooleanSetting("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.")
val YnoPredef = BooleanSetting("-Yno-predef", "Compile without importing Predef.")
val Yskip = PhasesSetting("-Yskip", "Skip")
val Ydumpclasses = StringSetting("-Ydump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "")
Expand Down
27 changes: 17 additions & 10 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,11 @@ class Definitions {
lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef))
def NothingType = NothingClass.typeRef
lazy val RuntimeNothingModuleRef = ctx.requiredModuleRef("scala.runtime.Nothing")
lazy val NullClass: ClassSymbol = enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Null, AbstractFinal, List(ObjectClass.typeRef))
def NullType = NullClass.typeRef
lazy val RuntimeNullModuleRef = ctx.requiredModuleRef("scala.runtime.Null")

lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef")
def ScalaPredefModule(implicit ctx: Context) = ScalaPredefModuleRef.symbol
Expand Down Expand Up @@ -392,9 +394,9 @@ class Definitions {
def ArrayModule(implicit ctx: Context) = ArrayModuleType.symbol.moduleClass.asClass


lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", BoxedUnitType, java.lang.Void.TYPE, UnitEnc)
lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", BoxedUnitType, java.lang.Void.TYPE, UnitEnc, nme.specializedTypeNames.Void)
def UnitClass(implicit ctx: Context) = UnitType.symbol.asClass
lazy val BooleanType = valueTypeRef("scala.Boolean", BoxedBooleanType, java.lang.Boolean.TYPE, BooleanEnc)
lazy val BooleanType = valueTypeRef("scala.Boolean", BoxedBooleanType, java.lang.Boolean.TYPE, BooleanEnc, nme.specializedTypeNames.Boolean)
def BooleanClass(implicit ctx: Context) = BooleanType.symbol.asClass
lazy val Boolean_notR = BooleanClass.requiredMethodRef(nme.UNARY_!)
def Boolean_! = Boolean_notR.symbol
Expand All @@ -413,13 +415,13 @@ class Definitions {
})
def Boolean_!= = Boolean_neqeqR.symbol

lazy val ByteType: TypeRef = valueTypeRef("scala.Byte", BoxedByteType, java.lang.Byte.TYPE, ByteEnc)
lazy val ByteType: TypeRef = valueTypeRef("scala.Byte", BoxedByteType, java.lang.Byte.TYPE, ByteEnc, nme.specializedTypeNames.Byte)
def ByteClass(implicit ctx: Context) = ByteType.symbol.asClass
lazy val ShortType: TypeRef = valueTypeRef("scala.Short", BoxedShortType, java.lang.Short.TYPE, ShortEnc)
lazy val ShortType: TypeRef = valueTypeRef("scala.Short", BoxedShortType, java.lang.Short.TYPE, ShortEnc, nme.specializedTypeNames.Short)
def ShortClass(implicit ctx: Context) = ShortType.symbol.asClass
lazy val CharType: TypeRef = valueTypeRef("scala.Char", BoxedCharType, java.lang.Character.TYPE, CharEnc)
lazy val CharType: TypeRef = valueTypeRef("scala.Char", BoxedCharType, java.lang.Character.TYPE, CharEnc, nme.specializedTypeNames.Char)
def CharClass(implicit ctx: Context) = CharType.symbol.asClass
lazy val IntType: TypeRef = valueTypeRef("scala.Int", BoxedIntType, java.lang.Integer.TYPE, IntEnc)
lazy val IntType: TypeRef = valueTypeRef("scala.Int", BoxedIntType, java.lang.Integer.TYPE, IntEnc, nme.specializedTypeNames.Int)
def IntClass(implicit ctx: Context) = IntType.symbol.asClass
lazy val Int_minusR = IntClass.requiredMethodRef(nme.MINUS, List(IntType))
def Int_- = Int_minusR.symbol
Expand All @@ -435,7 +437,7 @@ class Definitions {
def Int_>= = Int_geR.symbol
lazy val Int_leR = IntClass.requiredMethodRef(nme.LE, List(IntType))
def Int_<= = Int_leR.symbol
lazy val LongType: TypeRef = valueTypeRef("scala.Long", BoxedLongType, java.lang.Long.TYPE, LongEnc)
lazy val LongType: TypeRef = valueTypeRef("scala.Long", BoxedLongType, java.lang.Long.TYPE, LongEnc, nme.specializedTypeNames.Long)
def LongClass(implicit ctx: Context) = LongType.symbol.asClass
lazy val Long_XOR_Long = LongType.member(nme.XOR).requiredSymbol(
x => (x is Method) && (x.info.firstParamTypes.head isRef defn.LongClass)
Expand All @@ -450,9 +452,9 @@ class Definitions {
lazy val Long_divR = LongClass.requiredMethodRef(nme.DIV, List(LongType))
def Long_/ = Long_divR.symbol

lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc)
lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc, nme.specializedTypeNames.Float)
def FloatClass(implicit ctx: Context) = FloatType.symbol.asClass
lazy val DoubleType: TypeRef = valueTypeRef("scala.Double", BoxedDoubleType, java.lang.Double.TYPE, DoubleEnc)
lazy val DoubleType: TypeRef = valueTypeRef("scala.Double", BoxedDoubleType, java.lang.Double.TYPE, DoubleEnc, nme.specializedTypeNames.Double)
def DoubleClass(implicit ctx: Context) = DoubleType.symbol.asClass

lazy val BoxedUnitType: TypeRef = ctx.requiredClassRef("scala.runtime.BoxedUnit")
Expand Down Expand Up @@ -974,15 +976,17 @@ class Definitions {

private val boxedTypes = mutable.Map[TypeName, TypeRef]()
private val valueTypeEnc = mutable.Map[TypeName, PrimitiveClassEnc]()
private val typeTags = mutable.Map[TypeName, Name]().withDefaultValue(nme.specializedTypeNames.Object)

// private val unboxedTypeRef = mutable.Map[TypeName, TypeRef]()
// private val javaTypeToValueTypeRef = mutable.Map[Class[_], TypeRef]()
// private val valueTypeNameToJavaType = mutable.Map[TypeName, Class[_]]()

private def valueTypeRef(name: String, boxed: TypeRef, jtype: Class[_], enc: Int): TypeRef = {
private def valueTypeRef(name: String, boxed: TypeRef, jtype: Class[_], enc: Int, tag: Name): TypeRef = {
val vcls = ctx.requiredClassRef(name)
boxedTypes(vcls.name) = boxed
valueTypeEnc(vcls.name) = enc
typeTags(vcls.name) = tag
// unboxedTypeRef(boxed.name) = vcls
// javaTypeToValueTypeRef(jtype) = vcls
// valueTypeNameToJavaType(vcls.name) = jtype
Expand All @@ -992,6 +996,9 @@ class Definitions {
/** The type of the boxed class corresponding to primitive value type `tp`. */
def boxedType(tp: Type)(implicit ctx: Context): TypeRef = boxedTypes(scalaClassName(tp))

/** The JVM tag for `tp` if it's a primitive, `java.lang.Object` otherwise. */
def typeTag(tp: Type)(implicit ctx: Context): Name = typeTags(scalaClassName(tp))

type PrimitiveClassEnc = Int

val ByteEnc = 2
Expand Down
19 changes: 2 additions & 17 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -229,23 +229,8 @@ object NameOps {

def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = {

def typeToTag(tp: Types.Type): Name = {
tp.classSymbol match {
case t if t eq defn.IntClass => nme.specializedTypeNames.Int
case t if t eq defn.BooleanClass => nme.specializedTypeNames.Boolean
case t if t eq defn.ByteClass => nme.specializedTypeNames.Byte
case t if t eq defn.LongClass => nme.specializedTypeNames.Long
case t if t eq defn.ShortClass => nme.specializedTypeNames.Short
case t if t eq defn.FloatClass => nme.specializedTypeNames.Float
case t if t eq defn.UnitClass => nme.specializedTypeNames.Void
case t if t eq defn.DoubleClass => nme.specializedTypeNames.Double
case t if t eq defn.CharClass => nme.specializedTypeNames.Char
case _ => nme.specializedTypeNames.Object
}
}

val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => typeToTag(x._1))
val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => typeToTag(x._1))
val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => defn.typeTag(x._1))
val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => defn.typeTag(x._1))

name.likeSpaced(name ++ nme.specializedTypeNames.prefix ++
methodTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.separator ++
Expand Down
Loading