Skip to content

Commit 4a2889f

Browse files
committed
Use explicit destinations in codegen to avoid uselessly jumping around.
Previously, the codegen's main method `genLoad` always generated code that loaded the value on the stack before continuing. There were a number of situations where `genLoad` would be directly followed by unconditional jumps to instructions performing more jumps, returns and throws. This generated more spurious jumps than necessary, along with artifact dead code. We solve these limitations by introducing `LoadDestination`s that specify the destination of a loaded value: * FallThrough: as previously, load the value on the stack and continue. * Jump(label): load the value on the stack and jump to the given label. * Return: return the value from the enclosing method. * Throw: throw the value. We generalize `genLoad` as `genLoadTo`, taking a specific destination for the loaded value. `genLoadTo` can "push down" its destination into all control flow structures (except `Try`s, because of their cleanups). With that, when we get to the end of what amounts to "basic blocks", we know exactly the ultimate destination of the loaded value. We can therefore directly jump, return or throw to the final destination. This produces less bytecode, notably because fewer labels are necessary. For example, the method: def abs(x: Int): Int = if x < 0 then -x else x previously generated bytecode like ILOAD 1 ICONST_0 IF_ICMPGE Label(1) ILOAD 1 INEG GOTO Label(2) Label(1): ILOAD 1 Label(2): IRETURN Now, instead of jumping to Label(2), we directly perform an IRETURN: ILOAD 1 ICONST_0 IF_ICMPGE Label(1) ILOAD 1 INEG IRETURN Label(1): ILOAD 1 IRETURN While the changes are not very impressive on that simple example, they become more important in more complex cases, notably with pattern matching. Examples can be found in the changed bytecode tests. An added benefit is that `genLoadTo` knows when loading a value results in an unconditional control flow change (jump, return or throw). It can then avoid inserting any useless adaptation. This removes all the dead bytecode that the codegen used to generate as artifacts of its own compilation scheme. (It will still generate dead bytecode if the original source code/inlined code contains dead code.)
1 parent f50f72f commit 4a2889f

File tree

4 files changed

+192
-201
lines changed

4 files changed

+192
-201
lines changed

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

Lines changed: 126 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,6 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
113113
}
114114
}
115115

116-
def genThrow(expr: Tree): Unit = {
117-
val thrownKind = tpeTK(expr)
118-
// `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable.
119-
// Similarly for scala.Nothing (again, as defined in src/libray-aux).
120-
assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference))
121-
genLoad(expr, thrownKind)
122-
lineNumber(expr)
123-
emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level.
124-
}
125-
126116
/* Generate code for primitive arithmetic operations. */
127117
def genArithmeticOp(tree: Tree, code: Int): BType = tree match{
128118
case Apply(fun @ DesugaredSelect(larg, _), args) =>
@@ -211,7 +201,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
211201
generatedType
212202
}
213203

214-
def genLoadIf(tree: If, expectedType: BType): BType = tree match{
204+
def genLoadIfTo(tree: If, expectedType: BType, dest: LoadDestination): BType = tree match{
215205
case If(condp, thenp, elsep) =>
216206

217207
val success = new asm.Label
@@ -221,25 +211,37 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
221211
case Literal(value) if value.tag == UnitTag => false
222212
case _ => true
223213
})
224-
val postIf = if (hasElse) new asm.Label else failure
225214

226215
genCond(condp, success, failure, targetIfNoJump = success)
227216
markProgramPoint(success)
228217

229-
val thenKind = tpeTK(thenp)
230-
val elseKind = if (!hasElse) UNIT else tpeTK(elsep)
231-
def hasUnitBranch = (thenKind == UNIT || elseKind == UNIT) && expectedType == UNIT
232-
val resKind = if (hasUnitBranch) UNIT else tpeTK(tree)
233-
234-
genLoad(thenp, resKind)
235-
if (hasElse) { bc goTo postIf }
236-
markProgramPoint(failure)
237-
if (hasElse) {
238-
genLoad(elsep, resKind)
239-
markProgramPoint(postIf)
240-
}
241-
242-
resKind
218+
if dest == LoadDestination.FallThrough then
219+
if hasElse then
220+
val thenKind = tpeTK(thenp)
221+
val elseKind = tpeTK(elsep)
222+
def hasUnitBranch = (thenKind == UNIT || elseKind == UNIT) && expectedType == UNIT
223+
val resKind = if (hasUnitBranch) UNIT else tpeTK(tree)
224+
225+
val postIf = new asm.Label
226+
genLoadTo(thenp, resKind, LoadDestination.Jump(postIf))
227+
markProgramPoint(failure)
228+
genLoadTo(elsep, resKind, LoadDestination.FallThrough)
229+
markProgramPoint(postIf)
230+
resKind
231+
else
232+
genLoad(thenp, UNIT)
233+
markProgramPoint(failure)
234+
UNIT
235+
end if
236+
else
237+
genLoadTo(thenp, expectedType, dest)
238+
markProgramPoint(failure)
239+
if hasElse then
240+
genLoadTo(elsep, expectedType, dest)
241+
else
242+
genAdaptAndSendToDest(UNIT, expectedType, dest)
243+
expectedType
244+
end if
243245
}
244246

