Skip to content

Commit f70c77e

Browse files
committed
Generate leaner code for branches
GenBCode used to generate more bytecode for branching instructions than GenASM. A simple method def f(x: Int, b: Boolean) = if (b) 1 else 2 would generate ILOAD 2 IFNE L1 GOTO L2 L1 ICONST_1 GOTO L3 L2 ICONST_2 L3 IRETURN If the conditional branch is negated (IFEQ) the GOTO is unnecessary. While -Yopt:l:method would clean this up, it's also not too hard to generate the leaner bytecode in the first place.
1 parent 7c25282 commit f70c77e

File tree

3 files changed

+173
-91
lines changed

3 files changed

+173
-91
lines changed

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

Lines changed: 85 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
202202
val hasElse = !elsep.isEmpty
203203
val postIf = if (hasElse) new asm.Label else failure
204204

205-
genCond(condp, success, failure)
205+
genCond(condp, success, failure, targetIfNoJump = success)
206+
markProgramPoint(success)
206207

207208
val thenKind = tpeTK(thenp)
208209
val elseKind = if (!hasElse) UNIT else tpeTK(elsep)
209210
def hasUnitBranch = (thenKind == UNIT || elseKind == UNIT)
210211
val resKind = if (hasUnitBranch) UNIT else tpeTK(tree)
211212

212-
markProgramPoint(success)
213213
genLoad(thenp, resKind)
214214
if (hasElse) { bc goTo postIf }
215215
markProgramPoint(failure)
@@ -234,14 +234,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
234234
else if (isArrayOp(code)) genArrayOp(tree, code, expectedType)
235235
else if (isLogicalOp(code) || isComparisonOp(code)) {
236236
val success, failure, after = new asm.Label
237-
genCond(tree, success, failure)
237+
genCond(tree, success, failure, targetIfNoJump = success)
238238
// success block
239-
markProgramPoint(success)
240-
bc boolconst true
241-
bc goTo after
239+
markProgramPoint(success)
240+
bc boolconst true
241+
bc goTo after
242242
// failure block
243-
markProgramPoint(failure)
244-
bc boolconst false
243+
markProgramPoint(failure)
244+
bc boolconst false
245245
// after
246246
markProgramPoint(after)
247247

@@ -1108,53 +1108,58 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
11081108
}
11091109

11101110
/* Emit code to compare the two top-most stack values using the 'op' operator. */
1111-
private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) {
1112-
if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
1113-
bc.emitIF_ICMP(op, success)
1114-
} else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
1115-
bc.emitIF_ACMP(op, success)
1116-
} else {
1117-
(tk: @unchecked) match {
1118-
case LONG => emit(asm.Opcodes.LCMP)
1119-
case FLOAT =>
1120-
if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG)
1121-
else emit(asm.Opcodes.FCMPL)
1122-
case DOUBLE =>
1123-
if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG)
1124-
else emit(asm.Opcodes.DCMPL)
1111+
private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label) {
1112+
if (targetIfNoJump == success) genCJUMP(failure, success, op.negate, tk, targetIfNoJump)
1113+
else {
1114+
if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
1115+
bc.emitIF_ICMP(op, success)
1116+
} else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
1117+
bc.emitIF_ACMP(op, success)
1118+
} else {
1119+
(tk: @unchecked) match {
1120+
case LONG => emit(asm.Opcodes.LCMP)
1121+
case FLOAT =>
1122+
if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG)
1123+
else emit(asm.Opcodes.FCMPL)
1124+
case DOUBLE =>
1125+
if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG)
1126+
else emit(asm.Opcodes.DCMPL)
1127+
}
1128+
bc.emitIF(op, success)
11251129
}
1126-
bc.emitIF(op, success)
1130+
if (targetIfNoJump != failure) bc goTo failure
11271131
}
1128-
bc goTo failure
11291132
}
11301133

