Skip to content

Commit f327ff1

Browse files
committed
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. ``` > ;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] } ```
1 parent cdf38a1 commit f327ff1

23 files changed

+225
-144
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala

+5-7
Original file line numberDiff line numberDiff line change
@@ -1060,12 +1060,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
10601060
receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits
10611061
val receiverName = internalName(receiverClass)
10621062

1063-
// super calls are only allowed to direct parents
1064-
if (style.isSuper && receiverClass.isTraitOrInterface && !cnode.interfaces.contains(receiverName)) {
1065-
thisBType.info.get.inlineInfo.lateInterfaces += receiverName
1066-
cnode.interfaces.add(receiverName)
1067-
}
1068-
10691063
def needsInterfaceCall(sym: Symbol) = {
10701064
sym.isTraitOrInterface ||
10711065
sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass)
@@ -1082,7 +1076,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
10821076
case Virtual =>
10831077
if (needsInterfaceCall(receiverClass)) bc.invokeinterface(receiverName, jname, mdescr, pos)
10841078
else bc.invokevirtual (receiverName, jname, mdescr, pos)
1085-
case Super => bc.invokespecial (receiverName, jname, mdescr, pos)
1079+
case Super =>
1080+
if (receiverClass.isTraitOrInterface) {
1081+
val staticDesc = MethodBType(typeToBType(method.owner.info) :: method.info.paramTypes.map(typeToBType), typeToBType(method.info.resultType)).descriptor
1082+
bc.invokestatic(receiverName, jname, staticDesc, pos)
1083+
} else bc.invokespecial (receiverName, jname, mdescr, pos)
10861084
}
10871085

10881086
bmType.returnType

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

+4-52
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import scala.tools.asm
1111
import scala.tools.nsc.io.AbstractFile
1212
import GenBCode._
1313
import BackendReporting._
14+
import scala.reflect.internal.Flags
1415

1516
/*
1617
* Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
@@ -49,6 +50,9 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
4950
}
5051
}
5152

53+
def isTraitMethodRequiringStaticImpl(sym: Symbol) =
54+
sym.hasAttachment[global.mixer.NeedStaticImpl.type]
55+
5256
/**
5357
* True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
5458
* member class. This method is used to decide if we should emit an EnclosingMethod attribute.
@@ -230,58 +234,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
230234
sym.isErroneous
231235
}
232236

233-
/**
234-
* Build the [[InlineInfo]] for a class symbol.
235-
*/
236-
def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = {
237-
val isEffectivelyFinal = classSym.isEffectivelyFinal
238-
239-
val sam = {
240-
if (classSym.isEffectivelyFinal) None
241-
else {
242-
// Phase travel necessary. For example, nullary methods (getter of an abstract val) get an
243-
// empty parameter list in later phases and would therefore be picked as SAM.
244-
val samSym = exitingPickler(definitions.samOf(classSym.tpe))
245-
if (samSym == NoSymbol) None
246-
else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym))
247-
}
248-
}
249-
250-
var warning = Option.empty[ClassSymbolInfoFailureSI9111]
251-
252-
// Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
253-
// primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
254-
val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
255-
case methodSym =>
256-
if (completeSilentlyAndCheckErroneous(methodSym)) {
257-
// Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
258-
if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
259-
warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
260-
None
261-
} else {
262-
val name = methodSym.javaSimpleName.toString // same as in genDefDef
263-
val signature = name + methodSymToDescriptor(methodSym)
264-
265-
// In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the
266-
// method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded
267-
// so they are not marked final in the InlineInfo attribute.
268-
//
269-
// However, due to https://github.com/scala/scala-dev/issues/126, this currently does not
270-
// work, the abstract accessor for O will be marked effectivelyFinal.
271-
val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred
272-
273-
val info = MethodInlineInfo(
274-
effectivelyFinal = effectivelyFinal,
275-
annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
276-
annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass)
277-
)
278-
Some((signature, info))
279-
}
280-
}).toMap
281-
282-
InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning)
283-
}
284-
285237
/*
286238
* must-single-thread
287239
*/

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

+18-1
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,24 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
488488

