Skip to content

Commit 7d51b3f

Browse files
retronymadriaanm
authored andcommitted
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.
1 parent 91b066a commit 7d51b3f

33 files changed

+385
-206
lines changed

src/compiler/scala/tools/nsc/ast/TreeGen.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,13 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
336336
* - are associating the RHS with a cloned symbol, but intend for the original
337337
* method to remain and for recursive calls to target it.
338338
*/
339-
final def mkStatic(orig: DefDef, maybeClone: Symbol => Symbol): DefDef = {
339+
final def mkStatic(orig: DefDef, newName: Name, maybeClone: Symbol => Symbol): DefDef = {
340340
assert(phase.erasedTypes, phase)
341341
assert(!orig.symbol.hasFlag(SYNCHRONIZED), orig.symbol.defString)
342342
val origSym = orig.symbol
343343
val origParams = orig.symbol.info.params
344344
val newSym = maybeClone(orig.symbol)
345+
newSym.setName(newName)
345346
newSym.setFlag(STATIC)
346347
// Add an explicit self parameter
347348
val selfParamSym = newSym.newSyntheticValueParam(newSym.owner.typeConstructor, nme.SELF).setFlag(ARTIFACT)

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

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ package jvm
1111

1212
import scala.annotation.switch
1313
import scala.reflect.internal.Flags
14-
1514
import scala.tools.asm
1615
import GenBCode._
1716
import BackendReporting._
17+
import scala.tools.asm.Opcodes
1818
import scala.tools.asm.tree.MethodInsnNode
1919
import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp}
2020

@@ -637,15 +637,15 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
637637
val nativeKind = tpeTK(expr)
638638
genLoad(expr, nativeKind)
639639
val MethodNameAndType(mname, methodType) = srBoxesRuntimeBoxToMethods(nativeKind)
640-
bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos)
640+
bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
641641
generatedType = boxResultType(fun.symbol)
642642

643643
case Apply(fun, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) =>
644644
genLoad(expr)
645645
val boxType = unboxResultType(fun.symbol)
646646
generatedType = boxType
647647
val MethodNameAndType(mname, methodType) = srBoxesRuntimeUnboxToMethods(boxType)
648-
bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos)
648+
bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
649649

650650
case app @ Apply(fun, args) =>
651651
val sym = fun.symbol
@@ -1058,31 +1058,40 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
10581058
}
10591059

10601060
receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits
1061-
val receiverName = internalName(receiverClass)
1062-
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-
}
1061+
val receiverBType = classBTypeFromSymbol(receiverClass)
1062+
val receiverName = receiverBType.internalName
10681063

10691064
def needsInterfaceCall(sym: Symbol) = {
10701065
sym.isTraitOrInterface ||
10711066
sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass)
10721067
}
10731068

1074-
val jname = method.javaSimpleName.toString
1075-
val bmType = methodBTypeFromSymbol(method)
1076-
val mdescr = bmType.descriptor
1069+
val jname = method.javaSimpleName.toString
1070+
val bmType = methodBTypeFromSymbol(method)
1071+
val mdescr = bmType.descriptor
10771072

1073+
val isInterface = receiverBType.isInterface.get
10781074
import InvokeStyle._
1079-
style match {
1080-
case Static => bc.invokestatic (receiverName, jname, mdescr, pos)
1081-
case Special => bc.invokespecial (receiverName, jname, mdescr, pos)
1082-
case Virtual =>
1083-
if (needsInterfaceCall(receiverClass)) bc.invokeinterface(receiverName, jname, mdescr, pos)
1084-
else bc.invokevirtual (receiverName, jname, mdescr, pos)
1085-
case Super => bc.invokespecial (receiverName, jname, mdescr, pos)
1075+
if (style == Super) {
1076+
assert(receiverClass == methodOwner, s"for super call, expecting $receiverClass == $methodOwner")
1077+
if (receiverClass.isTrait && !receiverClass.isJavaDefined) {
1078+
val staticDesc = MethodBType(typeToBType(method.owner.info) :: bmType.argumentTypes, bmType.returnType).descriptor
1079+
val staticName = traitImplMethodName(method).toString
1080+
bc.invokestatic(receiverName, staticName, staticDesc, isInterface, pos)
1081+
} else {
1082+
if (receiverClass.isTraitOrInterface) {
1083+
// An earlier check in Mixin reports an error in this case, so it doesn't reach the backend
1084+
assert(cnode.interfaces.contains(receiverName), s"cannot invokespecial $receiverName.$jname, the interface is not a direct parent.")
1085+
}
1086+
bc.invokespecial(receiverName, jname, mdescr, isInterface, pos)
1087+
}
1088+
} else {
1089+
val opc = style match {
1090+
case Static => Opcodes.INVOKESTATIC
1091+
case Special => Opcodes.INVOKESPECIAL
1092+
case Virtual => if (isInterface) Opcodes.INVOKEINTERFACE else Opcodes.INVOKEVIRTUAL
1093+
}
1094+
bc.emitInvoke(opc, receiverName, jname, mdescr, isInterface, pos)
10861095
}
10871096

10881097
bmType.returnType

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

Lines changed: 10 additions & 61 deletions
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,14 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
4950
}
5051
}
5152