245247
def genPrimitiveOp(tree: Apply, expectedType: BType): BType = (tree: @unchecked) match {
@@ -285,8 +287,13 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
285287
}
286288

287289
/* Generate code for trees that produce values on the stack */
288-
def genLoad(tree: Tree, expectedType: BType): Unit = {
290+
def genLoad(tree: Tree, expectedType: BType): Unit =
291+
genLoadTo(tree, expectedType, LoadDestination.FallThrough)
292+
293+
/* Generate code for trees that produce values, sent to a given `LoadDestination`. */
294+
def genLoadTo(tree: Tree, expectedType: BType, dest: LoadDestination): Unit =
289295
var generatedType = expectedType
296+
var generatedDest = LoadDestination.FallThrough
290297

291298
lineNumber(tree)
292299

@@ -307,24 +314,29 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
307314
generatedType = UNIT
308315

309316
case t @ If(_, _, _) =>
310-
generatedType = genLoadIf(t, expectedType)
317+
generatedType = genLoadIfTo(t, expectedType, dest)
318+
generatedDest = dest
311319

312320
case t @ Labeled(_, _) =>
313-
generatedType = genLabeled(t)
321+
generatedType = genLabeledTo(t, expectedType, dest)
322+
generatedDest = dest
314323

315324
case r: Return =>
316325
genReturn(r)
317-
generatedType = expectedType
326+
generatedDest = LoadDestination.Return
318327

319328
case t @ WhileDo(_, _) =>
320-
generatedType = genWhileDo(t, expectedType)
329+
generatedDest = genWhileDo(t)
330+
generatedType = UNIT
321331

322332
case t @ Try(_, _, _) =>
323333
generatedType = genLoadTry(t)
324334

325335
case t: Apply if t.fun.symbol eq defn.throwMethod =>
326-
genThrow(t.args.head)
327-
generatedType = expectedType
336+
val thrownExpr = t.args.head
337+
val thrownKind = tpeTK(thrownExpr)
338+
genLoadTo(thrownExpr, thrownKind, LoadDestination.Throw)
339+
generatedDest = LoadDestination.Throw
328340

329341
case New(tpt) =>
330342
abort(s"Unexpected New(${tpt.tpe.showSummary()}/$tpt) reached GenBCode.\n" +
@@ -425,12 +437,18 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
425437

426438
case blck @ Block(stats, expr) =>
427439
if(stats.isEmpty)
428-
genLoad(expr, expectedType)
429-
else genBlock(blck, expectedType)
440+
genLoadTo(expr, expectedType, dest)
441+
else
442+
genBlockTo(blck, expectedType, dest)
443+
generatedDest = dest
430444

431-
case Typed(Super(_, _), _) => genLoad(tpd.This(claszSymbol.asClass), expectedType)
445+
case Typed(Super(_, _), _) =>
446+
genLoadTo(tpd.This(claszSymbol.asClass), expectedType, dest)
447+
generatedDest = dest
432448

433-
case Typed(expr, _) => genLoad(expr, expectedType)
449+
case Typed(expr, _) =>
450+
genLoadTo(expr, expectedType, dest)
451+
generatedDest = dest
434452

435453
case Assign(_, _) =>
436454
generatedType = UNIT
@@ -440,7 +458,8 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
440458
generatedType = genArrayValue(av)
441459

442460
case mtch @ Match(_, _) =>
443-
generatedType = genMatch(mtch)
461+
generatedType = genMatchTo(mtch, expectedType, dest)
462+
generatedDest = dest
444463

445464
case tpd.EmptyTree => if (expectedType != UNIT) { emitZeroOf(expectedType) }
446465

@@ -451,12 +470,29 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
451470
case _ => abort(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.span}")
452471
}
453472

454-
// emit conversion
455-
if (generatedType != expectedType) {
473+
// emit conversion and send to the right destination
474+
if generatedDest == LoadDestination.FallThrough then
475+
genAdaptAndSendToDest(generatedType, expectedType, dest)
476+
end genLoadTo
477+
478+
def genAdaptAndSendToDest(generatedType: BType, expectedType: BType, dest: LoadDestination): Unit =
479+
if generatedType != expectedType then
456480
adapt(generatedType, expectedType)
457-
}
458481

459-
} // end of GenBCode.genLoad()
482+
dest match
483+
case LoadDestination.FallThrough =>
484+
()
485+
case LoadDestination.Jump(label) =>
486+
bc goTo label
487+
case LoadDestination.Return =>
488+
bc emitRETURN returnType
489+
case LoadDestination.Throw =>
490+
val thrownType = expectedType
491+
// `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable.
492+
// Similarly for scala.Nothing (again, as defined in src/libray-aux).
493+
assert(thrownType.isNullType || thrownType.isNothingType || thrownType.asClassBType.isSubtypeOf(ThrowableReference))
494+
emit(asm.Opcodes.ATHROW)
495+
end genAdaptAndSendToDest
460496

