Skip to content

Commit b3b59d5

Browse files
committed
Use Labeled blocks in TailRec, instead of label-defs.
It's easier to first explain on an example. Consider the following tail-recursive method: def fact(n: Int, acc: Int): Int = if (n == 0) acc else fact(n - 1, n * acc) It is now translated as follows by the `tailrec` transform: def fact(n: Int, acc: Int): Int = { var n$tailLocal1: Int = n var acc$tailLocal1: Int = acc while (true) { tailLabel1[Unit]: { return { if (n$tailLocal1 == 0) { acc } else { val n$tailLocal1$tmp1: Int = n$tailLocal1 - 1 val acc$tailLocal1$tmp1: Int = n$tailLocal1 * acc$tailLocal1 n$tailLocal1 = n$tailLocal1$tmp1 acc$tailLocal1 = acc$tailLocal1$tmp1 (return[tailLabel1] ()): Int } } } } throw null // unreachable code } First, we allocate local `var`s for every parameter, as well as `this` if necessary. When we find a tail-recursive call, we evaluate the arguments into temporaries, then assign them to the `var`s. It is necessary to use temporaries in order not to use the new contents of a param local when computing the new value of another param local. We avoid reassigning param locals if their rhs (i.e., the actual argument to the recursive call) is itself, which does happen quite often in practice. In particular, we thus avoid reassigning the local var for `this` if the prefix is empty. We could further optimize this by avoiding the reassignment if the prefix is non-empty but equivalent to `this`. If only one parameter ends up changing value in any particular tail-recursive call, we can avoid the temporaries and directly assign it. This is also a fairly common situation, especially after discarding useless assignments to the local for `this`. After all that, we `return` from a labeled block, which is right inside an infinite `while` loop. The net result is to loop back to the beginning, implementing the jump. The `return` node is explicitly ascribed with the previous result type, so that lubs upstream are not affected (not doing so can cause Ycheck errors). For control flows that do *not* end up in a tail-recursive call, the result value is given to an explicit `return` out of the enclosing method, which prevents the looping. There is one pretty ugly artifact: after the `while` loop, we must insert a `throw null` for the body to still typecheck as an `Int` (the result type of the `def`). This could be avoided if we dared type a `WhileDo(Literal(Constant(true)), body)` as having type `Nothing` rather than `Unit`. This is probably dangerous, though, as we have no guarantee that further transformations will leave the `true` alone, especially in the presence of compiler plugins. If the `true` gets wrapped in any way, the type of the `WhileDo` will be altered, and chaos will ensue. In the future, we could enhance the codegen to avoid emitting that dead code. This should not be too difficult: * emitting a `WhileDo` whose argument is `true` would set the generated `BType` to `Nothing`. * then, when emitting a `Block`, we would drop any statements and expr following a statement whose generated `BType` was `Nothing`. This commit does not go to such lengths, however. This change removes the last source of label-defs in the compiler. After this commit, we will be able to entirely remove label-defs.
1 parent f2b0b8a commit b3b59d5

File tree

3 files changed

+211
-121
lines changed

3 files changed

+211
-121
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
130130
def Return(expr: Tree, from: Tree)(implicit ctx: Context): Return =
131131
ta.assignType(untpd.Return(expr, from))
132132

133+
def Return(expr: Tree, from: Symbol)(implicit ctx: Context): Return =
134+
Return(expr, Ident(from.termRef))
135+
133136
def WhileDo(cond: Tree, body: Tree)(implicit ctx: Context): WhileDo =
134137
ta.assignType(untpd.WhileDo(cond, body))
135138

compiler/src/dotty/tools/dotc/core/NameKinds.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ object NameKinds {
286286
val NonLocalReturnKeyName: UniqueNameKind = new UniqueNameKind("nonLocalReturnKey")
287287
val WildcardParamName: UniqueNameKind = new UniqueNameKind("_$")
288288
val TailLabelName: UniqueNameKind = new UniqueNameKind("tailLabel")
289+
val TailLocalName: UniqueNameKind = new UniqueNameKind("$tailLocal")
290+
val TailTempName: UniqueNameKind = new UniqueNameKind("$tmp")
289291
val ExceptionBinderName: UniqueNameKind = new UniqueNameKind("ex")
290292
val SkolemName: UniqueNameKind = new UniqueNameKind("?")
291293
val LiftedTreeName: UniqueNameKind = new UniqueNameKind("liftedTree")

0 commit comments

Comments
 (0)