Skip to content

Commit ecc8944

Browse files
committed
Merge pull request scala#5058 from lrytz/newTraitsInliner
Inline super calls, as they are statically resolved
2 parents e5bbc98 + e483fe2 commit ecc8944

File tree

7 files changed

+58
-61
lines changed

7 files changed

+58
-61
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ object BytecodeUtils {
8888

8989
def isLoadOrStore(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction)
9090

91+
def isNonVirtualCall(instruction: AbstractInsnNode): Boolean = {
92+
val op = instruction.getOpcode
93+
op == INVOKESPECIAL || op == INVOKESTATIC
94+
}
95+
9196
def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
9297

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

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
132132
(declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)]
133133
} yield {
134134
val declarationClassBType = classBTypeFromClassNode(declarationClassNode)
135-
val CallsiteInfo(safeToInline, safeToRewrite, canInlineFromSource, annotatedInline, annotatedNoInline, samParamTypes, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source)
135+
val CallsiteInfo(safeToInline, safeToRewrite, canInlineFromSource, annotatedInline, annotatedNoInline, samParamTypes, warning) = analyzeCallsite(method, declarationClassBType, call, source)
136136
Callee(
137137
callee = method,
138138
calleeDeclarationClass = declarationClassBType,
@@ -264,7 +264,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
264264
/**
265265
* Analyze a callsite and gather meta-data that can be used for inlining decisions.
266266
*/
267-
private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): CallsiteInfo = {
267+
private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, call: MethodInsnNode, calleeSource: Source): CallsiteInfo = {
268268
val methodSignature = calleeMethodNode.name + calleeMethodNode.desc
269269

270270
try {
@@ -277,7 +277,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
277277

278278
val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode)
279279

280-
val receiverType = classBTypeFromParsedClassfile(receiverTypeInternalName)
280+
val receiverType = classBTypeFromParsedClassfile(call.owner)
281281
// (1) A non-final method can be safe to inline if the receiver type is a final subclass. Example:
282282
// class A { @inline def f = 1 }; object B extends A; B.f // can be inlined
283283
//
@@ -295,6 +295,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
295295
// TODO: type analysis can render more calls statically resolved. Example:
296296
// new A.f // can be inlined, the receiver type is known to be exactly A.
297297
val isStaticallyResolved: Boolean = {
298+
isNonVirtualCall(call) || // SD-86: super calls (invokespecial) can be inlined
298299
methodInlineInfo.effectivelyFinal ||
299300
receiverType.info.orThrow.inlineInfo.isEffectivelyFinal // (1)
300301
}

test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,17 @@ class BTypesFromClassfileTest {
6767
// there's a separate InlineInfoTest.
6868

6969
val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked)
70+
71+
// was:
72+
// val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1)
7073

74+
// TODO: The new trait encoding emits redundant parents in the backend to avoid linkage errors in invokespecial
75+
// Need to give this some more thought, maybe do it earlier so it is reflected in the Symbol's info, too.
7176
val fromSymInterfaces = fromSym.interfaces
7277
val fromClassFileInterfaces = fromClassfile.interfaces
7378
val (matching, other) = fromClassFileInterfaces.partition(x => fromSymInterfaces.exists(_.internalName == x.internalName))
7479
val chk2 = sameBTypes(fromSym.interfaces, matching, chk1)
7580
for (redundant <- other) {
76-
// TODO SD-86 The new trait encoding emits redundant parents in the backend to avoid linkage errors in invokespecial
77-
// Need to give this some more thought, maybe do it earlier so it is reflected in the Symbol's info, too.
7881
assert(matching.exists(x => x.isSubtypeOf(redundant).orThrow), redundant)
7982
}
8083

test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ class InlineInfoTest extends ClearAfterClass {
5959
|}
6060
|class C extends T with U
6161
""".stripMargin
62-
// val classes = compile(code) // SD-86
63-
InlineInfoTest.notPerRun.foreach(_.clear())
64-
val classes = compileClasses(compiler)(code, allowMessage = _ => true) // SD-86 inline warnings
62+
val classes = compile(code)
6563

6664
val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.get.inlineInfo)
6765

test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ class InlineWarningTest extends ClearAfterClass {
6767
"T::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
6868
"D::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden")
6969
compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
70-
assert(count == 5, count) // TODO SD-85: 5th warning
70+
assert(count == 4, count)
7171
}
7272

73-
// TODO SD-85: no more impl classes. this test (and the warning it tests!) can be removed
73+
// TODO SD-86: no more impl classes. this test (and the warning it tests!) can be removed
7474
@org.junit.Ignore @Test
7575
def traitMissingImplClass(): Unit = {
7676
val codeA = "trait T { @inline final def f = 1 }"

test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,10 @@ class InlinerSeparateCompilationTest {
4242
|}
4343
""".stripMargin
4444

45-
val warns = Set(
46-
"T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
47-
// TODO SD-85
48-
"""O$::f()I is annotated @inline but could not be inlined:
49-
|The callee O$::f()I contains the instruction INVOKESPECIAL T.f ()I
50-
|that would cause an IllegalAccessError when inlined into class C""".stripMargin)
51-
val List(c, o, oMod, t) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", i => warns.exists(i.msg contains _))
45+
val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
46+
val List(c, o, oMod, t) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn)
5247
assertInvoke(getSingleMethod(c, "t1"), "T", "f")
53-
// assertNoInvoke(getSingleMethod(c, "t2")) // SD-85
48+
assertNoInvoke(getSingleMethod(c, "t2"))
5449
assertNoInvoke(getSingleMethod(c, "t3"))
5550
}
5651