53+
def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type]
54+
55+
final def traitImplMethodName(sym: Symbol): Name = {
56+
val name = sym.javaSimpleName
57+
if (sym.isMixinConstructor) name
58+
else name.append(nme.NAME_JOIN_STRING)
59+
}
60+
5261
/**
5362
* True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
5463
* 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 {
230239
sym.isErroneous
231240
}
232241

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-
285242
/*
286243
* must-single-thread
287244
*/
@@ -568,15 +525,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
568525
/**
569526
* The class internal name for a given class symbol.
570527
*/
571-
final def internalName(sym: Symbol): String = {
572-
// For each java class, the scala compiler creates a class and a module (thus a module class).
573-
// If the `sym` is a java module class, we use the java class instead. This ensures that the
574-
// ClassBType is created from the main class (instead of the module class).
575-
// The two symbols have the same name, so the resulting internalName is the same.
576-
// Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting)
577-
val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
578-
classBTypeFromSymbol(classSym).internalName
579-
}
528+
final def internalName(sym: Symbol): String = classBTypeFromSymbol(sym).internalName
580529
} // end of trait BCInnerClassGen
581530

582531
trait BCAnnotGen extends BCInnerClassGen {

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ abstract class BCodeIdiomatic extends SubComponent {
190190
JavaStringBuilderClassName,
191191
INSTANCE_CONSTRUCTOR_NAME,
192192
"()V",
193+
itf = false,
193194
pos
194195
)
195196
}
@@ -373,30 +374,27 @@ abstract class BCodeIdiomatic extends SubComponent {
373374
final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread
374375

375376
// can-multi-thread
376-
final def invokespecial(owner: String, name: String, desc: String, pos: Position) {
377-
addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos)
377+
final def invokespecial(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
378+
emitInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, itf, pos)
378379
}
379380
// can-multi-thread
380-
final def invokestatic(owner: String, name: String, desc: String, pos: Position) {
381-
addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos)
381+
final def invokestatic(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
382+
emitInvoke(Opcodes.INVOKESTATIC, owner, name, desc, itf, pos)
382383
}
383384
// can-multi-thread
384-
final def invokeinterface(owner: String, name: String, desc: String, pos: Position) {
385-
addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos)
385+
final def invokeinterface(owner: String, name: String, desc: String, pos: Position): Unit = {
386+
emitInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, itf = true, pos)
386387
}
387388
// can-multi-thread
388-
final def invokevirtual(owner: String, name: String, desc: String, pos: Position) {
389-
addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos)
389+
final def invokevirtual(owner: String, name: String, desc: String, pos: Position): Unit = {
390+
emitInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf = false, pos)
390391
}
391392

392-
private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = {
393+
def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
393394
val node = new MethodInsnNode(opcode, owner, name, desc, itf)
394395
jmethod.instructions.add(node)
395396
if (settings.optInlinerEnabled) callsitePositions(node) = pos
396397
}
397-
final def invokedynamic(owner: String, name: String, desc: String) {
398-
jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc)
399-
}
400398

401399
// can-multi-thread
402400
final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,22 @@ 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 (needsStaticImplMethod(sym)) {
494+
val staticDefDef = global.gen.mkStatic(dd, traitImplMethodName(sym), _.cloneSymbol)
495+
val forwarderDefDef = {
496+
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)
497+
// we don't want to the optimizer to inline the static method into the forwarder. Instead,
498+
// the backend has a special case to transitively inline into a callsite of the forwarder
499+
// when the forwarder itself is inlined.
500+
forwarderBody.updateAttachment(NoInlineCallsiteAttachment)
501+
deriveDefDef(dd)(_ => global.atPos(dd.pos)(forwarderBody))
502+
}
503+
genDefDef(staticDefDef)
504+
if (!sym.isMixinConstructor)
505+
genDefDef(forwarderDefDef)
506+
} else genDefDef(dd)
492507

493508
case Template(_, _, body) => body foreach gen
494509

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

Lines changed: 1 addition & 21 deletions
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)

0 commit comments

Comments
 (0)