Skip to content
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

Optimize tailcalls even when called with different type arguments. #6065

Merged
merged 1 commit into from Sep 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/compiler/scala/tools/nsc/transform/TailCalls.scala
Expand Up @@ -63,10 +63,11 @@ abstract class TailCalls extends Transform {
* <p>
* A method call is self-recursive if it calls the current method and
* the method is final (otherwise, it could
* be a call to an overridden method in a subclass). Furthermore, If
* the method has type parameters, the call must contain these
* parameters as type arguments. Recursive calls on a different instance
* are optimized. Since 'this' is not a local variable, a dummy local val
* be a call to an overridden method in a subclass). Furthermore, if
* the method has `@specialized` annotated type parameters, the recursive
* call must contain these parameters as type arguments.
* Recursive calls on a different instance are optimized.
* Since 'this' is not a local variable, a dummy local val
* is added and used as a label parameter. The backend knows to load
* the corresponding argument in the 'this' (local at index 0). This dummy local
* is never used and should be cleaned up by dead code elimination (when enabled).
Expand Down Expand Up @@ -228,7 +229,10 @@ abstract class TailCalls extends Transform {
def receiverIsSuper = ctx.enclosingType.widen <:< receiver.tpe.widen
def isRecursiveCall = (ctx.method eq fun.symbol) && ctx.tailPos
def transformArgs = if (mustTransformArgs) noTailTransforms(args) else args
def matchesTypeArgs = ctx.tparams sameElements (targs map (_.tpe.typeSymbol))
def matchesTypeArgs = (ctx.tparams corresponds targs)((p, a) => !isSpecialized(p) || p == a.tpe.typeSymbol)

def isSpecialized(tparam: Symbol) =
tparam.hasAnnotation(SpecializedClass)

/* Records failure reason in Context for reporting.
* Position is unchanged (by default, the method definition.)
Expand Down Expand Up @@ -258,7 +262,7 @@ abstract class TailCalls extends Transform {
failHere("it contains a recursive call targeting a supertype")
else failHere(defaultReason)
}
else if (!matchesTypeArgs) failHere("it is called recursively with different type arguments")
else if (!matchesTypeArgs) failHere("it is called recursively with different specialized type arguments")
else if (receiver == EmptyTree) rewriteTailCall(This(currentClass))
else if (!receiverIsSame) failHere("it changes type of 'this' on a polymorphic recursive call")
else rewriteTailCall(receiver)
Expand Down
5 changes: 1 addition & 4 deletions test/files/neg/t6574.check
@@ -1,7 +1,4 @@
t6574.scala:4: error: could not optimize @tailrec annotated method notTailPos$extension: it contains a recursive call not in tail position
println("tail")
^
t6574.scala:8: error: could not optimize @tailrec annotated method differentTypeArgs$extension: it is called recursively with different type arguments
{(); new Bad[String, Unit](0)}.differentTypeArgs
^
two errors found
one error found
4 changes: 0 additions & 4 deletions test/files/neg/t6574.scala
Expand Up @@ -3,8 +3,4 @@ class Bad[X, Y](val v: Int) extends AnyVal {
this.notTailPos[Z](a)(b)
println("tail")
}

@annotation.tailrec final def differentTypeArgs {
{(); new Bad[String, Unit](0)}.differentTypeArgs
}
}
6 changes: 3 additions & 3 deletions test/files/neg/tailrec.check
Expand Up @@ -7,9 +7,9 @@ tailrec.scala:50: error: could not optimize @tailrec annotated method fail1: it
tailrec.scala:53: error: could not optimize @tailrec annotated method fail2: it contains a recursive call not in tail position
@tailrec final def fail2[T](xs: List[T]): List[T] = xs match {
^
tailrec.scala:59: error: could not optimize @tailrec annotated method fail3: it is called recursively with different type arguments
@tailrec final def fail3[T](x: Int): Int = fail3(x - 1)
^
tailrec.scala:59: error: could not optimize @tailrec annotated method fail3: it is called recursively with different specialized type arguments
@tailrec final def fail3[@specialized(Int) T](x: Int): Int = fail3(x - 1)
^
tailrec.scala:63: error: could not optimize @tailrec annotated method fail4: it changes type of 'this' on a polymorphic recursive call
@tailrec final def fail4[U](other: Tom[U], x: Int): Int = other.fail4[U](other, x - 1)
^
Expand Down
2 changes: 1 addition & 1 deletion test/files/neg/tailrec.scala
Expand Up @@ -56,7 +56,7 @@ class Failures {
}

// unsafe
@tailrec final def fail3[T](x: Int): Int = fail3(x - 1)
@tailrec final def fail3[@specialized(Int) T](x: Int): Int = fail3(x - 1)

// unsafe
class Tom[T](x: Int) {
Expand Down
4 changes: 4 additions & 0 deletions test/files/pos/t6574.scala
Expand Up @@ -11,6 +11,10 @@ class Bad[X, Y](val v: Int) extends AnyVal {
@annotation.tailrec final def dependent[Z](a: Int)(b: String): b.type = {
this.dependent[Z](a)(b)
}

@annotation.tailrec final def differentTypeArgs {
{(); new Bad[String, Unit](0)}.differentTypeArgs
}
}

class HK[M[_]](val v: Int) extends AnyVal {
Expand Down
13 changes: 13 additions & 0 deletions test/files/pos/t9647.scala
@@ -0,0 +1,13 @@
sealed trait HList
case class HCons[H, T <: HList](head: H, tail: T) extends HList
case object HNil extends HList

object Test {

@annotation.tailrec
def foo[L <: HList](l: L): Unit = l match {
case HNil => ()
case HCons(h, t) => foo(t)
}

}
6 changes: 6 additions & 0 deletions test/files/run/tailcalls.check
Expand Up @@ -56,6 +56,9 @@ test FancyTailCalls.tcInIfCond was successful
test FancyTailCalls.tcInPatternGuard was successful
test FancyTailCalls.differentInstance was successful
test PolyObject.tramp was successful
test PolyObject.size was successful
test PolyObject.specializedSize[Int] was successful
test PolyObject.specializedSize[String] was successful
#partest avian
test Object .f was successful
test Final .f was successful
Expand Down Expand Up @@ -114,3 +117,6 @@ test FancyTailCalls.tcInIfCond was successful
test FancyTailCalls.tcInPatternGuard was successful
test FancyTailCalls.differentInstance was successful
test PolyObject.tramp was successful
test PolyObject.size was successful
test PolyObject.specializedSize[Int] was successful
test PolyObject.specializedSize[String] was successful
16 changes: 16 additions & 0 deletions test/files/run/tailcalls.scala
Expand Up @@ -199,6 +199,19 @@ object PolyObject extends App {
tramp[A](x - 1)
else
0

def size[A](a: A, len: A => Int, tail: List[Either[String, Int]], acc: Int): Int = {
val acc1 = acc + len(a)
tail match {
case Nil => acc1
case Left(s) :: t => size[String](s, _.length, t, acc1)
case Right(i) :: t => size[Int] (i, _ => 1, t, acc1)
}
}

def specializedSize[@specialized(Int) A](len: A => Int, as: List[A], acc: Int): Int =
if(as.isEmpty) acc
else specializedSize[A](len, as.tail, acc + len(as.head))
}


Expand Down Expand Up @@ -410,6 +423,9 @@ object Test {
check_success_b("FancyTailCalls.tcInPatternGuard", FancyTailCalls.tcInPatternGuard(max, max), true)
check_success("FancyTailCalls.differentInstance", FancyTailCalls.differentInstance(max, 42), 42)
check_success("PolyObject.tramp", PolyObject.tramp[Int](max), 0)
check_success("PolyObject.size", PolyObject.size[Int](1, _ => 1, (1 to 5000).toList.flatMap(_ => List(Left("hi"), Right(5))), 0), 15001)
check_success("PolyObject.specializedSize[Int]", PolyObject.specializedSize[Int](_ => 1, (1 to 5000).toList, 0), 5000)
check_success("PolyObject.specializedSize[String]", PolyObject.specializedSize[String](_.length, List.fill(5000)("hi"), 0), 10000)
}

// testing explicit tailcalls.
Expand Down
Expand Up @@ -111,7 +111,7 @@ class OptimizedBytecodeTest extends BytecodeTesting {
def t8062(): Unit = {
val c1 =
"""package warmup
|object Warmup { def filter[A](p: Any => Boolean): Any = filter[Any](p) }
|object Warmup { def filter[A](p: Any => Boolean): Int = 1 + filter[Any](p) }
""".stripMargin
val c2 = "class C { def t = warmup.Warmup.filter[Any](x => false) }"
val List(c, _, _) = compileClassesSeparately(List(c1, c2), extraArgs = compilerArgs)
Expand Down