test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,6 @@ class InlinerTest extends ClearAfterClass {
324324
|}
325325
""".stripMargin
326326
val List(c, t) = compile(code)
327-
println(textify(c))
328-
println(textify(t))
329327
assertNoInvoke(getSingleMethod(c, "g"))
330328
}
331329

@@ -507,8 +505,7 @@ class InlinerTest extends ClearAfterClass {
507505
"T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden")
508506
var count = 0
509507
val List(c, t) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
510-
// 3rd warnings because of mixin-method, see SD-86
511-
assert(count == 3, count)
508+
assert(count == 2, count)
512509
assertInvoke(getSingleMethod(c, "t1"), "T", "f")
513510
assertInvoke(getSingleMethod(c, "t2"), "C", "f")
514511
}
@@ -535,27 +532,22 @@ class InlinerTest extends ClearAfterClass {
535532
|}
536533
|object O extends T {
537534
| @inline def g = 1
538-
| // mixin generates `def f = T$class.f(this)`, which is inlined here (we get ICONST_0)
535+
| // mixin generates `def f = super[T].f`, which is inlined here (we get ICONST_0)
539536
|}
540537
|class C {
541538
| def t1 = O.f // the mixin method of O is inlined, so we directly get the ICONST_0
542539
| def t2 = O.g // object members are inlined
543540
| def t3(t: T) = t.f // no inlining here
544541
|}
545542
""".stripMargin
546-
val warns = Set(
547-
"T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
548-
// SD-86 -- once the mixin-method O.f inlines the body of T.f, we can also inline O.g into class C.
549-
"""O$::f()I is annotated @inline but could not be inlined:
550-
|The callee O$::f()I contains the instruction INVOKESPECIAL T.f ()I
551-
|that would cause an IllegalAccessError when inlined into class C""".stripMargin)
543+
val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
552544
var count = 0
553-
val List(c, oMirror, oModule, t) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
554-
assert(count == 3, count) // SD-86
545+
val List(c, oMirror, oModule, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
546+
assert(count == 1, count)
555547

556-
// assertNoInvoke(getSingleMethod(oModule, "f")) // SD-86
548+
assertNoInvoke(getSingleMethod(oModule, "f"))
557549

558-
// assertNoInvoke(getSingleMethod(c, "t1")) // SD-86
550+
assertNoInvoke(getSingleMethod(c, "t1"))
559551
assertNoInvoke(getSingleMethod(c, "t2"))
560552
assertInvoke(getSingleMethod(c, "t3"), "T", "f")
561553
}
@@ -569,12 +561,10 @@ class InlinerTest extends ClearAfterClass {
569561
|}
570562
|trait Assembly extends T {
571563
| @inline final def g = 1
572-
| @inline final def n = m // inlined. (*)
573-
| // (*) the declaration class of m is T. the signature of T$class.m is m(LAssembly;)I. so we need the self type to build the
574-
| // signature. then we can look up the MethodNode of T$class.m and then rewrite the INVOKEINTERFACE to INVOKESTATIC.
564+
| @inline final def n = m // inlined (m is final)
575565
|}
576566
|class C {
577-
| def t1(a: Assembly) = a.f // like above, decl class is T, need self-type of T to rewrite the interface call to static.
567+
| def t1(a: Assembly) = a.f // inlined (f is final)
578568
| def t2(a: Assembly) = a.n
579569
|}
580570
""".stripMargin
@@ -618,30 +608,30 @@ class InlinerTest extends ClearAfterClass {
618608
val code =
619609
"""trait T1 {
620610
| @inline def f: Int = 0
621-
| @inline def g1 = f // not inlined: f not final, so T1$class.g1 has an interface call T1.f
611+
| @inline def g1 = f // not inlined: f not final
622612
|}
623613
|
624-
|// erased self-type (used in impl class for `self` parameter): T1
614+
|// erased self-type: T1
625615
|trait T2a { self: T1 with T2a =>
626616
| @inline override final def f = 1
627-
| @inline def g2a = f // inlined: resolved as T2a.f, which is re-written to T2a$class.f, so T2a$class.g2a has ICONST_1
617+
| @inline def g2a = f // inlined: resolved as T2a.f
628618
|}
629619
|
630620
|final class Ca extends T1 with T2a {
631-
| // mixin generates accessors like `def g1 = T1$class.g1`, the impl class method call is inlined into the accessor.
621+
| // mixin generates accessors like `def g1 = super[T1].g1`, the impl super call is inlined into the accessor.
632622
|
633623
| def m1a = g1 // call to accessor, inlined, we get the interface call T1.f
634624
| def m2a = g2a // call to accessor, inlined, we get ICONST_1
635625
| def m3a = f // call to accessor, inlined, we get ICONST_1
636626
|
637-
| def m4a(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
638-
| def m5a(t: T2a) = t.f // re-written to T2a$class.f, inlined, ICONST_1
627+
| def m4a(t: T1) = t.f // T1.f is not final, so not inlined, we get an interface call T1.f
628+
| def m5a(t: T2a) = t.f // inlined, we get ICONST_1
639629
|}
640630
|
641631
|// erased self-type: T2b
642632
|trait T2b { self: T2b with T1 =>
643633
| @inline override final def f = 1
644-
| @inline def g2b = f // not inlined: resolved as T1.f, so T2b$class.g2b has an interface call T1.f
634+
| @inline def g2b = f // not inlined: resolved as T1.f, we get an interface call T1.f
645635
|}
646636
|
647637
|final class Cb extends T1 with T2b {
@@ -650,31 +640,26 @@ class InlinerTest extends ClearAfterClass {
650640
| def m3b = f // inlined, we get ICONST_1
651641
|
652642
| def m4b(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
653-
| def m5b(t: T2b) = t.f // re-written to T2b$class.f, inlined, ICONST_1
643+
| def m5b(t: T2b) = t.f // inlined, ICONST_1
654644
|}
655645
""".stripMargin
656646

657-
val warnings = Set(
658-
"T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
659-
"T2b::g2b()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
660-
"T1::g1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
661-
"T2a::g2a()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
662-
"T1::g1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden")
647+
val warning = "T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
663648
var count = 0
664-
val List(ca, cb, t1, t2a, t2b) = compile(code, allowMessage = i => {count += 1; warnings.exists(i.msg contains _)})
665-
assert(count == 8, count) // see comments, f is not inlined 4 times, additional warnings due to SD-86
649+
val List(ca, cb, t1, t2a, t2b) = compile(code, allowMessage = i => {count += 1; i.msg contains warning})
650+
assert(count == 4, count) // see comments, f is not inlined 4 times
666651

667652
assertNoInvoke(getSingleMethod(t2a, "g2a"))
668653
assertInvoke(getSingleMethod(t2b, "g2b"), "T1", "f")
669654

670-
// assertInvoke(getSingleMethod(ca, "m1a"), "T1", "f") // disabled due to SD-86: m1a calls the mixin-method g1a, which calls super[T1].g1a. we inline the mixin-method and end up with the super call.
671-
// assertNoInvoke(getSingleMethod(ca, "m2a")) // no invoke, see comment on def g2a // SD-86
655+
assertInvoke(getSingleMethod(ca, "m1a"), "T1", "f")
656+
assertNoInvoke(getSingleMethod(ca, "m2a")) // no invoke, see comment on def g2a
672657
assertNoInvoke(getSingleMethod(ca, "m3a"))
673658
assertInvoke(getSingleMethod(ca, "m4a"), "T1", "f")
674659
assertNoInvoke(getSingleMethod(ca, "m5a"))
675660

676-
// assertInvoke(getSingleMethod(cb, "m1b"), "T1", "f") // SD-86
677-
// assertInvoke(getSingleMethod(cb, "m2b"), "T1", "f") // invoke, see comment on def g2b // SD-86
661+
assertInvoke(getSingleMethod(cb, "m1b"), "T1", "f")
662+
assertInvoke(getSingleMethod(cb, "m2b"), "T1", "f") // invoke, see comment on def g2b
678663
assertNoInvoke(getSingleMethod(cb, "m3b"))
679664
assertInvoke(getSingleMethod(cb, "m4b"), "T1", "f")
680665
assertNoInvoke(getSingleMethod(cb, "m5b"))
@@ -772,8 +757,8 @@ class InlinerTest extends ClearAfterClass {
772757
| final val d = 3
773758
| final val d1: Int = 3
774759
|
775-
| @noinline def f = 5 // re-written to T$class
776-
| @noinline final def g = 6 // re-written
760+
| @noinline def f = 5
761+
| @noinline final def g = 6
777762
|
778763
| @noinline def h: Int
779764
| @inline def i: Int
@@ -786,8 +771,8 @@ class InlinerTest extends ClearAfterClass {
786771
| final val d = 3
787772
| final val d1: Int = 3
788773
|
789-
| @noinline def f = 5 // not re-written (not final)
790-
| @noinline final def g = 6 // re-written
774+
| @noinline def f = 5
775+
| @noinline final def g = 6
791776
|
792777
| @noinline def h: Int
793778
| @inline def i: Int
@@ -1508,4 +1493,14 @@ class InlinerTest extends ClearAfterClass {
15081493
val List(a, b) = compileClassesSeparately(List(codeA, codeB), extraArgs = "-Yopt:l:project -Yopt-warnings")
15091494
assertInvoke(getSingleMethod(b, "t"), "A", "f")
15101495
}
1496+
1497+
@Test
1498+
def sd86(): Unit = {
1499+
val code =
1500+
"""trait T { @inline def f = 1 } // note that f is not final
1501+
|class C extends T
1502+
""".stripMargin
1503+
val List(c, t) = compile(code, allowMessage = _ => true)
1504+
assertSameSummary(getSingleMethod(c, "f"), List(ICONST_1, IRETURN))
1505+
}
15111506
}

0 commit comments

Comments
 (0)