489489
case ValDef(mods, name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()`
490490

491-
case dd : DefDef => genDefDef(dd)
491+
case dd : DefDef =>
492+
val sym = dd.symbol
493+
if (isTraitMethodRequiringStaticImpl(sym)) {
494+
// Split concrete methods in traits (including mixin constructors) into a static method
495+
// with an explicit this parameter, and a non-static forwarder method.
496+
val staticDefDef = global.gen.mkStatic(dd, _.cloneSymbol)
497+
val forwarderDefDef = {
498+
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)
499+
// we don't want to the optimizer to inline the static method into the forwarder. Instead,
500+
// the backend has a special case to transitively inline into a callsite of the forwarder
501+
// when the forwarder itself is inlined.
502+
forwarderBody.updateAttachment(NoInlineCallsiteAttachment)
503+
deriveDefDef(dd)(_ => global.atPos(dd.pos)(forwarderBody))
504+
}
505+
genDefDef(staticDefDef)
506+
if (!sym.isMixinConstructor)
507+
genDefDef(forwarderDefDef)
508+
} else genDefDef(dd)
492509

493510
case Template(_, _, body) => body foreach gen
494511

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

+1-21
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,7 @@ abstract class BTypes {
225225

226226
val inlineInfo = inlineInfoFromClassfile(classNode)
227227

228-
val classfileInterfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
229-
val interfaces = classfileInterfaces.filterNot(i => inlineInfo.lateInterfaces.contains(i.internalName))
228+
val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
230229

231230
classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
232231
classBType
@@ -1147,25 +1146,6 @@ object BTypes {
11471146
sam: Option[String],
11481147
methodInfos: Map[String, MethodInlineInfo],
11491148
warning: Option[ClassInlineInfoWarning]) {
1150-
/**
1151-
* A super call (invokespecial) to a default method T.m is only allowed if the interface T is
1152-
* a direct parent of the class. Super calls are introduced for example in Mixin when generating
1153-
* forwarder methods:
1154-
*
1155-
* trait T { override def clone(): Object = "hi" }
1156-
* trait U extends T
1157-
* class C extends U
1158-
*
1159-
* The class C gets a forwarder that invokes T.clone(). During code generation the interface T
1160-
* is added as direct parent to class C. Note that T is not a (direct) parent in the frontend
1161-
* type of class C.
1162-
*
1163-
* All interfaces that are added to a class during code generation are added to this buffer and
1164-
* stored in the InlineInfo classfile attribute. This ensures that the ClassBTypes for a
1165-
* specific class is the same no matter if it's constructed from a Symbol or from a classfile.
1166-
* This is tested in BTypesFromClassfileTest.
1167-
*/
1168-
val lateInterfaces: ListBuffer[InternalName] = ListBuffer.empty
11691149
}
11701150

11711151
val EmptyInlineInfo = InlineInfo(false, None, Map.empty, None)

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

+68-1
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
509509
* classfile attribute.
510510
*/
511511
private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = {
512-
def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor)
512+
def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym)
513513

514514
// phase travel required, see implementation of `compiles`. for nested classes, it checks if the
515515
// enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level,
@@ -530,6 +530,73 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
530530
}
531531
}
532532

533+
/**
534+
* Build the [[InlineInfo]] for a class symbol.
535+
*/
536+
def buildInlineInfoFromClassSymbol(classSym: Symbol): InlineInfo = {
537+
val isEffectivelyFinal = classSym.isEffectivelyFinal
538+
539+
val sam = {
540+
if (classSym.isEffectivelyFinal) None
541+
else {
542+
// Phase travel necessary. For example, nullary methods (getter of an abstract val) get an
543+
// empty parameter list in later phases and would therefore be picked as SAM.
544+
val samSym = exitingPickler(definitions.samOf(classSym.tpe))
545+
if (samSym == NoSymbol) None
546+
else Some(samSym.javaSimpleName.toString + methodBTypeFromSymbol(samSym).descriptor)
547+
}
548+
}
549+
550+
var warning = Option.empty[ClassSymbolInfoFailureSI9111]
551+
552+
// Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
553+
// primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
554+
val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
555+
case methodSym =>
556+
if (completeSilentlyAndCheckErroneous(methodSym)) {
557+
// Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
558+
if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
559+
warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
560+
Nil
561+
} else {
562+
val name = methodSym.javaSimpleName.toString // same as in genDefDef
563+
val signature = name + methodBTypeFromSymbol(methodSym).descriptor
564+
565+
// In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the
566+
// method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded
567+
// so they are not marked final in the InlineInfo attribute.
568+
//
569+
// However, due to https://github.com/scala/scala-dev/issues/126, this currently does not
570+
// work, the abstract accessor for O will be marked effectivelyFinal.
571+
val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred
572+
573+
val info = MethodInlineInfo(
574+
effectivelyFinal = effectivelyFinal,
575+
annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
576+
annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass))
577+
578+
if (isTraitMethodRequiringStaticImpl(methodSym)) {
579+
val selfParam = methodSym.newSyntheticValueParam(methodSym.owner.typeConstructor, nme.SELF)
580+
val staticMethodType = methodSym.info match {
581+
case mt @ MethodType(params, res) => copyMethodType(mt, selfParam :: params, res)
582+
}
583+
val staticMethodSignature = name + methodBTypeFromMethodType(staticMethodType, isConstructor = false)
584+
val staticMethodInfo = MethodInlineInfo(
585+
effectivelyFinal = true,
586+
annotatedInline = info.annotatedInline,
587+
annotatedNoInline = info.annotatedNoInline)
588+
if (methodSym.isMixinConstructor)
589+
List((staticMethodSignature, staticMethodInfo))
590+
else
591+
List((signature, info), (staticMethodSignature, staticMethodInfo))
592+
} else
593+
List((signature, info))
594+
}
595+
}).toMap
596+
597+
InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning)
598+
}
599+
533600
/**
534601
* For top-level objects without a companion class, the compiler generates a mirror class with
535602
* static forwarders (Java compat). There's no symbol for the mirror class, but we still need a

src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala

+9
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ object BytecodeUtils {
9393
op == INVOKESPECIAL || op == INVOKESTATIC
9494
}
9595

96+
def isVirtualCall(instruction: AbstractInsnNode): Boolean = {
97+
val op = instruction.getOpcode
98+
op == INVOKEVIRTUAL || op == INVOKEINTERFACE
99+
}
100+
101+
def isCall(instruction: AbstractInsnNode): Boolean = {
102+
isNonVirtualCall(instruction) || isVirtualCall(instruction)
103+
}
104+
96105
def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
97106

98107
def isConstructor(methodNode: MethodNode): Boolean = {

src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala

+6-4
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ package opt
99

1010
import scala.collection.immutable.IntMap
1111
import scala.reflect.internal.util.{NoPosition, Position}
12-
import scala.tools.asm.{Opcodes, Type, Handle}
12+
import scala.tools.asm.{Handle, Opcodes, Type}
1313
import scala.tools.asm.tree._
1414
import scala.collection.{concurrent, mutable}
1515
import scala.collection.JavaConverters._
16-
import scala.tools.nsc.backend.jvm.BTypes.InternalName
16+
import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo}
1717
import scala.tools.nsc.backend.jvm.BackendReporting._
1818
import scala.tools.nsc.backend.jvm.analysis._
19-
import ByteCodeRepository.{Source, CompilationUnit}
19+
import ByteCodeRepository.{CompilationUnit, Source}
2020
import BytecodeUtils._
2121

2222
class CallGraph[BT <: BTypes](val btypes: BT) {
@@ -68,6 +68,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
6868
}
6969

7070
def containsCallsite(callsite: Callsite): Boolean = callsites(callsite.callsiteMethod) contains callsite.callsiteInstruction
71+
def findCallSite(method: MethodNode, call: MethodInsnNode): Option[Callsite] = callsites.getOrElse(method, Map.empty).get(call)
7172

7273
def removeClosureInstantiation(indy: InvokeDynamicInsnNode, methodNode: MethodNode): Option[ClosureInstantiation] = {
7374
val methodClosureInits = closureInstantiations(methodNode)
@@ -359,7 +360,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
359360
"Invocation of" +
360361
s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" +
361362
s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" +
362-
s" in ${callsiteClass.internalName}.${callsiteMethod.name}"
363+
s" in ${callsiteClass.internalName}.${callsiteMethod.name}${callsiteMethod.desc}"
363364
}
364365

365366
final case class ClonedCallsite(callsite: Callsite, clonedWhenInlining: Callsite)
@@ -394,6 +395,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
394395
samParamTypes: IntMap[btypes.ClassBType],
395396
calleeInfoWarning: Option[CalleeInfoWarning]) {
396397
override def toString = s"Callee($calleeDeclarationClass.${callee.name})"
398+
def textifyCallee = AsmUtils.textify(callee)
397399
}
398400

399401
/**

src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala

-13
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
5151
if (inlineInfo.isEffectivelyFinal) flags |= 1
5252
// flags |= 2 // no longer written
5353
if (inlineInfo.sam.isDefined) flags |= 4
54-
if (inlineInfo.lateInterfaces.nonEmpty) flags |= 8
5554
result.putByte(flags)
5655

5756
for (samNameDesc <- inlineInfo.sam) {
@@ -79,9 +78,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
7978
result.putByte(inlineInfo)
8079
}
8180

82-
result.putShort(inlineInfo.lateInterfaces.length)
83-
for (i <- inlineInfo.lateInterfaces) result.putShort(cw.newUTF8(i))
84-
8581
result
8682
}
8783

@@ -105,7 +101,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
105101
val isFinal = (flags & 1) != 0
106102
val hasSelf = (flags & 2) != 0
107103
val hasSam = (flags & 4) != 0
108-
val hasLateInterfaces = (flags & 8) != 0
109104

110105
if (hasSelf) nextUTF8() // no longer used
111106

@@ -128,13 +123,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
128123
(name + desc, MethodInlineInfo(isFinal, isInline, isNoInline))
129124
}).toMap
130125

131-
val lateInterfaces = if (!hasLateInterfaces) Nil else {
132-
val numLateInterfaces = nextShort()
133-
(0 until numLateInterfaces).map(_ => nextUTF8())
134-
}
135-
136126
val info = InlineInfo(isFinal, sam, infos, None)
137-
info.lateInterfaces ++= lateInterfaces
138127
InlineInfoAttribute(info)
139128
} else {
140129
val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version)
@@ -161,8 +150,6 @@ object InlineInfoAttribute {
161150
* [u2] name (reference)
162151
* [u2] descriptor (reference)
163152
* [u1] isFinal (<< 0), traitMethodWithStaticImplementation (<< 1), hasInlineAnnotation (<< 2), hasNoInlineAnnotation (<< 3)
164-
* [u2]? numLateInterfaces
165-
* [u2] lateInterface (reference)
166153
*/
167154
final val VERSION: Byte = 1
168155

0 commit comments

Comments
 (0)