Skip to content

adapt optimizer tests to nuke-impl-classes #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ object BytecodeUtils {

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

def isNonVirtualCall(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
op == INVOKESPECIAL || op == INVOKESTATIC
}

def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0

def isConstructor(methodNode: MethodNode): Boolean = {
Expand Down
7 changes: 4 additions & 3 deletions src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
(declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)]
} yield {
val declarationClassBType = classBTypeFromClassNode(declarationClassNode)
val CallsiteInfo(safeToInline, safeToRewrite, canInlineFromSource, annotatedInline, annotatedNoInline, samParamTypes, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source)
val CallsiteInfo(safeToInline, safeToRewrite, canInlineFromSource, annotatedInline, annotatedNoInline, samParamTypes, warning) = analyzeCallsite(method, declarationClassBType, call, source)
Callee(
callee = method,
calleeDeclarationClass = declarationClassBType,
Expand Down Expand Up @@ -264,7 +264,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
/**
* Analyze a callsite and gather meta-data that can be used for inlining decisions.
*/
private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): CallsiteInfo = {
private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, call: MethodInsnNode, calleeSource: Source): CallsiteInfo = {
val methodSignature = calleeMethodNode.name + calleeMethodNode.desc

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

val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode)

val receiverType = classBTypeFromParsedClassfile(receiverTypeInternalName)
val receiverType = classBTypeFromParsedClassfile(call.owner)
// (1) A non-final method can be safe to inline if the receiver type is a final subclass. Example:
// class A { @inline def f = 1 }; object B extends A; B.f // can be inlined
//
Expand All @@ -295,6 +295,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
// TODO: type analysis can render more calls statically resolved. Example:
// new A.f // can be inlined, the receiver type is known to be exactly A.
val isStaticallyResolved: Boolean = {
isNonVirtualCall(call) || // SD-86: super calls (invokespecial) can be inlined
methodInlineInfo.effectivelyFinal ||
receiverType.info.orThrow.inlineInfo.isEffectivelyFinal // (1)
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
import backendUtils._

def runInliner(): Unit = {
rewriteFinalTraitMethodInvocations()
// rewriteFinalTraitMethodInvocations()

for (request <- collectAndOrderInlineRequests) {
val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ class BTypesFromClassfileTest {
sameBType(fromSymbol, fromClassfile)
}

@Test
@Test @org.junit.Ignore
// test currently disabled, see https://github.com/retronym/scala/pull/19#issuecomment-185818210
def compareClassBTypes(): Unit = {
// Note that not only these classes are tested, but also all their parents and all nested
// classes in their InnerClass attributes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ class InlineWarningTest extends ClearAfterClass {
assert(count == 4, count)
}

@Test
// TODO SD-86: no more impl classes. this test (and the warning it tests!) can be removed
@org.junit.Ignore @Test
def traitMissingImplClass(): Unit = {
val codeA = "trait T { @inline final def f = 1 }"
val codeB = "class C { def t1(t: T) = t.f }"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class InlinerSeparateCompilationTest {
""".stripMargin

val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn)
val List(c, o, oMod, t) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn)
assertInvoke(getSingleMethod(c, "t1"), "T", "f")
assertNoInvoke(getSingleMethod(c, "t2"))
assertNoInvoke(getSingleMethod(c, "t3"))
Expand All @@ -63,7 +63,7 @@ class InlinerSeparateCompilationTest {
|}
""".stripMargin

val List(c, t, tCls) = compileClassesSeparately(List(codeA, codeB), args)
val List(c, t) = compileClassesSeparately(List(codeA, codeB), args)
assertNoInvoke(getSingleMethod(c, "t1"))
}

Expand All @@ -86,7 +86,7 @@ class InlinerSeparateCompilationTest {
|}
""".stripMargin

val List(c, t, tCls, u, uCls) = compileClassesSeparately(List(codeA, codeB), args)
val List(c, t, u) = compileClassesSeparately(List(codeA, codeB), args)
for (m <- List("t1", "t2", "t3")) assertNoInvoke(getSingleMethod(c, m))
}

Expand All @@ -107,8 +107,8 @@ class InlinerSeparateCompilationTest {
|$assembly
""".stripMargin

val List(a, aCls, t, tCls) = compileClassesSeparately(List(codeA, assembly), args)
assertNoInvoke(getSingleMethod(tCls, "f"))
assertNoInvoke(getSingleMethod(aCls, "n"))
val List(a, t) = compileClassesSeparately(List(codeA, assembly), args)
assertNoInvoke(getSingleMethod(t, "f"))
assertNoInvoke(getSingleMethod(a, "n"))
}
}
93 changes: 47 additions & 46 deletions test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class InlinerTest extends ClearAfterClass {
| def g(t: T) = t.f
|}
""".stripMargin
val List(c, t, tClass) = compile(code)
val List(c, t) = compile(code)
assertNoInvoke(getSingleMethod(c, "g"))
}

Expand Down Expand Up @@ -451,7 +451,7 @@ class InlinerTest extends ClearAfterClass {
| def t2(c: C) = c.f
|}
""".stripMargin
val List(c, t, tClass) = compile(code)
val List(c, t) = compile(code)
// both are just `return 1`, no more calls
assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
Expand All @@ -465,7 +465,7 @@ class InlinerTest extends ClearAfterClass {
|}
|class C extends T
""".stripMargin
val List(c, t, tClass) = compile(code)
val List(c, t) = compile(code)
// the static implementation method is inlined into the mixin, so there's no invocation in the mixin
assertNoInvoke(getSingleMethod(c, "f"))
}
Expand All @@ -484,7 +484,7 @@ class InlinerTest extends ClearAfterClass {
| def t2 = g
|}
""".stripMargin
val List(c, t, tClass, u, uClass) = compile(code)
val List(c, t, u) = compile(code)
assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
}
Expand All @@ -504,7 +504,7 @@ class InlinerTest extends ClearAfterClass {
"C::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
"T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden")
var count = 0
val List(c, t, tClass) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
val List(c, t) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
assert(count == 2, count)
assertInvoke(getSingleMethod(c, "t1"), "T", "f")
assertInvoke(getSingleMethod(c, "t2"), "C", "f")
Expand All @@ -520,7 +520,7 @@ class InlinerTest extends ClearAfterClass {
| def t1(t: T) = t.f
|}
""".stripMargin
val List(c, t, tClass) = compile(code)
val List(c, t) = compile(code)
assertNoInvoke(getSingleMethod(c, "t1"))
}

Expand All @@ -532,7 +532,7 @@ class InlinerTest extends ClearAfterClass {
|}
|object O extends T {
| @inline def g = 1
| // mixin generates `def f = T$class.f(this)`, which is inlined here (we get ICONST_0)
| // mixin generates `def f = super[T].f`, which is inlined here (we get ICONST_0)
|}
|class C {
| def t1 = O.f // the mixin method of O is inlined, so we directly get the ICONST_0
Expand All @@ -542,7 +542,7 @@ class InlinerTest extends ClearAfterClass {
""".stripMargin
val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
var count = 0
val List(c, oMirror, oModule, t, tClass) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
val List(c, oMirror, oModule, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
assert(count == 1, count)

assertNoInvoke(getSingleMethod(oModule, "f"))
Expand All @@ -561,21 +561,19 @@ class InlinerTest extends ClearAfterClass {
|}
|trait Assembly extends T {
| @inline final def g = 1
| @inline final def n = m // inlined. (*)
| // (*) 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
| // signature. then we can look up the MethodNode of T$class.m and then rewrite the INVOKEINTERFACE to INVOKESTATIC.
| @inline final def n = m // inlined (m is final)
|}
|class C {
| def t1(a: Assembly) = a.f // like above, decl class is T, need self-type of T to rewrite the interface call to static.
| def t1(a: Assembly) = a.f // inlined (f is final)
| def t2(a: Assembly) = a.n
|}
""".stripMargin

val List(assembly, assemblyClass, c, t, tClass) = compile(code)
val List(assembly, c, t) = compile(code)

assertNoInvoke(getSingleMethod(tClass, "f"))
assertNoInvoke(getSingleMethod(t, "f"))

assertNoInvoke(getSingleMethod(assemblyClass, "n"))
assertNoInvoke(getSingleMethod(assembly, "n"))

assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
Expand Down Expand Up @@ -610,30 +608,30 @@ class InlinerTest extends ClearAfterClass {
val code =
"""trait T1 {
| @inline def f: Int = 0
| @inline def g1 = f // not inlined: f not final, so T1$class.g1 has an interface call T1.f
| @inline def g1 = f // not inlined: f not final
|}
|
|// erased self-type (used in impl class for `self` parameter): T1
|// erased self-type: T1
|trait T2a { self: T1 with T2a =>
| @inline override final def f = 1
| @inline def g2a = f // inlined: resolved as T2a.f, which is re-written to T2a$class.f, so T2a$class.g2a has ICONST_1
| @inline def g2a = f // inlined: resolved as T2a.f
|}
|
|final class Ca extends T1 with T2a {
| // mixin generates accessors like `def g1 = T1$class.g1`, the impl class method call is inlined into the accessor.
| // mixin generates accessors like `def g1 = super[T1].g1`, the impl super call is inlined into the accessor.
|
| def m1a = g1 // call to accessor, inlined, we get the interface call T1.f
| def m2a = g2a // call to accessor, inlined, we get ICONST_1
| def m3a = f // call to accessor, inlined, we get ICONST_1
|
| def m4a(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
| def m5a(t: T2a) = t.f // re-written to T2a$class.f, inlined, ICONST_1
| def m4a(t: T1) = t.f // T1.f is not final, so not inlined, we get an interface call T1.f
| def m5a(t: T2a) = t.f // inlined, we get ICONST_1
|}
|
|// erased self-type: T2b
|trait T2b { self: T2b with T1 =>
| @inline override final def f = 1
| @inline def g2b = f // not inlined: resolved as T1.f, so T2b$class.g2b has an interface call T1.f
| @inline def g2b = f // not inlined: resolved as T1.f, we get an interface call T1.f
|}
|
|final class Cb extends T1 with T2b {
Expand All @@ -642,23 +640,17 @@ class InlinerTest extends ClearAfterClass {
| def m3b = f // inlined, we get ICONST_1
|
| def m4b(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
| def m5b(t: T2b) = t.f // re-written to T2b$class.f, inlined, ICONST_1
| def m5b(t: T2b) = t.f // inlined, ICONST_1
|}
""".stripMargin

val warning = "T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
var count = 0
val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code, allowMessage = i => {count += 1; i.msg contains warning})
val List(ca, cb, t1, t2a, t2b) = compile(code, allowMessage = i => {count += 1; i.msg contains warning})
assert(count == 4, count) // see comments, f is not inlined 4 times

val t2aCfDesc = t2aC.methods.asScala.find(_.name == "f").get.desc
assert(t2aCfDesc == "(LT1;)I", t2aCfDesc) // self-type of T2a is T1

val t2bCfDesc = t2bC.methods.asScala.find(_.name == "f").get.desc
assert(t2bCfDesc == "(LT2b;)I", t2bCfDesc) // self-type of T2b is T2b

assertNoInvoke(getSingleMethod(t2aC, "g2a"))
assertInvoke(getSingleMethod(t2bC, "g2b"), "T1", "f")
assertNoInvoke(getSingleMethod(t2a, "g2a"))
assertInvoke(getSingleMethod(t2b, "g2b"), "T1", "f")

assertInvoke(getSingleMethod(ca, "m1a"), "T1", "f")
assertNoInvoke(getSingleMethod(ca, "m2a")) // no invoke, see comment on def g2a
Expand Down Expand Up @@ -695,14 +687,13 @@ class InlinerTest extends ClearAfterClass {
val code =
"""class C {
| trait T { @inline final def f = 1 }
| class D extends T{
| class D extends T {
| def m(t: T) = t.f
| }
|
| def m(d: D) = d.f
|}
""".stripMargin
val List(c, d, t, tC) = compile(code)
val List(c, d, t) = compile(code)
assertNoInvoke(getSingleMethod(d, "m"))
assertNoInvoke(getSingleMethod(c, "m"))
}
Expand All @@ -717,9 +708,9 @@ class InlinerTest extends ClearAfterClass {
| def t2(t: T) = t.f(2)
|}
""".stripMargin
val List(c, t, tc) = compile(code)
val t1 = getSingleMethod(tc, "t1")
val t2 = getSingleMethod(tc, "t2")
val List(c, t) = compile(code)
val t1 = getSingleMethod(t, "t1")
val t2 = getSingleMethod(t, "t2")
val cast = TypeOp(CHECKCAST, "C")
Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions))
}
Expand Down Expand Up @@ -766,8 +757,8 @@ class InlinerTest extends ClearAfterClass {
| final val d = 3
| final val d1: Int = 3
|
| @noinline def f = 5 // re-written to T$class
| @noinline final def g = 6 // re-written
| @noinline def f = 5
| @noinline final def g = 6
|
| @noinline def h: Int
| @inline def i: Int
Expand All @@ -780,8 +771,8 @@ class InlinerTest extends ClearAfterClass {
| final val d = 3
| final val d1: Int = 3
|
| @noinline def f = 5 // not re-written (not final)
| @noinline final def g = 6 // re-written
| @noinline def f = 5
| @noinline final def g = 6
|
| @noinline def h: Int
| @inline def i: Int
Expand All @@ -798,7 +789,7 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin

val List(c, t, tClass, u, uClass) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined")
val List(c, t, u) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined")
val m1 = getSingleMethod(c, "m1")
assertInvoke(m1, "T", "a")
assertInvoke(m1, "T", "b")
Expand All @@ -807,8 +798,8 @@ class InlinerTest extends ClearAfterClass {
assertNoInvoke(getSingleMethod(c, "m2"))

val m3 = getSingleMethod(c, "m3")
assertInvoke(m3, "T$class", "f")
assertInvoke(m3, "T$class", "g")
assertInvoke(m3, "T", "f")
assertInvoke(m3, "T", "g")
assertInvoke(m3, "T", "h")
assertInvoke(m3, "T", "i")

Expand All @@ -821,7 +812,7 @@ class InlinerTest extends ClearAfterClass {

val m6 = getSingleMethod(c, "m6")
assertInvoke(m6, "U", "f")
assertInvoke(m6, "U$class", "g")
assertInvoke(m6, "U", "g")
assertInvoke(m6, "U", "h")
assertInvoke(m6, "U", "i")
}
Expand Down Expand Up @@ -1502,4 +1493,14 @@ class InlinerTest extends ClearAfterClass {
val List(a, b) = compileClassesSeparately(List(codeA, codeB), extraArgs = "-Yopt:l:project -Yopt-warnings")
assertInvoke(getSingleMethod(b, "t"), "A", "f")
}

@Test
def sd86(): Unit = {
val code =
"""trait T { @inline def f = 1 } // note that f is not final
|class C extends T
""".stripMargin
val List(c, t) = compile(code, allowMessage = _ => true)
assertSameSummary(getSingleMethod(c, "f"), List(ICONST_1, IRETURN))
}
}
Loading