Skip to content

Commit

Permalink
Emit trait method bodies in statics
Browse files Browse the repository at this point in the history
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.

```
> ;scalac sandbox/test.scala ; scala Test

[info] Running scala.tools.nsc.MainGenericRunner -usejavacp Test
T
C
[success] Total time: 2 s, completed 04/05/2016 11:07:13 AM
> eval "javap -classpath . -c -private C".!!
[info] ans: String = Compiled from "test.scala"
[info] public class C implements T {
[info]   public C();
[info]     Code:
[info]        0: aload_0
[info]        1: invokespecial #14                 // Method java/lang/Object."<init>":()V
[info]        4: aload_0
[info]        5: invokespecial #17                 // Method T.$init$:()V
[info]        8: getstatic     #23                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
[info]       11: ldc           #24                 // String C
[info]       13: invokevirtual #28                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
[info]       16: aload_0
[info]       17: invokespecial #32                 // Method T.foo:()I
[info]       20: pop
[info]       21: return
[info] }
> ;scalac -Yuse-trait-statics sandbox/test.scala ; scala Test

[info] Running scala.tools.nsc.MainGenericRunner -usejavacp Test
T
C
[success] Total time: 2 s, completed 04/05/2016 11:07:39 AM
> eval "javap -classpath . -c -private C".!!
[info] ans: String = Compiled from "test.scala"
[info] public class C implements T {
[info]   public C();
[info]     Code:
[info]        0: aload_0
[info]        1: invokespecial #14                 // Method java/lang/Object."<init>":()V
[info]        4: aload_0
[info]        5: invokestatic  #18                 // Method T.$init$:(LT;)V
[info]        8: getstatic     #24                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
[info]       11: ldc           #25                 // String C
[info]       13: invokevirtual #29                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
[info]       16: aload_0
[info]       17: invokestatic  #33                 // Method T.foo:(LT;)I
[info]       20: pop
[info]       21: return
[info] }
> eval "javap -classpath . -c -private T".!!
[info] ans: String = Compiled from "test.scala"
[info] public interface T {
[info]   public static int foo(T);
[info]     Code:
[info]        0: iconst_0
[info]        1: ireturn
[info]
[info]   public int foo();
[info]     Code:
[info]        0: aload_0
[info]        1: invokestatic  #15                 // Method foo:(LT;)I
[info]        4: ireturn
[info]
[info]   public static void $init$(T);
[info]     Code:
[info]        0: getstatic     #24                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
[info]        3: ldc           #25                 // String T
[info]        5: invokevirtual #29                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
[info]        8: return
[info]
[info] }
```
  • Loading branch information
retronym committed Jun 1, 2016
1 parent e82e457 commit e791790
Show file tree
Hide file tree
Showing 23 changed files with 222 additions and 141 deletions.
12 changes: 5 additions & 7 deletions src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
Expand Up @@ -1060,12 +1060,6 @@ 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)
}

def needsInterfaceCall(sym: Symbol) = {
sym.isTraitOrInterface ||
sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass)
Expand All @@ -1082,7 +1076,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
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)
case Super =>
if (receiverClass.isTraitOrInterface) {
val staticDesc = MethodBType(typeToBType(method.owner.info) :: method.info.paramTypes.map(typeToBType), typeToBType(method.info.resultType)).descriptor
bc.invokestatic(receiverName, jname, staticDesc, pos)
} else bc.invokespecial (receiverName, jname, mdescr, pos)
}

bmType.returnType
Expand Down
56 changes: 4 additions & 52 deletions src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
Expand Up @@ -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.
Expand Down Expand Up @@ -49,6 +50,9 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
}
}

def isTraitMethodRequiringStaticImpl(sym: Symbol) =
sym.hasAttachment[global.mixer.NeedStaticImpl.type]

/**
* 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.
Expand Down Expand Up @@ -230,58 +234,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
*/
Expand Down
19 changes: 18 additions & 1 deletion src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
Expand Up @@ -488,7 +488,24 @@ 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 (isTraitMethodRequiringStaticImpl(sym)) {
// Split concrete methods in traits (including mixin constructors) into a static method
// with an explicit this parameter, and a non-static forwarder method.
val staticDefDef = global.gen.mkStatic(dd, _.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

Expand Down
22 changes: 1 addition & 21 deletions src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
69 changes: 68 additions & 1 deletion src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
Expand Up @@ -509,7 +509,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,
Expand All @@ -530,6 +530,73 @@ 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 (isTraitMethodRequiringStaticImpl(methodSym)) {
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 = name + 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
Expand Down
Expand Up @@ -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 = {
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
Expand Up @@ -9,14 +9,14 @@ 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 ByteCodeRepository.{Source, CompilationUnit}
import ByteCodeRepository.{CompilationUnit, Source}
import BytecodeUtils._

class CallGraph[BT <: BTypes](val btypes: BT) {
Expand Down Expand Up @@ -68,6 +68,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)
Expand Down Expand Up @@ -359,7 +360,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)
Expand Down Expand Up @@ -394,6 +395,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
samParamTypes: IntMap[btypes.ClassBType],
calleeInfoWarning: Option[CalleeInfoWarning]) {
override def toString = s"Callee($calleeDeclarationClass.${callee.name})"
def textifyCallee = AsmUtils.textify(callee)
}

/**
Expand Down
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}

Expand All @@ -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

Expand All @@ -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)
Expand All @@ -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

Expand Down

0 comments on commit e791790

Please sign in to comment.