@@ -1039,6 +1039,383 @@ class TestBCode extends DottyBytecodeTest {
1039
1039
}
1040
1040
}
1041
1041
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
+
1042
1419
@ Test
1043
1420
def getClazz : Unit = {
1044
1421
val source = """
0 commit comments