461497
// ---------------- field load and store ----------------
462498

@@ -533,13 +569,23 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
533569
}
534570
}
535571

536-
private def genLabeled(tree: Labeled): BType = tree match {
572+
private def genLabeledTo(tree: Labeled, expectedType: BType, dest: LoadDestination): BType = tree match {
537573
case Labeled(bind, expr) =>
538574

539-
val resKind = tpeTK(tree)
540-
genLoad(expr, resKind)
541-
markProgramPoint(programPoint(bind.symbol))
542-
resKind
575+
val labelSym = bind.symbol
576+
577+
if dest == LoadDestination.FallThrough then
578+
val resKind = tpeTK(tree)
579+
val jumpTarget = new asm.Label
580+
registerJumpDest(labelSym, resKind, LoadDestination.Jump(jumpTarget))
581+
genLoad(expr, resKind)
582+
markProgramPoint(jumpTarget)
583+
resKind
584+
else
585+
registerJumpDest(labelSym, expectedType, dest)
586+
genLoadTo(expr, expectedType, dest)
587+
expectedType
588+
end if
543589
}
544590

545591
private def genReturn(r: Return): Unit = {
@@ -548,17 +594,14 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
548594

549595
if (NoSymbol == fromSym) {
550596
// return from enclosing method
551-
val returnedKind = tpeTK(expr)
552-
genLoad(expr, returnedKind)
553-
adapt(returnedKind, returnType)
554-
val saveReturnValue = (returnType != UNIT)
555-
lineNumber(r)
556-
557597
cleanups match {
558598
case Nil =>
559599
// not an assertion: !shouldEmitCleanup (at least not yet, pendingCleanups() may still have to run, and reset `shouldEmitCleanup`.
560-
bc emitRETURN returnType
600+
genLoadTo(expr, returnType, LoadDestination.Return)
561601
case nextCleanup :: rest =>
602+
genLoad(expr, returnType)
603+
lineNumber(r)
604+
val saveReturnValue = (returnType != UNIT)
562605
if (saveReturnValue) {
563606
// regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
564607
if (earlyReturnVar == null) {
@@ -578,54 +621,39 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
578621
* that cross cleanup boundaries. However, in theory such crossings are valid, so we should take care
579622
* of them.
580623
*/
581-
val resultKind = toTypeKind(fromSym.info)
582-
genLoad(expr, resultKind)
583-
lineNumber(r)
584-
bc goTo programPoint(fromSym)
624+
val (exprExpectedType, exprDest) = findJumpDest(fromSym)
625+
genLoadTo(expr, exprExpectedType, exprDest)
585626
}
586627
} // end of genReturn()
587628

588-
def genWhileDo(tree: WhileDo, expectedType: BType): BType = tree match{
629+
def genWhileDo(tree: WhileDo): LoadDestination = tree match{
589630
case WhileDo(cond, body) =>
590631

591632
val isInfinite = cond == tpd.EmptyTree
592633

634+
val loop = new asm.Label
635+
markProgramPoint(loop)
636+
593637
if isInfinite then
594-
body match
595-
case Labeled(bind, expr) if tpeTK(body) == UNIT =>
596-
// this is the shape of tailrec methods
597-
val loop = programPoint(bind.symbol)
598-
markProgramPoint(loop)
599-
genLoad(expr, UNIT)
600-
bc goTo loop
601-
case _ =>
602-
val loop = new asm.Label
603-
markProgramPoint(loop)
604-
genLoad(body, UNIT)
605-
bc goTo loop
606-
end match
607-
expectedType
638+
val dest = LoadDestination.Jump(loop)
639+
genLoadTo(body, UNIT, dest)
640+
dest
608641
else
609642
body match
610643
case Literal(value) if value.tag == UnitTag =>
611644
// this is the shape of do..while loops
612-
val loop = new asm.Label
613-
markProgramPoint(loop)
614645
val exitLoop = new asm.Label
615646
genCond(cond, loop, exitLoop, targetIfNoJump = exitLoop)
616647
markProgramPoint(exitLoop)
617648
case _ =>
618-
val loop = new asm.Label
619649
val success = new asm.Label
620650
val failure = new asm.Label
621-
markProgramPoint(loop)
622651
genCond(cond, success, failure, targetIfNoJump = success)
623652
markProgramPoint(success)
624-
genLoad(body, UNIT)
625-
bc goTo loop
653+
genLoadTo(body, UNIT, LoadDestination.Jump(loop))
626654
markProgramPoint(failure)
627655
end match
628-
UNIT
656+
LoadDestination.FallThrough
629657
}
630658

631659
def genTypeApply(t: TypeApply): BType = (t: @unchecked) match {
@@ -848,11 +876,16 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
848876
* Int/String values to use as keys, and a code block. The exception is the "default" case
849877
* clause which doesn't list any key (there is exactly one of these per match).
850878
*/
851-
private def genMatch(tree: Match): BType = tree match {
879+
private def genMatchTo(tree: Match, expectedType: BType, dest: LoadDestination): BType = tree match {
852880
case Match(selector, cases) =>
853881
lineNumber(tree)
854-
val generatedType = tpeTK(tree)
855-
val postMatch = new asm.Label
882+
883+
val (generatedType, postMatch, postMatchDest) =
884+
if dest == LoadDestination.FallThrough then
885+
val postMatch = new asm.Label
886+
(tpeTK(tree), postMatch, LoadDestination.Jump(postMatch))
887+
else
888+
(expectedType, null, dest)
856889

857890
// Only two possible selector types exist in `Match` trees at this point: Int and String
858891
if (tpeTK(selector) == INT) {
@@ -902,8 +935,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
902935
for (sb <- switchBlocks.reverse) {
903936
val (caseLabel, caseBody) = sb
904937
markProgramPoint(caseLabel)
905-
genLoad(caseBody, generatedType)
906-
bc goTo postMatch
938+
genLoadTo(caseBody, generatedType, postMatchDest)
907939
}
908940
} else {
909941

@@ -968,13 +1000,14 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
9681000
}
9691001

9701002
// Push the hashCode of the string (or `0` it is `null`) onto the stack and switch on it
971-
genLoadIf(
1003+
genLoadIfTo(
9721004
If(
9731005
tree.selector.select(defn.Any_==).appliedTo(nullLiteral),
9741006
Literal(Constant(0)),
9751007
tree.selector.select(defn.Any_hashCode).appliedToNone
9761008
),
977-
INT
1009+
INT,
1010+
LoadDestination.FallThrough
9781011
)
9791012
bc.emitSWITCH(mkArrayReverse(flatKeys), mkArrayL(targets.reverse), default, MIN_SWITCH_DENSITY)
9801013

@@ -993,8 +1026,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
9931026
val thisCaseMatches = new asm.Label
9941027
genCond(condp, thisCaseMatches, keepGoing, targetIfNoJump = thisCaseMatches)
9951028
markProgramPoint(thisCaseMatches)
996-
genLoad(caseBody, generatedType)
997-
bc goTo postMatch
1029+
genLoadTo(caseBody, generatedType, postMatchDest)
9981030
}
9991031
markProgramPoint(keepGoing)
10001032
}
@@ -1004,22 +1036,22 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
10041036
// emit blocks for common patterns
10051037
for ((caseLabel, caseBody) <- indirectBlocks.reverse) {
10061038
markProgramPoint(caseLabel)
1007-
genLoad(caseBody, generatedType)
1008-
bc goTo postMatch
1039+
genLoadTo(caseBody, generatedType, postMatchDest)
10091040
}
10101041
}
10111042

1012-
markProgramPoint(postMatch)
1043+
if postMatch != null then
1044+
markProgramPoint(postMatch)
10131045
generatedType
10141046
}
10151047

1016-
def genBlock(tree: Block, expectedType: BType) = tree match {
1048+
def genBlockTo(tree: Block, expectedType: BType, dest: LoadDestination): Unit = tree match {
10171049
case Block(stats, expr) =>
10181050

10191051
val savedScope = varsInScope
10201052
varsInScope = Nil
10211053
stats foreach genStat
1022-
genLoad(expr, expectedType)
1054+
genLoadTo(expr, expectedType, dest)
10231055
val end = currProgramPoint()
10241056
if (emitVars) {
10251057
// add entries to LocalVariableTable JVM attribute

0 commit comments

Comments
 (0)