11311134
/* Emits code to compare (and consume) stack-top and zero using the 'op' operator */
1132-
private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) {
1133-
if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
1134-
bc.emitIF(op, success)
1135-
} else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
1136-
// @unchecked because references aren't compared with GT, GE, LT, LE.
1137-
(op : @unchecked) match {
1138-
case TestOp.EQ => bc emitIFNULL success
1139-
case TestOp.NE => bc emitIFNONNULL success
1140-
}
1141-
} else {
1142-
(tk: @unchecked) match {
1143-
case LONG =>
1144-
emit(asm.Opcodes.LCONST_0)
1145-
emit(asm.Opcodes.LCMP)
1146-
case FLOAT =>
1147-
emit(asm.Opcodes.FCONST_0)
1148-
if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG)
1149-
else emit(asm.Opcodes.FCMPL)
1150-
case DOUBLE =>
1151-
emit(asm.Opcodes.DCONST_0)
1152-
if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG)
1153-
else emit(asm.Opcodes.DCMPL)
1135+
private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label) {
1136+
if (targetIfNoJump == success) genCZJUMP(failure, success, op.negate, tk, targetIfNoJump)
1137+
else {
1138+
if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
1139+
bc.emitIF(op, success)
1140+
} else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
1141+
op match { // references are only compared with EQ and NE
1142+
case TestOp.EQ => bc emitIFNULL success
1143+
case TestOp.NE => bc emitIFNONNULL success
1144+
}
1145+
} else {
1146+
(tk: @unchecked) match {
1147+
case LONG =>
1148+
emit(asm.Opcodes.LCONST_0)
1149+
emit(asm.Opcodes.LCMP)
1150+
case FLOAT =>
1151+
emit(asm.Opcodes.FCONST_0)
1152+
if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG)
1153+
else emit(asm.Opcodes.FCMPL)
1154+
case DOUBLE =>
1155+
emit(asm.Opcodes.DCONST_0)
1156+
if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG)
1157+
else emit(asm.Opcodes.DCMPL)
1158+
}
1159+
bc.emitIF(op, success)
11541160
}
1155-
bc.emitIF(op, success)
1161+
if (targetIfNoJump != failure) bc goTo failure
11561162
}
1157-
bc goTo failure
11581163
}
11591164

11601165
def testOpForPrimitive(primitiveCode: Int) = (primitiveCode: @switch) match {
@@ -1179,29 +1184,26 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
11791184
* Generate code for conditional expressions.
11801185
* The jump targets success/failure of the test are `then-target` and `else-target` resp.
11811186
*/
1182-
private def genCond(tree: Tree, success: asm.Label, failure: asm.Label) {
1187+
private def genCond(tree: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label) {
11831188

11841189
def genComparisonOp(l: Tree, r: Tree, code: Int) {
1185-
val op: TestOp = testOpForPrimitive(code)
1186-
// special-case reference (in)equality test for null (null eq x, x eq null)
1187-
var nonNullSide: Tree = null
1188-
if (scalaPrimitives.isReferenceEqualityOp(code) &&
1189-
{ nonNullSide = ifOneIsNull(l, r); nonNullSide != null }
1190-
) {
1190+
val op = testOpForPrimitive(code)
1191+
val nonNullSide = if (scalaPrimitives.isReferenceEqualityOp(code)) ifOneIsNull(l, r) else null
1192+
if (nonNullSide != null) {
1193+
// special-case reference (in)equality test for null (null eq x, x eq null)
11911194
genLoad(nonNullSide, ObjectRef)
1192-
genCZJUMP(success, failure, op, ObjectRef)
1193-
}
1194-
else {
1195+
genCZJUMP(success, failure, op, ObjectRef, targetIfNoJump)
1196+
} else {
11951197
val tk = tpeTK(l).maxType(tpeTK(r))
11961198
genLoad(l, tk)
11971199
genLoad(r, tk)
1198-
genCJUMP(success, failure, op, tk)
1200+
genCJUMP(success, failure, op, tk, targetIfNoJump)
11991201
}
12001202
}
12011203

1202-
def default() = {
1204+
def loadAndTestBoolean() = {
12031205
genLoad(tree, BOOL)
1204-
genCZJUMP(success, failure, TestOp.NE, BOOL)
1206+
genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
12051207
}
12061208

12071209
lineNumber(tree)
@@ -1212,37 +1214,35 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
12121214

12131215
// lhs and rhs of test
12141216
lazy val Select(lhs, _) = fun
1215-
val rhs = if (args.isEmpty) EmptyTree else args.head; // args.isEmpty only for ZNOT
1217+
val rhs = if (args.isEmpty) EmptyTree else args.head // args.isEmpty only for ZNOT
12161218

1217-
def genZandOrZor(and: Boolean) { // TODO WRONG
1219+
def genZandOrZor(and: Boolean) {
12181220
// reaching "keepGoing" indicates the rhs should be evaluated too (ie not short-circuited).
12191221
val keepGoing = new asm.Label
12201222

1221-
if (and) genCond(lhs, keepGoing, failure)
1222-
else genCond(lhs, success, keepGoing)
1223+
if (and) genCond(lhs, keepGoing, failure, targetIfNoJump = keepGoing)
1224+
else genCond(lhs, success, keepGoing, targetIfNoJump = keepGoing)
12231225

12241226
markProgramPoint(keepGoing)
1225-
genCond(rhs, success, failure)
1227+
genCond(rhs, success, failure, targetIfNoJump)
12261228
}
12271229

12281230
getPrimitive(fun.symbol) match {
1229-
case ZNOT => genCond(lhs, failure, success)
1231+
case ZNOT => genCond(lhs, failure, success, targetIfNoJump)
12301232
case ZAND => genZandOrZor(and = true)
12311233
case ZOR => genZandOrZor(and = false)
12321234
case code =>
1233-
// TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null)
12341235
if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).isClass) {
1235-
// `lhs` has reference type
1236-
if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, tree.pos)
1237-
else genEqEqPrimitive(lhs, rhs, failure, success, tree.pos)
1238-
}
1239-
else if (scalaPrimitives.isComparisonOp(code))
1236+
// rewrite `==` to null tests and `equals`. not needed for arrays (`equals` is reference equality).
1237+
if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, targetIfNoJump, tree.pos)
1238+
else genEqEqPrimitive(lhs, rhs, failure, success, targetIfNoJump, tree.pos)
1239+
} else if (scalaPrimitives.isComparisonOp(code)) {
12401240
genComparisonOp(lhs, rhs, code)
1241-
else
1242-
default
1241+
} else
1242+
loadAndTestBoolean()
12431243
}
12441244

1245-
case _ => default
1245+
case _ => loadAndTestBoolean()
12461246
}
12471247

