summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2016-06-06 14:24:38 +1000
committerAdriaan Moors <adriaan.moors@typesafe.com>2016-06-28 09:18:34 -0700
commit7d51b3fd1569917cb804363bd418466a306f5c89 (patch)
treeede84e6a0dda8750276d7c0986ffc6d15c9fb1dc /src/compiler/scala/tools
parent91b066aac5edf53ca18603f8486eb255514b3118 (diff)
downloadscala-7d51b3fd1569917cb804363bd418466a306f5c89.tar.gz
scala-7d51b3fd1569917cb804363bd418466a306f5c89.tar.bz2
scala-7d51b3fd1569917cb804363bd418466a306f5c89.zip
Emit trait method bodies in statics
And use this as the target of the default methods or statically resolved super or $init calls. The call-site change is predicated on `-Yuse-trait-statics` as a stepping stone for experimentation / bootstrapping. I have performed this transformation in the backend, rather than trying to reflect this in the view from Scala symbols + ASTs. We also need to add an restriction related to invokespecial to Java parents: to support a super call to one of these to implement a super accessor, the interface must be listed as a direct parent of the class. The static method names has a trailing $ added to avoid duplicate name and signature errors in classfiles.
Diffstat (limited to 'src/compiler/scala/tools')
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeGen.scala3
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala49
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala71
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala22
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala17
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala22
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala80
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala14
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala9
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala7
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala13
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala28
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala28
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala28
15 files changed, 235 insertions, 162 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
index 14ee7d7a78..bc89609a59 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
@@ -336,12 +336,13 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
* - are associating the RHS with a cloned symbol, but intend for the original
* method to remain and for recursive calls to target it.
*/
- final def mkStatic(orig: DefDef, maybeClone: Symbol => Symbol): DefDef = {
+ final def mkStatic(orig: DefDef, newName: Name, maybeClone: Symbol => Symbol): DefDef = {
assert(phase.erasedTypes, phase)
assert(!orig.symbol.hasFlag(SYNCHRONIZED), orig.symbol.defString)
val origSym = orig.symbol
val origParams = orig.symbol.info.params
val newSym = maybeClone(orig.symbol)
+ newSym.setName(newName)
newSym.setFlag(STATIC)
// Add an explicit self parameter
val selfParamSym = newSym.newSyntheticValueParam(newSym.owner.typeConstructor, nme.SELF).setFlag(ARTIFACT)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index d7106ae908..55fe47bde6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -11,10 +11,10 @@ package jvm
import scala.annotation.switch
import scala.reflect.internal.Flags
-
import scala.tools.asm
import GenBCode._
import BackendReporting._
+import scala.tools.asm.Opcodes
import scala.tools.asm.tree.MethodInsnNode
import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp}
@@ -637,7 +637,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val nativeKind = tpeTK(expr)
genLoad(expr, nativeKind)
val MethodNameAndType(mname, methodType) = srBoxesRuntimeBoxToMethods(nativeKind)
- bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos)
+ bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
generatedType = boxResultType(fun.symbol)
case Apply(fun, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) =>
@@ -645,7 +645,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val boxType = unboxResultType(fun.symbol)
generatedType = boxType
val MethodNameAndType(mname, methodType) = srBoxesRuntimeUnboxToMethods(boxType)
- bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos)
+ bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
case app @ Apply(fun, args) =>
val sym = fun.symbol
@@ -1058,31 +1058,40 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits
- val receiverName = internalName(receiverClass)
-
- // super calls are only allowed to direct parents
- if (style.isSuper && receiverClass.isTraitOrInterface && !cnode.interfaces.contains(receiverName)) {
- thisBType.info.get.inlineInfo.lateInterfaces += receiverName
- cnode.interfaces.add(receiverName)
- }
+ val receiverBType = classBTypeFromSymbol(receiverClass)
+ val receiverName = receiverBType.internalName
def needsInterfaceCall(sym: Symbol) = {
sym.isTraitOrInterface ||
sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass)
}
- val jname = method.javaSimpleName.toString
- val bmType = methodBTypeFromSymbol(method)
- val mdescr = bmType.descriptor
+ val jname = method.javaSimpleName.toString
+ val bmType = methodBTypeFromSymbol(method)
+ val mdescr = bmType.descriptor
+ val isInterface = receiverBType.isInterface.get
import InvokeStyle._
- style match {
- case Static => bc.invokestatic (receiverName, jname, mdescr, pos)
- case Special => bc.invokespecial (receiverName, jname, mdescr, pos)
- case Virtual =>
- if (needsInterfaceCall(receiverClass)) bc.invokeinterface(receiverName, jname, mdescr, pos)
- else bc.invokevirtual (receiverName, jname, mdescr, pos)
- case Super => bc.invokespecial (receiverName, jname, mdescr, pos)
+ if (style == Super) {
+ assert(receiverClass == methodOwner, s"for super call, expecting $receiverClass == $methodOwner")
+ if (receiverClass.isTrait && !receiverClass.isJavaDefined) {
+ val staticDesc = MethodBType(typeToBType(method.owner.info) :: bmType.argumentTypes, bmType.returnType).descriptor
+ val staticName = traitImplMethodName(method).toString
+ bc.invokestatic(receiverName, staticName, staticDesc, isInterface, pos)
+ } else {
+ if (receiverClass.isTraitOrInterface) {
+ // An earlier check in Mixin reports an error in this case, so it doesn't reach the backend
+ assert(cnode.interfaces.contains(receiverName), s"cannot invokespecial $receiverName.$jname, the interface is not a direct parent.")
+ }
+ bc.invokespecial(receiverName, jname, mdescr, isInterface, pos)
+ }
+ } else {
+ val opc = style match {
+ case Static => Opcodes.INVOKESTATIC
+ case Special => Opcodes.INVOKESPECIAL
+ case Virtual => if (isInterface) Opcodes.INVOKEINTERFACE else Opcodes.INVOKEVIRTUAL
+ }
+ bc.emitInvoke(opc, receiverName, jname, mdescr, isInterface, pos)
}
bmType.returnType
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
index 5a5747c81f..df3c2cb3d5 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
@@ -11,6 +11,7 @@ import scala.tools.asm
import scala.tools.nsc.io.AbstractFile
import GenBCode._
import BackendReporting._
+import scala.reflect.internal.Flags
/*
* Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
@@ -49,6 +50,14 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
}
}
+ def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type]
+
+ final def traitImplMethodName(sym: Symbol): Name = {
+ val name = sym.javaSimpleName
+ if (sym.isMixinConstructor) name
+ else name.append(nme.NAME_JOIN_STRING)
+ }
+
/**
* True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
* member class. This method is used to decide if we should emit an EnclosingMethod attribute.
@@ -230,58 +239,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
sym.isErroneous
}
- /**
- * Build the [[InlineInfo]] for a class symbol.
- */
- def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = {
- val isEffectivelyFinal = classSym.isEffectivelyFinal
-
- val sam = {
- if (classSym.isEffectivelyFinal) None
- else {
- // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an
- // empty parameter list in later phases and would therefore be picked as SAM.
- val samSym = exitingPickler(definitions.samOf(classSym.tpe))
- if (samSym == NoSymbol) None
- else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym))
- }
- }
-
- var warning = Option.empty[ClassSymbolInfoFailureSI9111]
-
- // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
- // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
- val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
- case methodSym =>
- if (completeSilentlyAndCheckErroneous(methodSym)) {
- // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
- if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
- warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
- None
- } else {
- val name = methodSym.javaSimpleName.toString // same as in genDefDef
- val signature = name + methodSymToDescriptor(methodSym)
-
- // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the
- // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded
- // so they are not marked final in the InlineInfo attribute.
- //
- // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not
- // work, the abstract accessor for O will be marked effectivelyFinal.
- val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred
-
- val info = MethodInlineInfo(
- effectivelyFinal = effectivelyFinal,
- annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
- annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass)
- )
- Some((signature, info))
- }
- }).toMap
-
- InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning)
- }
-
/*
* must-single-thread
*/
@@ -568,15 +525,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
/**
* The class internal name for a given class symbol.
*/
- final def internalName(sym: Symbol): String = {
- // For each java class, the scala compiler creates a class and a module (thus a module class).
- // If the `sym` is a java module class, we use the java class instead. This ensures that the
- // ClassBType is created from the main class (instead of the module class).
- // The two symbols have the same name, so the resulting internalName is the same.
- // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting)
- val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
- classBTypeFromSymbol(classSym).internalName
- }
+ final def internalName(sym: Symbol): String = classBTypeFromSymbol(sym).internalName
} // end of trait BCInnerClassGen
trait BCAnnotGen extends BCInnerClassGen {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
index ed1b4ec325..e3d45a9b3e 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
@@ -190,6 +190,7 @@ abstract class BCodeIdiomatic extends SubComponent {
JavaStringBuilderClassName,
INSTANCE_CONSTRUCTOR_NAME,
"()V",
+ itf = false,
pos
)
}
@@ -373,30 +374,27 @@ abstract class BCodeIdiomatic extends SubComponent {
final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread
// can-multi-thread
- final def invokespecial(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos)
+ final def invokespecial(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, itf, pos)
}
// can-multi-thread
- final def invokestatic(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos)
+ final def invokestatic(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKESTATIC, owner, name, desc, itf, pos)
}
// can-multi-thread
- final def invokeinterface(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos)
+ final def invokeinterface(owner: String, name: String, desc: String, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, itf = true, pos)
}
// can-multi-thread
- final def invokevirtual(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos)
+ final def invokevirtual(owner: String, name: String, desc: String, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf = false, pos)
}
- private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = {
+ def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
val node = new MethodInsnNode(opcode, owner, name, desc, itf)
jmethod.instructions.add(node)
if (settings.optInlinerEnabled) callsitePositions(node) = pos
}
- final def invokedynamic(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc)
- }
// can-multi-thread
final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
index bddc41e5c6..1bff8519ec 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -488,7 +488,22 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
case ValDef(mods, name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()`
- case dd : DefDef => genDefDef(dd)
+ case dd : DefDef =>
+ val sym = dd.symbol
+ if (needsStaticImplMethod(sym)) {
+ val staticDefDef = global.gen.mkStatic(dd, traitImplMethodName(sym), _.cloneSymbol)
+ val forwarderDefDef = {
+ val forwarderBody = Apply(global.gen.mkAttributedRef(staticDefDef.symbol), This(sym.owner).setType(sym.owner.typeConstructor) :: dd.vparamss.head.map(p => global.gen.mkAttributedIdent(p.symbol))).setType(sym.info.resultType)
+ // we don't want to the optimizer to inline the static method into the forwarder. Instead,
+ // the backend has a special case to transitively inline into a callsite of the forwarder
+ // when the forwarder itself is inlined.
+ forwarderBody.updateAttachment(NoInlineCallsiteAttachment)
+ deriveDefDef(dd)(_ => global.atPos(dd.pos)(forwarderBody))
+ }
+ genDefDef(staticDefDef)
+ if (!sym.isMixinConstructor)
+ genDefDef(forwarderDefDef)
+ } else genDefDef(dd)
case Template(_, _, body) => body foreach gen
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index a708feb0a7..7b2686e7a9 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -225,8 +225,7 @@ abstract class BTypes {
val inlineInfo = inlineInfoFromClassfile(classNode)
- val classfileInterfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
- val interfaces = classfileInterfaces.filterNot(i => inlineInfo.lateInterfaces.contains(i.internalName))
+ val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
classBType
@@ -1147,25 +1146,6 @@ object BTypes {
sam: Option[String],
methodInfos: Map[String, MethodInlineInfo],
warning: Option[ClassInlineInfoWarning]) {
- /**
- * A super call (invokespecial) to a default method T.m is only allowed if the interface T is
- * a direct parent of the class. Super calls are introduced for example in Mixin when generating
- * forwarder methods:
- *
- * trait T { override def clone(): Object = "hi" }
- * trait U extends T
- * class C extends U
- *
- * The class C gets a forwarder that invokes T.clone(). During code generation the interface T
- * is added as direct parent to class C. Note that T is not a (direct) parent in the frontend
- * type of class C.
- *
- * All interfaces that are added to a class during code generation are added to this buffer and
- * stored in the InlineInfo classfile attribute. This ensures that the ClassBTypes for a
- * specific class is the same no matter if it's constructed from a Symbol or from a classfile.
- * This is tested in BTypesFromClassfileTest.
- */
- val lateInterfaces: ListBuffer[InternalName] = ListBuffer.empty
}
val EmptyInlineInfo = InlineInfo(false, None, Map.empty, None)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index 21ea351a99..1a4590e7d1 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -97,11 +97,19 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* for the Nothing / Null. If used for example as a parameter type, we use the runtime classes
* in the classfile method signature.
*/
- final def classBTypeFromSymbol(classSym: Symbol): ClassBType = {
+ final def classBTypeFromSymbol(sym: Symbol): ClassBType = {
+ // For each java class, the scala compiler creates a class and a module (thus a module class).
+ // If the `sym` is a java module class, we use the java class instead. This ensures that the
+ // ClassBType is created from the main class (instead of the module class).
+ // The two symbols have the same name, so the resulting internalName is the same.
+ // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting)
+ val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
+
assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol")
assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym")
assertClassNotArrayNotPrimitive(classSym)
assert(!primitiveTypeToBType.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym")
+
if (classSym == NothingClass) srNothingRef
else if (classSym == NullClass) srNullRef
else {
@@ -509,7 +517,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* classfile attribute.
*/
private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = {
- def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor)
+ def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym)
// phase travel required, see implementation of `compiles`. for nested classes, it checks if the
// enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level,
@@ -531,6 +539,74 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
/**
+ * Build the [[InlineInfo]] for a class symbol.
+ */
+ def buildInlineInfoFromClassSymbol(classSym: Symbol): InlineInfo = {
+ val isEffectivelyFinal = classSym.isEffectivelyFinal
+
+ val sam = {
+ if (classSym.isEffectivelyFinal) None
+ else {
+ // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an
+ // empty parameter list in later phases and would therefore be picked as SAM.
+ val samSym = exitingPickler(definitions.samOf(classSym.tpe))
+ if (samSym == NoSymbol) None
+ else Some(samSym.javaSimpleName.toString + methodBTypeFromSymbol(samSym).descriptor)
+ }
+ }
+
+ var warning = Option.empty[ClassSymbolInfoFailureSI9111]
+
+ // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
+ // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
+ val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
+ case methodSym =>
+ if (completeSilentlyAndCheckErroneous(methodSym)) {
+ // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
+ if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
+ warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
+ Nil
+ } else {
+ val name = methodSym.javaSimpleName.toString // same as in genDefDef
+ val signature = name + methodBTypeFromSymbol(methodSym).descriptor
+
+ // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the
+ // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded
+ // so they are not marked final in the InlineInfo attribute.
+ //
+ // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not
+ // work, the abstract accessor for O will be marked effectivelyFinal.
+ val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred
+
+ val info = MethodInlineInfo(
+ effectivelyFinal = effectivelyFinal,
+ annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
+ annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass))
+
+ if (needsStaticImplMethod(methodSym)) {
+ val staticName = traitImplMethodName(methodSym).toString
+ val selfParam = methodSym.newSyntheticValueParam(methodSym.owner.typeConstructor, nme.SELF)
+ val staticMethodType = methodSym.info match {
+ case mt @ MethodType(params, res) => copyMethodType(mt, selfParam :: params, res)
+ }
+ val staticMethodSignature = staticName + methodBTypeFromMethodType(staticMethodType, isConstructor = false)
+ val staticMethodInfo = MethodInlineInfo(
+ effectivelyFinal = true,
+ annotatedInline = info.annotatedInline,
+ annotatedNoInline = info.annotatedNoInline)
+ if (methodSym.isMixinConstructor)
+ List((staticMethodSignature, staticMethodInfo))
+ else
+ List((signature, info), (staticMethodSignature, staticMethodInfo))
+ } else
+ List((signature, info))
+ }
+ }).toMap
+
+ InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning)
+ }
+
+ /**
* For top-level objects without a companion class, the compiler generates a mirror class with
* static forwarders (Java compat). There's no symbol for the mirror class, but we still need a
* ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes).
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
index 513c71fe2e..539435a326 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
@@ -2,18 +2,18 @@ package scala.tools.nsc
package backend.jvm
package analysis
+import java.lang.invoke.LambdaMetafactory
+
import scala.annotation.switch
-import scala.tools.asm.{Handle, Type}
+import scala.collection.JavaConverters._
+import scala.collection.mutable
import scala.tools.asm.Opcodes._
import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis._
-import GenBCode._
+import scala.tools.asm.{Handle, Type}
import scala.tools.nsc.backend.jvm.BTypes._
+import scala.tools.nsc.backend.jvm.GenBCode._
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
-import java.lang.invoke.LambdaMetafactory
-
-import scala.collection.mutable
-import scala.collection.JavaConverters._
/**
* This component hosts tools and utilities used in the backend that require access to a `BTypes`
@@ -39,7 +39,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
case ae: AnalyzerException =>
throw new AnalyzerException(null, "While processing " + classInternalName + "." + methodNode.name, ae)
}
- def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode)
+ def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode)
}
/**
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
index 63906d80e5..e21c46dbe9 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -93,6 +93,15 @@ object BytecodeUtils {
op == INVOKESPECIAL || op == INVOKESTATIC
}
+ def isVirtualCall(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ op == INVOKEVIRTUAL || op == INVOKEINTERFACE
+ }
+
+ def isCall(instruction: AbstractInsnNode): Boolean = {
+ isNonVirtualCall(instruction) || isVirtualCall(instruction)
+ }
+
def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
def isConstructor(methodNode: MethodNode): Boolean = {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
index 40344809bf..d6942d9ff9 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -9,11 +9,11 @@ package opt
import scala.collection.immutable.IntMap
import scala.reflect.internal.util.{NoPosition, Position}
-import scala.tools.asm.{Opcodes, Type, Handle}
+import scala.tools.asm.{Handle, Opcodes, Type}
import scala.tools.asm.tree._
import scala.collection.{concurrent, mutable}
import scala.collection.JavaConverters._
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo}
import scala.tools.nsc.backend.jvm.BackendReporting._
import scala.tools.nsc.backend.jvm.analysis._
import BytecodeUtils._
@@ -67,6 +67,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
}
def containsCallsite(callsite: Callsite): Boolean = callsites(callsite.callsiteMethod) contains callsite.callsiteInstruction
+ def findCallSite(method: MethodNode, call: MethodInsnNode): Option[Callsite] = callsites.getOrElse(method, Map.empty).get(call)
def removeClosureInstantiation(indy: InvokeDynamicInsnNode, methodNode: MethodNode): Option[ClosureInstantiation] = {
val methodClosureInits = closureInstantiations(methodNode)
@@ -356,7 +357,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
"Invocation of" +
s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" +
s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" +
- s" in ${callsiteClass.internalName}.${callsiteMethod.name}"
+ s" in ${callsiteClass.internalName}.${callsiteMethod.name}${callsiteMethod.desc}"
}
final case class ClonedCallsite(callsite: Callsite, clonedWhenInlining: Callsite)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
index 79d26b0b4e..5ce7072c60 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
@@ -51,7 +51,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
if (inlineInfo.isEffectivelyFinal) flags |= 1
// flags |= 2 // no longer written
if (inlineInfo.sam.isDefined) flags |= 4
- if (inlineInfo.lateInterfaces.nonEmpty) flags |= 8
result.putByte(flags)
for (samNameDesc <- inlineInfo.sam) {
@@ -79,9 +78,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
result.putByte(inlineInfo)
}
- result.putShort(inlineInfo.lateInterfaces.length)
- for (i <- inlineInfo.lateInterfaces) result.putShort(cw.newUTF8(i))
-
result
}
@@ -105,7 +101,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
val isFinal = (flags & 1) != 0
val hasSelf = (flags & 2) != 0
val hasSam = (flags & 4) != 0
- val hasLateInterfaces = (flags & 8) != 0
if (hasSelf) nextUTF8() // no longer used
@@ -128,13 +123,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
(name + desc, MethodInlineInfo(isFinal, isInline, isNoInline))
}).toMap
- val lateInterfaces = if (!hasLateInterfaces) Nil else {
- val numLateInterfaces = nextShort()
- (0 until numLateInterfaces).map(_ => nextUTF8())
- }
-
val info = InlineInfo(isFinal, sam, infos, None)
- info.lateInterfaces ++= lateInterfaces
InlineInfoAttribute(info)
} else {
val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version)
@@ -161,8 +150,6 @@ object InlineInfoAttribute {
* [u2] name (reference)
* [u2] descriptor (reference)
* [u1] isFinal (<< 0), traitMethodWithStaticImplementation (<< 1), hasInlineAnnotation (<< 2), hasNoInlineAnnotation (<< 3)
- * [u2]? numLateInterfaces
- * [u2] lateInterface (reference)
*/
final val VERSION: Byte = 1
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
index 7b4cfe2a18..9c5a1a9f98 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -106,6 +106,8 @@ class Inliner[BT <: BTypes](val btypes: BT) {
val elided = mutable.Set.empty[InlineRequest]
def nonElidedRequests(methodNode: MethodNode): Set[InlineRequest] = requestsByMethod(methodNode) diff elided
+ def allCallees(r: InlineRequest): Set[MethodNode] = r.post.flatMap(allCallees).toSet + r.callsite.callee.get.callee
+
/**
* Break cycles in the inline request graph by removing callsites.
*
@@ -114,20 +116,20 @@ class Inliner[BT <: BTypes](val btypes: BT) {
*/
def breakInlineCycles: List[InlineRequest] = {
// is there a path of inline requests from start to goal?
- def isReachable(start: MethodNode, goal: MethodNode): Boolean = {
- @tailrec def reachableImpl(check: List[MethodNode], visited: Set[MethodNode]): Boolean = check match {
- case x :: xs =>
+ def isReachable(start: Set[MethodNode], goal: MethodNode): Boolean = {
+ @tailrec def reachableImpl(check: Set[MethodNode], visited: Set[MethodNode]): Boolean = {
+ if (check.isEmpty) false
+ else {
+ val x = check.head
if (x == goal) true
- else if (visited(x)) reachableImpl(xs, visited)
+ else if (visited(x)) reachableImpl(check - x, visited)
else {
- val callees = nonElidedRequests(x).map(_.callsite.callee.get.callee)
- reachableImpl(xs ::: callees.toList, visited + x)
+ val callees = nonElidedRequests(x).flatMap(allCallees)
+ reachableImpl(check - x ++ callees, visited + x)
}
-
- case Nil =>
- false
+ }
}
- reachableImpl(List(start), Set.empty)
+ reachableImpl(start, Set.empty)
}
val result = new mutable.ListBuffer[InlineRequest]()
@@ -136,7 +138,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
java.util.Arrays.sort(requests, callsiteOrdering)
for (r <- requests) {
// is there a chain of inlining requests that would inline the callsite method into the callee?
- if (isReachable(r.callsite.callee.get.callee, r.callsite.callsiteMethod))
+ if (isReachable(allCallees(r), r.callsite.callsiteMethod))
elided += r
else
result += r
@@ -150,8 +152,8 @@ class Inliner[BT <: BTypes](val btypes: BT) {
if (requests.isEmpty) Nil
else {
val (leaves, others) = requests.partition(r => {
- val inlineRequestsForCallee = nonElidedRequests(r.callsite.callee.get.callee)
- inlineRequestsForCallee.forall(visited)
+ val inlineRequestsForCallees = allCallees(r).flatMap(nonElidedRequests)
+ inlineRequestsForCallees.forall(visited)
})
assert(leaves.nonEmpty, requests)
leaves ::: leavesFirst(others, visited ++ leaves)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
index 009742501e..79e74f3eb7 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
@@ -7,14 +7,14 @@ package scala.tools.nsc
package backend.jvm
package opt
-import scala.tools.asm.tree.MethodNode
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.collection.JavaConverters._
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.{MethodInsnNode, MethodNode}
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.tools.nsc.backend.jvm.BackendReporting.OptimizerWarning
class InlinerHeuristics[BT <: BTypes](val bTypes: BT) {
import bTypes._
- import inliner._
import callGraph._
case class InlineRequest(callsite: Callsite, post: List[InlineRequest], reason: String) {
@@ -93,7 +93,27 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) {
val callee = callsite.callee.get
def requestIfCanInline(callsite: Callsite, reason: String): Either[OptimizerWarning, InlineRequest] = inliner.earlyCanInlineCheck(callsite) match {
case Some(w) => Left(w)
- case None => Right(InlineRequest(callsite, Nil, reason))
+ case None =>
+ val callee = callsite.callee.get
+ val postInlineRequest: List[InlineRequest] = callee.calleeDeclarationClass.isInterface match {
+ case Right(true) =>
+ // Treat the pair of trait interface method and static method as one for the purposes of inlining:
+ // if we inline invokeinterface, invoke the invokestatic, too.
+ val calls = callee.callee.instructions.iterator().asScala.filter(BytecodeUtils.isCall).take(2).toList
+ calls match {
+ case List(x: MethodInsnNode) if x.getOpcode == Opcodes.INVOKESTATIC && x.name == (callee.callee.name + "$") =>
+ callGraph.addIfMissing(callee.callee, callee.calleeDeclarationClass)
+ val maybeNodeToCallsite1 = callGraph.findCallSite(callee.callee, x)
+ maybeNodeToCallsite1.toList.flatMap(x => requestIfCanInline(x, reason).right.toOption)
+ case _ =>
+ Nil
+
+ }
+ case _ => Nil
+ }
+
+ Right(InlineRequest(callsite, postInlineRequest, reason))
+
}
compilerSettings.YoptInlineHeuristics.value match {
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 2dd8def53e..804bcddb7b 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -261,7 +261,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
def pretransform(tree: Tree): Tree = tree match {
case dd: DefDef if dd.symbol.isDelambdafyTarget =>
if (!dd.symbol.hasFlag(STATIC) && methodReferencesThis(dd.symbol)) {
- gen.mkStatic(dd, sym => sym)
+ gen.mkStatic(dd, dd.symbol.name, sym => sym)
} else {
dd.symbol.setFlag(STATIC)
dd
@@ -276,8 +276,10 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
case dd: DefDef if dd.symbol.isLiftedMethod && !dd.symbol.isDelambdafyTarget =>
// SI-9390 emit lifted methods that don't require a `this` reference as STATIC
// delambdafy targets are excluded as they are made static by `transformFunction`.
- if (!dd.symbol.hasFlag(STATIC) && !methodReferencesThis(dd.symbol))
+ if (!dd.symbol.hasFlag(STATIC) && !methodReferencesThis(dd.symbol)) {
dd.symbol.setFlag(STATIC)
+ dd.symbol.removeAttachment[mixer.NeedStaticImpl.type]
+ }
super.transform(tree)
case Apply(fun, outer :: rest) if shouldElideOuterArg(fun.symbol, outer) =>
val nullOuter = gen.mkZero(outer.tpe)
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index 6df0b992ed..d62b77dac2 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -130,6 +130,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
*/
def addMember(clazz: Symbol, member: Symbol): Symbol = {
debuglog(s"mixing into $clazz: ${member.defString}")
+ // This attachment is used to instruct the backend about which methids in traits require
+ // a static trait impl method. We remove this from the new symbol created for the method
+ // mixed into the subclass.
+ member.removeAttachment[NeedStaticImpl.type]
clazz.info.decls enter member setFlag MIXEDIN resetFlag JAVA_DEFAULTMETHOD
}
def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol =
@@ -344,6 +348,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
reporter.error(clazz.pos, "Member %s of mixin %s is missing a concrete super implementation.".format(
mixinMember.alias, mixinClass))
case alias1 =>
+ if (alias1.owner.isJavaDefined && alias1.owner.isInterface && !clazz.parentSymbols.contains(alias1.owner)) {
+ val suggestedParent = exitingTyper(clazz.info.baseType(alias1.owner))
+ reporter.error(clazz.pos, s"Unable to implement a super accessor required by trait ${mixinClass.name} unless $suggestedParent is directly extended by $clazz.")
+ }
superAccessor.asInstanceOf[TermSymbol] setAlias alias1
}
}
@@ -1001,13 +1009,20 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
val parents1 = currentOwner.info.parents map (t => TypeTree(t) setPos tree.pos)
// mark fields which can be nulled afterward
lazyValNullables = nullableFields(templ) withDefaultValue Set()
+ // Remove bodies of accessors in traits - TODO: after PR #5141 (fields refactoring), this might be a no-op
val bodyEmptyAccessors = if (!sym.enclClass.isTrait) body else body mapConserve {
case dd: DefDef if dd.symbol.isAccessor && !dd.symbol.isLazy =>
deriveDefDef(dd)(_ => EmptyTree)
case tree => tree
}
// add all new definitions to current class or interface
- treeCopy.Template(tree, parents1, self, addNewDefs(currentOwner, bodyEmptyAccessors))
+ val body1 = addNewDefs(currentOwner, bodyEmptyAccessors)
+ body1 foreach {
+ case dd: DefDef if isTraitMethodRequiringStaticImpl(dd) =>
+ dd.symbol.updateAttachment(NeedStaticImpl)
+ case _ =>
+ }
+ treeCopy.Template(tree, parents1, self, body1)
case Select(qual, name) if sym.owner.isTrait && !sym.isMethod =>
// refer to fields in some trait an abstract getter in the interface.
@@ -1023,7 +1038,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
typedPos(tree.pos)((qual DOT setter)(rhs))
-
case _ =>
tree
}
@@ -1042,4 +1056,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
finally localTyper = saved
}
}
+
+ private def isTraitMethodRequiringStaticImpl(dd: DefDef): Boolean = {
+ val sym = dd.symbol
+ dd.rhs.nonEmpty &&
+ sym.owner.isTrait &&
+ !sym.isPrivate && // no need to put implementations of private methods into a static method
+ !sym.hasFlag(Flags.STATIC)
+ }
+
+ case object NeedStaticImpl extends PlainAttachment
}