Skip to content

Commit 1bf0f6d

Browse files
authored
Opt: Get rid of the LiftTry phase; instead handle things in the back-end. (#18619)
When we enter a `try-catch` at the JVM level, we have to make sure that the stack is empty. That's because, upon exception, the JVM wipes the stack, and we must not lose operands that are already on the stack that we will still use. Previously, this was achieved with a transformation phase, `LiftTry`, which lifted problematic `try-catch`es in local `def`s, called `liftedTree$x`. It analyzed the tree to predict which `try-catch`es would execute on a non-empty stack when eventually compiled to the JVM. This approach has several shortcomings. It exhibits performance cliffs, as the generated def can then cause more variables to be boxed in to `XRef`s. These were the only extra defs created for implementation reasons rather than for language reasons. As a user of the language, it is hard to predict when such a lifted def will be needed. The additional `liftedTree` methods also show up on stack traces and obfuscate them. Debugging can be severely hampered as well. Phases executing after `LiftTry`, notably `CapturedVars`, also had to take care not to create more problematic situations as a result of their transformations, which is hard to predict and to remember. Finally, Scala.js and Scala Native do not have the same restriction, so they received suboptimal code for no reason. In this commit, we entirely remove the `LiftTry` phase. Instead, we enhance the JVM back-end to deal with the situation. When starting a `try-catch` on a non-empty stack, we stash the entire contents of the stack into local variables. After the `try-catch`, we pop all those local variables back onto the stack. We also null out the leftover vars not to prevent garbage collection. This new approach solves all of the problems mentioned above. --- This could be back-ported to Scala 2 if there is interest. /cc @adpi2 who wanted this to improve debugging.
2 parents 76df8d9 + 52e8e74 commit 1bf0f6d

File tree

13 files changed

+190
-206
lines changed

13 files changed

+190
-206
lines changed

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

+62-51
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,14 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
7979

8080
tree match {
8181
case Assign(lhs @ DesugaredSelect(qual, _), rhs) =>
82-
val savedStackHeight = stackHeight
82+
val savedStackSize = stack.recordSize()
8383
val isStatic = lhs.symbol.isStaticMember
8484
if (!isStatic) {
85-
genLoadQualifier(lhs)
86-
stackHeight += 1
85+
val qualTK = genLoad(qual)
86+
stack.push(qualTK)
8787
}
8888
genLoad(rhs, symInfoTK(lhs.symbol))
89-
stackHeight = savedStackHeight
89+
stack.restoreSize(savedStackSize)
9090
lineNumber(tree)
9191
// receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError
9292
val receiverClass = qual.tpe.typeSymbol
@@ -150,9 +150,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
150150
}
151151

152152
genLoad(larg, resKind)
153-
stackHeight += resKind.size
153+
stack.push(resKind)
154154
genLoad(rarg, if (isShift) INT else resKind)
155-
stackHeight -= resKind.size
155+
stack.pop()
156156

157157
(code: @switch) match {
158158
case ADD => bc add resKind
@@ -189,19 +189,19 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
189189
if (isArrayGet(code)) {
190190
// load argument on stack
191191
assert(args.length == 1, s"Too many arguments for array get operation: $tree");
192-
stackHeight += 1
192+
stack.push(k)
193193
genLoad(args.head, INT)
194-
stackHeight -= 1
194+
stack.pop()
195195
generatedType = k.asArrayBType.componentType
196196
bc.aload(elementType)
197197
}
198198
else if (isArraySet(code)) {
199199
val List(a1, a2) = args
200-
stackHeight += 1
200+
stack.push(k)
201201
genLoad(a1, INT)
202-
stackHeight += 1
202+
stack.push(INT)
203203
genLoad(a2)
204-
stackHeight -= 2
204+
stack.pop(2)
205205
generatedType = UNIT
206206
bc.astore(elementType)
207207
} else {
@@ -235,7 +235,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
235235
val resKind = if (hasUnitBranch) UNIT else tpeTK(tree)
236236

237237
val postIf = new asm.Label
238-
genLoadTo(thenp, resKind, LoadDestination.Jump(postIf, stackHeight))
238+
genLoadTo(thenp, resKind, LoadDestination.Jump(postIf, stack.recordSize()))
239239
markProgramPoint(failure)
240240
genLoadTo(elsep, resKind, LoadDestination.FallThrough)
241241
markProgramPoint(postIf)
@@ -294,8 +294,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
294294
)
295295
}
296296

297-
def genLoad(tree: Tree): Unit = {
298-
genLoad(tree, tpeTK(tree))
297+
def genLoad(tree: Tree): BType = {
298+
val generatedType = tpeTK(tree)
299+
genLoad(tree, generatedType)
300+
generatedType
299301
}
300302

301303
/* Generate code for trees that produce values on the stack */
@@ -364,6 +366,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
364366
case t @ Ident(_) => (t, Nil)
365367
}
366368

369+
val savedStackSize = stack.recordSize()
367370
if (!fun.symbol.isStaticMember) {
368371
// load receiver of non-static implementation of lambda
369372

@@ -372,10 +375,12 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
372375
// AbstractValidatingLambdaMetafactory.validateMetafactoryArgs
373376

374377
val DesugaredSelect(prefix, _) = fun: @unchecked
375-
genLoad(prefix)
378+
val prefixTK = genLoad(prefix)
379+
stack.push(prefixTK)
376380
}
377381

378382
genLoadArguments(env, fun.symbol.info.firstParamTypes map toTypeKind)
383+
stack.restoreSize(savedStackSize)
379384
generatedType = genInvokeDynamicLambda(NoSymbol, fun.symbol, env.size, functionalInterface)
380385

381386
case app @ Apply(_, _) =>
@@ -494,9 +499,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
494499
dest match
495500
case LoadDestination.FallThrough =>
496501
()
497-
case LoadDestination.Jump(label, targetStackHeight) =>
498-
if targetStackHeight < stackHeight then
499-
val stackDiff = stackHeight - targetStackHeight
502+
case LoadDestination.Jump(label, targetStackSize) =>
503+
val stackDiff = stack.heightDiffWrt(targetStackSize)
504+
if stackDiff != 0 then
500505
if expectedType == UNIT then
501506
bc dropMany stackDiff
502507
else
@@ -599,7 +604,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
599604
if dest == LoadDestination.FallThrough then
600605
val resKind = tpeTK(tree)
601606
val jumpTarget = new asm.Label
602-
registerJumpDest(labelSym, resKind, LoadDestination.Jump(jumpTarget, stackHeight))
607+
registerJumpDest(labelSym, resKind, LoadDestination.Jump(jumpTarget, stack.recordSize()))
603608
genLoad(expr, resKind)
604609
markProgramPoint(jumpTarget)
605610
resKind
@@ -657,7 +662,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
657662
markProgramPoint(loop)
658663

659664
if isInfinite then
660-
val dest = LoadDestination.Jump(loop, stackHeight)
665+
val dest = LoadDestination.Jump(loop, stack.recordSize())
661666
genLoadTo(body, UNIT, dest)
662667
dest
663668
else
@@ -672,7 +677,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
672677
val failure = new asm.Label
673678
genCond(cond, success, failure, targetIfNoJump = success)
674679
markProgramPoint(success)
675-
genLoadTo(body, UNIT, LoadDestination.Jump(loop, stackHeight))
680+
genLoadTo(body, UNIT, LoadDestination.Jump(loop, stack.recordSize()))
676681
markProgramPoint(failure)
677682
end match
678683
LoadDestination.FallThrough
@@ -765,10 +770,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
765770
// on the stack (contrary to what the type in the AST says).
766771

767772
// scala/bug#10290: qual can be `this.$outer()` (not just `this`), so we call genLoad (not just ALOAD_0)
768-
genLoad(superQual)
769-
stackHeight += 1
773+
val superQualTK = genLoad(superQual)
774+
stack.push(superQualTK)
770775
genLoadArguments(args, paramTKs(app))
771-
stackHeight -= 1
776+
stack.pop()
772777
generatedType = genCallMethod(fun.symbol, InvokeStyle.Super, app.span)
773778

774779
// 'new' constructor call: Note: since constructors are
@@ -790,9 +795,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
790795
assert(classBTypeFromSymbol(ctor.owner) == rt, s"Symbol ${ctor.owner.showFullName} is different from $rt")
791796
mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName)
792797
bc dup generatedType
793-
stackHeight += 2
798+
stack.push(rt)
799+
stack.push(rt)
794800
genLoadArguments(args, paramTKs(app))
795-
stackHeight -= 2
801+
stack.pop(2)
796802
genCallMethod(ctor, InvokeStyle.Special, app.span)
797803