12481248
} // end of genCond()
@@ -1254,7 +1254,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
12541254
* @param l left-hand-side of the '=='
12551255
* @param r right-hand-side of the '=='
12561256
*/
1257-
def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, pos: Position) {
1257+
def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label, pos: Position) {
12581258

12591259
/* True if the equality comparison is between values that require the use of the rich equality
12601260
* comparator (scala.runtime.Comparator.equals). This is the case when either side of the
@@ -1264,7 +1264,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
12641264
*/
12651265
val mustUseAnyComparator: Boolean = {
12661266
val areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe)
1267-
12681267
!areSameFinals && platform.isMaybeBoxed(l.tpe.typeSymbol) && platform.isMaybeBoxed(r.tpe.typeSymbol)
12691268
}
12701269

@@ -1279,23 +1278,22 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
12791278
genLoad(l, ObjectRef)
12801279
genLoad(r, ObjectRef)
12811280
genCallMethod(equalsMethod, InvokeStyle.Static, pos)
1282-
genCZJUMP(success, failure, TestOp.NE, BOOL)
1283-
}
1284-
else {
1281+
genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
1282+
} else {
12851283
if (isNull(l)) {
12861284
// null == expr -> expr eq null
12871285
genLoad(r, ObjectRef)
1288-
genCZJUMP(success, failure, TestOp.EQ, ObjectRef)
1286+
genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump)
12891287
} else if (isNull(r)) {
12901288
// expr == null -> expr eq null
12911289
genLoad(l, ObjectRef)
1292-
genCZJUMP(success, failure, TestOp.EQ, ObjectRef)
1290+
genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump)
12931291
} else if (isNonNullExpr(l)) {
12941292
// SI-7852 Avoid null check if L is statically non-null.
12951293
genLoad(l, ObjectRef)
12961294
genLoad(r, ObjectRef)
12971295
genCallMethod(Object_equals, InvokeStyle.Virtual, pos)
1298-
genCZJUMP(success, failure, TestOp.NE, BOOL)
1296+
genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
12991297
} else {
13001298
// l == r -> if (l eq null) r eq null else l.equals(r)
13011299
val eqEqTempLocal = locals.makeLocal(ObjectRef, nme.EQEQ_LOCAL_VAR.toString)
@@ -1306,17 +1304,17 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
13061304
genLoad(r, ObjectRef)
13071305
locals.store(eqEqTempLocal)
13081306
bc dup ObjectRef
1309-
genCZJUMP(lNull, lNonNull, TestOp.EQ, ObjectRef)
1307+
genCZJUMP(lNull, lNonNull, TestOp.EQ, ObjectRef, targetIfNoJump = lNull)
13101308

13111309
markProgramPoint(lNull)
13121310
bc drop ObjectRef
13131311
locals.load(eqEqTempLocal)
1314-
genCZJUMP(success, failure, TestOp.EQ, ObjectRef)
1312+
genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump = lNonNull)
13151313

13161314
markProgramPoint(lNonNull)
13171315
locals.load(eqEqTempLocal)
13181316
genCallMethod(Object_equals, InvokeStyle.Virtual, pos)
1319-
genCZJUMP(success, failure, TestOp.NE, BOOL)
1317+
genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
13201318
}
13211319
}
13221320
}

src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,15 @@ object BCodeHelpers {
13551355
}
13561356

13571357
class TestOp(val op: Int) extends AnyVal {
1358+
import TestOp._
1359+
def negate = this match {
1360+
case EQ => NE
1361+
case NE => EQ
1362+
case LT => GE
1363+
case GE => LT
1364+
case GT => LE
1365+
case LE => GT
1366+
}
13581367
def opcodeIF = asm.Opcodes.IFEQ + op
13591368
def opcodeIFICMP = asm.Opcodes.IF_ICMPEQ + op
13601369
}

0 commit comments

Comments
 (0)