Skip to content

Commit f50f72f

Browse files
committed
Add bytecode tests with the status quo of codegen control flow.
1 parent 73df472 commit f50f72f

File tree

1 file changed

+377
-0
lines changed

1 file changed

+377
-0
lines changed

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

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,383 @@ class TestBCode extends DottyBytecodeTest {
10391039
}
10401040
}
10411041

1042+
@Test def patmatControlFlow(): Unit = {
1043+
val source =
1044+
s"""class Foo {
1045+
| def m1(xs: List[Int]): Int = xs match
1046+
| case x :: xr => x
1047+
| case Nil => 20
1048+
|
1049+
| def m2(xs: List[Int]): Int = xs match
1050+
| case (1 | 2) :: xr => 10
1051+
| case x :: xr => x
1052+
| case _ => 20
1053+
|}
1054+
""".stripMargin
1055+
1056+
checkBCode(source) { dir =>
1057+
val fooClass = loadClassNode(dir.lookupName("Foo.class", directory = false).input)
1058+
1059+
// ---------------
1060+
1061+
val m1Meth = getMethod(fooClass, "m1")
1062+
1063+
assertSameCode(m1Meth, List(
1064+
VarOp(ALOAD, 1),
1065+
VarOp(ASTORE, 2),
1066+
VarOp(ALOAD, 2),
1067+
TypeOp(INSTANCEOF, "scala/collection/immutable/$colon$colon"),
1068+
Jump(IFEQ, Label(19)),
1069+
VarOp(ALOAD, 2),
1070+
TypeOp(CHECKCAST, "scala/collection/immutable/$colon$colon"),
1071+
VarOp(ASTORE, 3),
1072+
VarOp(ALOAD, 3),
1073+
Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "next$access$1", "()Lscala/collection/immutable/List;", false),
1074+
VarOp(ASTORE, 4),
1075+
VarOp(ALOAD, 3),
1076+
Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "head", "()Ljava/lang/Object;", false),
1077+
Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false),
1078+
VarOp(ISTORE, 5),
1079+
VarOp(ALOAD, 4),
1080+
VarOp(ASTORE, 6),
1081+
VarOp(ILOAD, 5),
1082+
Jump(GOTO, Label(47)),
1083+
Label(19),
1084+
Field(GETSTATIC, "scala/package$", "MODULE$", "Lscala/package$;"),
1085+
Invoke(INVOKEVIRTUAL, "scala/package$", "Nil", "()Lscala/collection/immutable/Nil$;", false),
1086+
VarOp(ALOAD, 2),
1087+
VarOp(ASTORE, 7),
1088+
Op(DUP),
1089+
Jump(IFNONNULL, Label(31)),
1090+
Op(POP),
1091+
VarOp(ALOAD, 7),
1092+
Jump(IFNULL, Label(36)),
1093+
Jump(GOTO, Label(40)),
1094+
Label(31),
1095+
VarOp(ALOAD, 7),
1096+
Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false),
1097+
Jump(IFEQ, Label(40)),
1098+
Label(36),
1099+
IntOp(BIPUSH, 20),
1100+
Jump(GOTO, Label(47)),
1101+
Label(40),
1102+
TypeOp(NEW, "scala/MatchError"),
1103+
Op(DUP),
1104+
VarOp(ALOAD, 2),
1105+
Invoke(INVOKESPECIAL, "scala/MatchError", "<init>", "(Ljava/lang/Object;)V", false),
1106+
Op(ATHROW),
1107+
Label(47),
1108+
Op(IRETURN),
1109+
))
1110+
1111+
// ---------------
1112+
1113+
val m2Meth = getMethod(fooClass, "m2")
1114+
1115+
assertSameCode(m2Meth, List(
1116+
VarOp(ALOAD, 1),
1117+
VarOp(ASTORE, 2),
1118+
VarOp(ALOAD, 2),
1119+
TypeOp(INSTANCEOF, "scala/collection/immutable/$colon$colon"),
1120+
Jump(IFEQ, Label(42)),
1121+
VarOp(ALOAD, 2),
1122+
TypeOp(CHECKCAST, "scala/collection/immutable/$colon$colon"),
1123+
VarOp(ASTORE, 3),
1124+
VarOp(ALOAD, 3),
1125+
Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "head", "()Ljava/lang/Object;", false),
1126+
Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false),
1127+
VarOp(ISTORE, 4),
1128+
VarOp(ALOAD, 3),
1129+
Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "next$access$1", "()Lscala/collection/immutable/List;", false),
1130+
VarOp(ASTORE, 5),
1131+
Op(ICONST_1),
1132+
VarOp(ILOAD, 4),
1133+
Jump(IF_ICMPNE, Label(19)),
1134+
Jump(GOTO, Label(28)),
1135+
Label(19),
1136+
Op(ICONST_2),
1137+
VarOp(ILOAD, 4),
1138+
Jump(IF_ICMPNE, Label(25)),
1139+
Jump(GOTO, Label(28)),
1140+
Label(25),
1141+
Jump(GOTO, Label(34)),
1142+
Label(28),
1143+
VarOp(ALOAD, 5),
1144+
VarOp(ASTORE, 6),
1145+
IntOp(BIPUSH, 10),
1146+
Jump(GOTO, Label(46)),
1147+
Label(34),
1148+
VarOp(ILOAD, 4),
1149+
VarOp(ISTORE, 7),
1150+
VarOp(ALOAD, 5),
1151+
VarOp(ASTORE, 8),
1152+
VarOp(ILOAD, 7),
1153+
Jump(GOTO, Label(46)),
1154+
Label(42),
1155+
IntOp(BIPUSH, 20),
1156+
Jump(GOTO, Label(46)),
1157+
Label(46),
1158+
Op(IRETURN),
1159+
))
1160+
}
1161+
}
1162+
1163+
@Test def switchControlFlow(): Unit = {
1164+
val source =
1165+
s"""import scala.annotation.switch
1166+
|
1167+
|class Foo {
1168+
| def m1(x: Int): Int = (x: @switch) match
1169+
| case 1 => 10
1170+
| case 7 => 20
1171+
| case 8 => 30
1172+
| case 9 => 40
1173+
| case _ => x
1174+
|
1175+
| def m2(x: Int): Int = (x: @switch) match
1176+
| case (1 | 2) => 10
1177+
| case 7 => 20
1178+
| case 8 => 30
1179+
| case c if c > 100 => 20
1180+
|}
1181+
""".stripMargin
1182+
1183+
checkBCode(source) { dir =>
1184+
val fooClass = loadClassNode(dir.lookupName("Foo.class", directory = false).input)
1185+
1186+
// ---------------
1187+
1188+
val m1Meth = getMethod(fooClass, "m1")
1189+
1190+
assertSameCode(m1Meth, List(
1191+
VarOp(ILOAD, 1),
1192+
VarOp(ISTORE, 2),
1193+
VarOp(ILOAD, 2),
1194+
LookupSwitch(LOOKUPSWITCH, Label(40), List(1, 7, 8, 9), List(Label(4), Label(13), Label(22), Label(31))),
1195+
Label(4),
1196+
IntOp(BIPUSH, 10),
1197+
Jump(GOTO, Label(52)),
1198+
Op(NOP),
1199+
Op(NOP),
1200+
Op(ATHROW),
1201+
Label(13),
1202+
IntOp(BIPUSH, 20),
1203+
Jump(GOTO, Label(52)),
1204+
Op(NOP),
1205+
Op(NOP),
1206+
Op(ATHROW),
1207+
Label(22),
1208+
IntOp(BIPUSH, 30),
1209+
Jump(GOTO, Label(52)),
1210+
Op(NOP),
1211+
Op(NOP),
1212+
Op(ATHROW),
1213+
Label(31),
1214+
IntOp(BIPUSH, 40),
1215+
Jump(GOTO, Label(52)),
1216+
Op(NOP),
1217+
Op(NOP),
1218+
Op(ATHROW),
1219+
Label(40),
1220+
VarOp(ILOAD, 1),
1221+
Jump(GOTO, Label(52)),
1222+
Op(NOP),
1223+
Op(NOP),
1224+
Op(ATHROW),
1225+
Op(ATHROW),
1226+
Label(52),
1227+
Op(IRETURN),
1228+
))
1229+
1230+
// ---------------
1231+
1232+
val m2Meth = getMethod(fooClass, "m2")
1233+
1234+
assertSameCode(m2Meth, List(
1235+
VarOp(ILOAD, 1),
1236+
VarOp(ISTORE, 2),
1237+
VarOp(ILOAD, 2),
1238+
LookupSwitch(LOOKUPSWITCH, Label(31), List(1, 2, 7, 8), List(Label(4), Label(4), Label(13), Label(22))),
1239+
Label(4),
1240+
IntOp(BIPUSH, 10),
1241+
Jump(GOTO, Label(56)),
1242+
Op(NOP),
1243+
Op(NOP),
1244+
Op(ATHROW),
1245+
Label(13),
1246+
IntOp(BIPUSH, 20),
1247+
Jump(GOTO, Label(56)),
1248+
Op(NOP),
1249+
Op(NOP),
1250+
Op(ATHROW),
1251+
Label(22),
1252+
IntOp(BIPUSH, 30),
1253+
Jump(GOTO, Label(56)),
1254+
Op(NOP),
1255+
Op(NOP),
1256+
Op(ATHROW),
1257+
Label(31),
1258+
VarOp(ILOAD, 2),
1259+
VarOp(ISTORE, 3),
1260+
VarOp(ILOAD, 3),
1261+
IntOp(BIPUSH, 100),
1262+
Jump(IF_ICMPLE, Label(40)),
1263+
IntOp(BIPUSH, 20),
1264+
Jump(GOTO, Label(56)),
1265+
Label(40),
1266+
TypeOp(NEW, "scala/MatchError"),
1267+
Op(DUP),
1268+
VarOp(ILOAD, 2),
1269+
Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false),
1270+
Invoke(INVOKESPECIAL, "scala/MatchError", "<init>", "(Ljava/lang/Object;)V", false),
1271+
Op(ATHROW),
1272+
Op(NOP),
1273+
Op(NOP),
1274+
Op(ATHROW),
1275+
Op(ATHROW),
1276+
Label(56),
1277+
Op(IRETURN),
1278+
))
1279+
}
1280+
}
1281+
1282+
@Test def ifThenElseControlFlow(): Unit = {
1283+
/* This is a test case coming from the Scala.js linker, where in Scala 2 we
1284+
* had to introduce a "useless" `return` to make the bytecode size smaller,
1285+
* measurably increasing performance (!).
1286+
*/
1287+
1288+
val source =
1289+
s"""import java.io.Writer
1290+
|
1291+
|final class SourceMapWriter(out: Writer) {
1292+
| private val Base64Map =
1293+
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
1294+
| "abcdefghijklmnopqrstuvwxyz" +
1295+
| "0123456789+/"
1296+
|
1297+
| private final val VLQBaseShift = 5
1298+
| private final val VLQBase = 1 << VLQBaseShift
1299+
| private final val VLQBaseMask = VLQBase - 1
1300+
| private final val VLQContinuationBit = VLQBase
1301+
|
1302+
| def entryPoint(value: Int): Unit = writeBase64VLQ(value)
1303+
|
1304+
| private def writeBase64VLQ(value0: Int): Unit = {
1305+
| val signExtended = value0 >> 31
1306+
| val value = (((value0 ^ signExtended) - signExtended) << 1) | (signExtended & 1)
1307+
| if (value < 26) {
1308+
| out.write('A' + value) // was `return out...`
1309+
| } else {
1310+
| def writeBase64VLQSlowPath(value0: Int): Unit = {
1311+
| var value = value0
1312+
| while ({
1313+
| // do {
1314+
| var digit = value & VLQBaseMask
1315+
| value = value >>> VLQBaseShift
1316+
| if (value != 0)
1317+
| digit |= VLQContinuationBit
1318+
| out.write(Base64Map.charAt(digit))
1319+
| // } while (
1320+
| value != 0
1321+
| // )
1322+
| }) ()
1323+
| }
1324+
| writeBase64VLQSlowPath(value)
1325+
| }
1326+
| }
1327+
|}
1328+
""".stripMargin
1329+
1330+
checkBCode(source) { dir =>
1331+
val sourceMapWriterClass = loadClassNode(dir.lookupName("SourceMapWriter.class", directory = false).input)
1332+
1333+
// ---------------
1334+
1335+
val writeBase64VLQMeth = getMethod(sourceMapWriterClass, "writeBase64VLQ")
1336+
1337+
assertSameCode(writeBase64VLQMeth, List(
1338+
VarOp(ILOAD, 1),
1339+
IntOp(BIPUSH, 31),
1340+
Op(ISHR),
1341+
VarOp(ISTORE, 2),
1342+
VarOp(ILOAD, 1),
1343+
VarOp(ILOAD, 2),
1344+
Op(IXOR),
1345+
VarOp(ILOAD, 2),
1346+
Op(ISUB),
1347+
Op(ICONST_1),
1348+
Op(ISHL),
1349+
VarOp(ILOAD, 2),
1350+
Op(ICONST_1),
1351+
Op(IAND),
1352+
Op(IOR),
1353+
VarOp(ISTORE, 3),
1354+
VarOp(ILOAD, 3),
1355+
IntOp(BIPUSH, 26),
1356+
Jump(IF_ICMPGE, Label(26)),
1357+
VarOp(ALOAD, 0),
1358+
Field(GETFIELD, "SourceMapWriter", "out", "Ljava/io/Writer;"),
1359+
IntOp(BIPUSH, 65),
1360+
VarOp(ILOAD, 3),
1361+
Op(IADD),
1362+
Invoke(INVOKEVIRTUAL, "java/io/Writer", "write", "(I)V", false),
1363+
Jump(GOTO, Label(31)),
1364+
Label(26),
1365+
VarOp(ALOAD, 0),
1366+
VarOp(ILOAD, 3),
1367+
Invoke(INVOKESPECIAL, "SourceMapWriter", "writeBase64VLQSlowPath$1", "(I)V", false),
1368+
Label(31),
1369+
Op(RETURN),
1370+
))
1371+
1372+
// ---------------
1373+
1374+
val writeBase64VLQSlowPathMeth = getMethod(sourceMapWriterClass, "writeBase64VLQSlowPath$1")
1375+
1376+
assertSameCode(writeBase64VLQSlowPathMeth, List(
1377+
VarOp(ILOAD, 1),
1378+
VarOp(ISTORE, 2),
1379+
Label(2),
1380+
VarOp(ILOAD, 2),
1381+
IntOp(BIPUSH, 31),
1382+
Op(IAND),
1383+
VarOp(ISTORE, 3),
1384+
VarOp(ILOAD, 2),
1385+
Op(ICONST_5),
1386+
Op(IUSHR),
1387+
VarOp(ISTORE, 2),
1388+
VarOp(ILOAD, 2),
1389+
Op(ICONST_0),
1390+
Jump(IF_ICMPEQ, Label(19)),
1391+
VarOp(ILOAD, 3),
1392+
IntOp(BIPUSH, 32),
1393+
Op(IOR),
1394+
VarOp(ISTORE, 3),
1395+
Label(19),
1396+
VarOp(ALOAD, 0),
1397+
Field(GETFIELD, "SourceMapWriter", "out", "Ljava/io/Writer;"),
1398+
Field(GETSTATIC, "scala/Char$", "MODULE$", "Lscala/Char$;"),
1399+
VarOp(ALOAD, 0),
1400+
Field(GETFIELD, "SourceMapWriter", "Base64Map", "Ljava/lang/String;"),
1401+
VarOp(ILOAD, 3),
1402+
Invoke(INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C", false),
1403+
Invoke(INVOKEVIRTUAL, "scala/Char$", "char2int", "(C)I", false),
1404+
Invoke(INVOKEVIRTUAL, "java/io/Writer", "write", "(I)V", false),
1405+
VarOp(ILOAD, 2),
1406+
Op(ICONST_0),
1407+
Jump(IF_ICMPEQ, Label(35)),
1408+
Op(ICONST_1),
1409+
Jump(GOTO, Label(38)),
1410+
Label(35),
1411+
Op(ICONST_0),
1412+
Label(38),
1413+
Jump(IFNE, Label(2)),
1414+
Op(RETURN),
1415+
))
1416+
}
1417+
}
1418+
10421419
@Test
10431420
def getClazz: Unit = {
10441421
val source = """

0 commit comments

Comments
 (0)