798804
case _ =>
@@ -825,12 +831,11 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
825831
else if (app.hasAttachment(BCodeHelpers.UseInvokeSpecial)) InvokeStyle.Special
826832
else InvokeStyle.Virtual
827833

828-
val savedStackHeight = stackHeight
834+
val savedStackSize = stack.recordSize()
829835
if invokeStyle.hasInstance then
830-
genLoadQualifier(fun)
831-
stackHeight += 1
836+
stack.push(genLoadQualifier(fun))
832837
genLoadArguments(args, paramTKs(app))
833-
stackHeight = savedStackHeight
838+
stack.restoreSize(savedStackSize)
834839

835840
val DesugaredSelect(qual, name) = fun: @unchecked // fun is a Select, also checked in genLoadQualifier
836841
val isArrayClone = name == nme.clone_ && qual.tpe.widen.isInstanceOf[JavaArrayType]
@@ -888,7 +893,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
888893
bc iconst elems.length
889894
bc newarray elmKind
890895

891-
stackHeight += 3 // during the genLoad below, there is the result, its dup, and the index
896+
// during the genLoad below, there is the result, its dup, and the index
897+
stack.push(generatedType)
898+
stack.push(generatedType)
899+
stack.push(INT)
892900

893901
var i = 0
894902
var rest = elems
@@ -901,7 +909,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
901909
i = i + 1
902910
}
903911

