Skip to content

Commit 633812e

Browse files
sjrdmichelou
authored andcommitted
Fix scala#14773: Reuse the param slots for the tailrec local mutable vars.
The tailrec phase generates code that looks like class Bar { def foo(x1: Int, x2: Int): Int = { var this$: Bar = this; var taillocal$x1: Int = x1; var taillocal$x2: Int = x2; ... // body where `this`, `x1` and `x2` are replaced by // `this$`, `taillocal$x1` and `taillocal$x2` } } This generates bytecode where the `this` value and the parameters never actually change, and are never used. Instead, the synthetic mutable variables are used instead. As described in the linked issue, this confuses debuggers, which only display the never-changing original `this` value and parameters. In this commit, we intercept this shape of code in the back-end. We reliable identify tailrec-generated `ValDef`s from their semantic names, with an additional safety check that they are `Synthetic | Mutable`. When we find this shape, we do not allocate local slots for the `var`s, and instead reuse the slots for `this` and the parameters. We skip past the `ValDef`s so that code generation does not re-emit useless assignments.
1 parent cf2eb91 commit 633812e

File tree

3 files changed

+84
-55
lines changed

3 files changed

+84
-55
lines changed

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

+43-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package jvm
44

55
import scala.language.unsafeNulls
66

7+
import scala.annotation.tailrec
8+
79
import scala.collection.{ mutable, immutable }
810

911
import scala.tools.asm
@@ -484,6 +486,14 @@ trait BCodeSkelBuilder extends BCodeHelpers {
484486
slots.getOrElse(locSym, makeLocal(locSym))
485487
}
486488

489+
def reuseLocal(sym: Symbol, loc: Local): Unit =
490+
val existing = slots.put(sym, loc)
491+
if (existing.isDefined)
492+
report.error("attempt to create duplicate local var.", ctx.source.atSpan(sym.span))
493+
494+
def reuseThisSlot(sym: Symbol): Unit =
495+
reuseLocal(sym, Local(symInfoTK(sym), sym.javaSimpleName, 0, sym.is(Synthetic)))
496+
487497
private def makeLocal(sym: Symbol, tk: BType): Local = {
488498
assert(nxtIdx != -1, "not a valid start index")
489499
val loc = Local(tk, sym.javaSimpleName, nxtIdx, sym.is(Synthetic))
@@ -753,18 +763,47 @@ trait BCodeSkelBuilder extends BCodeHelpers {
753763
.addFlagIf(isNative, asm.Opcodes.ACC_NATIVE) // native methods of objects are generated in mirror classes
754764

755765
// TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize }
756-
initJMethod(flags, params.map(_.symbol))
766+
val paramSyms = params.map(_.symbol)
767+
initJMethod(flags, paramSyms)
757768

758769

759770
if (!isAbstractMethod && !isNative) {
771+
// #14773 Reuse locals slots for tailrec-generated mutable vars
772+
val trimmedRhs: Tree =
773+
@tailrec def loop(stats: List[Tree]): List[Tree] =
774+
stats match
775+
case (tree @ ValDef(TailLocalName(_, _), _, _)) :: rest if tree.symbol.isAllOf(Mutable | Synthetic) =>
776+
tree.rhs match
777+
case This(_) =>
778+
locals.reuseThisSlot(tree.symbol)
779+
loop(rest)
780+
case rhs: Ident if paramSyms.contains(rhs.symbol) =>
781+
locals.reuseLocal(tree.symbol, locals(rhs.symbol))
782+
loop(rest)
783+
case _ =>
784+
stats
785+
case _ =>
786+
stats
787+
end loop
788+
789+
rhs match
790+
case Block(stats, expr) =>
791+
val trimmedStats = loop(stats)
792+
if trimmedStats eq stats then
793+
rhs
794+
else
795+
Block(trimmedStats, expr)
796+
case _ =>
797+
rhs
798+
end trimmedRhs
760799

761800
def emitNormalMethodBody(): Unit = {
762801
val veryFirstProgramPoint = currProgramPoint()
763-
genLoad(rhs, returnType)
802+
genLoad(trimmedRhs, returnType)
764803

765-
rhs match {
804+
trimmedRhs match {
766805
case (_: Return) | Block(_, (_: Return)) => ()
767-
case (_: Apply) | Block(_, (_: Apply)) if rhs.symbol eq defn.throwMethod => ()
806+
case (_: Apply) | Block(_, (_: Apply)) if trimmedRhs.symbol eq defn.throwMethod => ()
768807
case tpd.EmptyTree =>
769808
report.error("Concrete method has no definition: " + dd + (
770809
if (ctx.settings.Ydebug.value) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")"

compiler/src/dotty/tools/dotc/transform/TailRec.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ class TailRec extends MiniPhase {
253253
val tpe =
254254
if (enclosingClass.is(Module)) enclosingClass.thisType
255255
else enclosingClass.classInfo.selfType
256-
val sym = newSymbol(method, nme.SELF, Synthetic | Mutable, tpe)
256+
val sym = newSymbol(method, TailLocalName.fresh(nme.SELF), Synthetic | Mutable, tpe)
257257
varForRewrittenThis = Some(sym)
258258
sym
259259
}

compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala

+40-50
Original file line numberDiff line numberDiff line change
@@ -971,41 +971,35 @@ class TestBCode extends DottyBytecodeTest {
971971
val factMeth = getMethod(fooClass, "fact")
972972

973973
assertSameCode(factMeth, List(
974+
Label(0),
975+
VarOp(ILOAD, 1),
976+
Op(ICONST_0),
977+
Jump(IF_ICMPNE, Label(7)),
978+
VarOp(ILOAD, 2),
979+
Jump(GOTO, Label(26)),
980+
Label(7),
974981
VarOp(ALOAD, 0),
975982
VarOp(ASTORE, 3),
976-
VarOp(ILOAD, 2),
983+
VarOp(ILOAD, 1),
984+
Op(ICONST_1),
985+
Op(ISUB),
977986
VarOp(ISTORE, 4),
987+
VarOp(ILOAD, 2),
978988
VarOp(ILOAD, 1),
989+
Op(IMUL),
979990
VarOp(ISTORE, 5),
980-
Label(6),
981-
VarOp(ILOAD, 5),
982-
Op(ICONST_0),
983-
Jump(IF_ICMPNE, Label(13)),
984-
VarOp(ILOAD, 4),
985-
Jump(GOTO, Label(32)),
986-
Label(13),
987991
VarOp(ALOAD, 3),
988-
VarOp(ASTORE, 6),
989-
VarOp(ILOAD, 5),
990-
Op(ICONST_1),
991-
Op(ISUB),
992-
VarOp(ISTORE, 7),
992+
VarOp(ASTORE, 0),
993993
VarOp(ILOAD, 4),
994+
VarOp(ISTORE, 1),
994995
VarOp(ILOAD, 5),
995-
Op(IMUL),
996-
VarOp(ISTORE, 8),
997-
VarOp(ALOAD, 6),
998-
VarOp(ASTORE, 3),
999-
VarOp(ILOAD, 7),
1000-
VarOp(ISTORE, 5),
1001-
VarOp(ILOAD, 8),
1002-
VarOp(ISTORE, 4),
1003-
Jump(GOTO, Label(35)),
1004-
Label(32),
996+
VarOp(ISTORE, 2),
997+
Jump(GOTO, Label(29)),
998+
Label(26),
1005999
Op(IRETURN),
1006-
Label(35),
1007-
Jump(GOTO, Label(6)),
1008-
Op(ATHROW),
1000+
Label(29),
1001+
Jump(GOTO, Label(0)),
1002+
Op(NOP),
10091003
Op(ATHROW),
10101004
))
10111005

@@ -1015,39 +1009,35 @@ class TestBCode extends DottyBytecodeTest {
10151009
val sumMeth = getMethod(intListClass, "sum")
10161010

10171011
assertSameCode(sumMeth, List(
1012+
Label(0),
10181013
VarOp(ALOAD, 0),
1019-
VarOp(ASTORE, 2),
1020-
VarOp(ILOAD, 1),
1021-
VarOp(ISTORE, 3),
1022-
Label(4),
1023-
VarOp(ALOAD, 2),
10241014
Field(GETFIELD, "IntList", "tail", "LIntList;"),
1025-
VarOp(ASTORE, 4),
1026-
VarOp(ALOAD, 4),
1027-
Jump(IFNONNULL, Label(16)),
1028-
VarOp(ILOAD, 3),
1015+
VarOp(ASTORE, 2),
10291016
VarOp(ALOAD, 2),
1017+
Jump(IFNONNULL, Label(12)),
1018+
VarOp(ILOAD, 1),
1019+
VarOp(ALOAD, 0),
10301020
Field(GETFIELD, "IntList", "head", "I"),
10311021
Op(IADD),
1032-
Jump(GOTO, Label(30)),
1033-
Label(16),
1034-
VarOp(ALOAD, 4),
1035-
VarOp(ASTORE, 5),
1036-
VarOp(ILOAD, 3),
1022+
Jump(GOTO, Label(26)),
1023+
Label(12),
10371024
VarOp(ALOAD, 2),
1025+
VarOp(ASTORE, 3),
1026+
VarOp(ILOAD, 1),
1027+
VarOp(ALOAD, 0),
10381028
Field(GETFIELD, "IntList", "head", "I"),
10391029
Op(IADD),
1040-
VarOp(ISTORE, 6),
1041-
VarOp(ALOAD, 5),
1042-
VarOp(ASTORE, 2),
1043-
VarOp(ILOAD, 6),
1044-
VarOp(ISTORE, 3),
1045-
Jump(GOTO, Label(33)),
1046-
Label(30),
1030+
VarOp(ISTORE, 4),
1031+
VarOp(ALOAD, 3),
1032+
VarOp(ASTORE, 0),
1033+
VarOp(ILOAD, 4),
1034+
VarOp(ISTORE, 1),
1035+
Jump(GOTO, Label(29)),
1036+
Label(26),
10471037
Op(IRETURN),
1048-
Label(33),
1049-
Jump(GOTO, Label(4)),
1050-
Op(ATHROW),
1038+
Label(29),
1039+
Jump(GOTO, Label(0)),
1040+
Op(NOP),
10511041
Op(ATHROW),
10521042
))
10531043
}

0 commit comments

Comments
 (0)