904-
stackHeight -= 3
912+
stack.pop(3)
905913

906914
generatedType
907915
}
@@ -917,7 +925,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
917925
val (generatedType, postMatch, postMatchDest) =
918926
if dest == LoadDestination.FallThrough then
919927
val postMatch = new asm.Label
920-
(tpeTK(tree), postMatch, LoadDestination.Jump(postMatch, stackHeight))
928+
(tpeTK(tree), postMatch, LoadDestination.Jump(postMatch, stack.recordSize()))
921929
else
922930
(expectedType, null, dest)
923931

@@ -1179,7 +1187,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
11791187
}
11801188

11811189
/* Emit code to Load the qualifier of `tree` on top of the stack. */
1182-
def genLoadQualifier(tree: Tree): Unit = {
1190+
def genLoadQualifier(tree: Tree): BType = {
11831191
lineNumber(tree)
11841192
tree match {
11851193
case DesugaredSelect(qualifier, _) => genLoad(qualifier)
@@ -1188,6 +1196,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
11881196
case Some(sel) => genLoadQualifier(sel)
11891197
case None =>
11901198
assert(t.symbol.owner == this.claszSymbol)
1199+
UNIT
11911200
}
11921201
case _ => abort(s"Unknown qualifier $tree")
11931202
}
@@ -1200,14 +1209,14 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
12001209
btpes match
12011210
case btpe :: btpes1 =>
12021211
genLoad(arg, btpe)
1203-
stackHeight += btpe.size
1212+
stack.push(btpe)
12041213
loop(args1, btpes1)
12051214
case _ =>
12061215
case _ =>
12071216

1208-
val savedStackHeight = stackHeight
1217+
val savedStackSize = stack.recordSize()
12091218
loop(args, btpes)
1210-
stackHeight = savedStackHeight
1219+
stack.restoreSize(savedStackSize)
12111220
end genLoadArguments
12121221

12131222
def genLoadModule(tree: Tree): BType = {
@@ -1307,13 +1316,13 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
13071316
}.sum
13081317
bc.genNewStringBuilder(approxBuilderSize)
13091318

1310-
stackHeight += 1 // during the genLoad below, there is a reference to the StringBuilder on the stack
1319+
stack.push(jlStringBuilderRef) // during the genLoad below, there is a reference to the StringBuilder on the stack
13111320
for (elem <- concatArguments) {
13121321
val elemType = tpeTK(elem)
13131322
genLoad(elem, elemType)
13141323
bc.genStringBuilderAppend(elemType)
13151324
}
1316-
stackHeight -= 1
1325+
stack.pop()
13171326

13181327
bc.genStringBuilderEnd
13191328
} else {
@@ -1331,15 +1340,17 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
13311340
var totalArgSlots = 0
13321341
var countConcats = 1 // ie. 1 + how many times we spilled
13331342

1334-
val savedStackHeight = stackHeight
1343+
val savedStackSize = stack.recordSize()
13351344

13361345
for (elem <- concatArguments) {
13371346
val tpe = tpeTK(elem)
13381347
val elemSlots = tpe.size
13391348

13401349
// Unlikely spill case
13411350
if (totalArgSlots + elemSlots >= MaxIndySlots) {
1342-
stackHeight = savedStackHeight + countConcats
1351+
stack.restoreSize(savedStackSize)
1352+
for _ <- 0 until countConcats do
1353+
stack.push(StringRef)
13431354
bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result())
13441355
countConcats += 1
13451356
totalArgSlots = 0
@@ -1364,10 +1375,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
13641375
val tpe = tpeTK(elem)
13651376
argTypes += tpe.toASMType
13661377
genLoad(elem, tpe)
1367-
stackHeight += 1
1378+
stack.push(tpe)
13681379
}
13691380
}
1370-
stackHeight = savedStackHeight
1381+
stack.restoreSize(savedStackSize)
13711382
bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result())
13721383

13731384
// If we spilled, generate one final concat
@@ -1562,9 +1573,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
15621573
} else {
15631574
val tk = tpeTK(l).maxType(tpeTK(r))
15641575
genLoad(l, tk)
1565-
stackHeight += tk.size
1576+
stack.push(tk)
15661577
genLoad(r, tk)
1567-
stackHeight -= tk.size
1578+
stack.pop()
15681579
genCJUMP(success, failure, op, tk, targetIfNoJump)
15691580
}
15701581
}
@@ -1679,9 +1690,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
16791690
}
16801691

16811692
genLoad(l, ObjectRef)
1682-
stackHeight += 1
1693+
stack.push(ObjectRef)
16831694
genLoad(r, ObjectRef)
1684-
stackHeight -= 1
1695+
stack.pop()
16851696
genCallMethod(equalsMethod, InvokeStyle.Static)
16861697
genCZJUMP(success, failure, Primitives.NE, BOOL, targetIfNoJump)
16871698
}
@@ -1697,9 +1708,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
16971708
} else if (isNonNullExpr(l)) {
16981709
// SI-7852 Avoid null check if L is statically non-null.
16991710
genLoad(l, ObjectRef)
1700-
stackHeight += 1
1711+
stack.push(ObjectRef)
17011712
genLoad(r, ObjectRef)
1702-
stackHeight -= 1
1713+
stack.pop()
17031714
genCallMethod(defn.Any_equals, InvokeStyle.Virtual)
17041715
genCZJUMP(success, failure, Primitives.NE, BOOL, targetIfNoJump)
17051716
} else {
@@ -1709,9 +1720,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
17091720
val lNonNull = new asm.Label
17101721

17111722
genLoad(l, ObjectRef)
1712-
stackHeight += 1
1723+
stack.push(ObjectRef)
17131724
genLoad(r, ObjectRef)
1714-
stackHeight -= 1
1725+
stack.pop()
17151726
locals.store(eqEqTempLocal)
17161727
bc dup ObjectRef
17171728
genCZJUMP(lNull, lNonNull, Primitives.EQ, ObjectRef, targetIfNoJump = lNull)

0 commit comments

Comments
 (0)