35 36 package compiler.valhalla.valuetypes; 37 38 import compiler.whitebox.CompilerWhiteBoxTest; 39 import jdk.internal.misc.Unsafe; 40 import jdk.test.lib.Asserts; 41 import jdk.test.lib.Platform; 42 import jdk.test.lib.ProcessTools; 43 import jdk.test.lib.OutputAnalyzer; 44 import jdk.test.lib.Utils; 45 import sun.hotspot.WhiteBox; 46 47 import java.lang.annotation.Retention; 48 import java.lang.annotation.RetentionPolicy; 49 import java.lang.reflect.Method; 50 import java.util.ArrayList; 51 import java.util.Hashtable; 52 import java.util.regex.Matcher; 53 import java.util.regex.Pattern; 54 55 // Test value type 56 __ByValue final class MyValue { 57 final int x; 58 final long y; 59 final double z; 60 61 private MyValue(int x, long y, double z) { 62 this.x = x; 63 this.y = y; 64 this.z = z; 65 } 66 67 @DontInline 68 public static MyValue createDontInline(int x, long y, double z) { 69 return __Make MyValue(x, y, z); 70 } 71 72 @ForceInline 73 public static MyValue createInline(int x, long y, double z) { 74 return __Make MyValue(x, y, z); 75 } 76 77 @DontInline 78 public String toStringDontInline() { 79 return "MyValue: x=" + x + " y=" + y + " z=" + z; 80 } 81 82 @ForceInline 83 public String toStringInline() { 84 return "MyValue: x=" + x + " y=" + y + " z=" + z; 85 } 86 } 87 88 public class ValueTypeTestBench { 89 // Print ideal graph after execution of each test 90 private static final boolean PRINT_GRAPH = true; 91 92 // ========== Test definitions ========== 93 94 // Receive value type through call to interpreter 95 @Test(failOn = ALLOC + STORE) 96 public double test1() { 97 MyValue v = MyValue.createDontInline(rI, rL, rD); 98 return v.x + v.y + v.z; 99 } 100 101 @DontCompile 102 public void test1_verifier(boolean warmup) { 103 double result = test1(); 104 Asserts.assertEQ(result, rI + rL + rD); 105 } 106 107 // Receive value type from interpreter via parameter 108 @Test(failOn = ALLOC + STORE) 109 public double test2(MyValue v) { 110 return v.x + v.y + v.z; 111 } 112 113 @DontCompile 114 public void test2_verifier(boolean warmup) { 115 MyValue v = MyValue.createDontInline(rI, rL, rD); 116 double result = test2(v); 117 Asserts.assertEQ(result, rI + rL + rD); 118 } 119 120 // Return incoming value type without accessing fields 121 @Test(failOn = ALLOC + LOAD + STORE) 122 public MyValue test3(MyValue v) { 123 return v; 124 } 125 126 @DontCompile 127 public void test3_verifier(boolean warmup) { 128 MyValue v1 = MyValue.createDontInline(rI, rL, rD); 129 MyValue v2 = test3(v1); 130 Asserts.assertEQ(v1.x, v2.x); 131 Asserts.assertEQ(v1.y, v2.y); 132 Asserts.assertEQ(v1.z, v2.z); 133 } 134 135 // Create a value type in compiled code and only use fields. 136 // Allocation should go away because value type does not escape. 137 @Test(failOn = ALLOC + LOAD + STORE) 138 public double test4() { 139 MyValue v = MyValue.createInline(rI, rL, rD); 140 return v.x + v.y + v.z; 141 } 142 143 @DontCompile 144 public void test4_verifier(boolean warmup) { 145 double result = test4(); 146 Asserts.assertEQ(result, rI + rL + rD); 147 } 148 149 // Create a value type in compiled code and pass it to 150 // an inlined compiled method via a call. 151 @Test(failOn = ALLOC + LOAD + STORE) 152 public double test5() { 153 MyValue v = MyValue.createInline(rI, rL, rD); 154 return test5Inline(v); 155 } 156 157 @ForceInline 158 public double test5Inline(MyValue v) { 159 return v.x + v.y + v.z; 160 } 161 162 @DontCompile 163 public void test5_verifier(boolean warmup) { 164 double result = test5(); 165 Asserts.assertEQ(result, rI + rL + rD); 166 } 167 168 // Create a value type in compiled code and pass it to 169 // the interpreter via a call. 170 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) 171 public double test6() { 172 MyValue v = MyValue.createInline(rI, rL, rD); 173 // Pass to interpreter 174 return sumValue(v); 175 } 176 177 @DontCompile 178 public void test6_verifier(boolean warmup) { 179 double result = test6(); 180 Asserts.assertEQ(result, rI + rL + rD); 181 } 182 183 // Create a value type in compiled code and pass it to 184 // the interpreter by returning. 185 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) 186 public MyValue test7(int x, long y, double z) { 187 return MyValue.createInline(x, y, z); 188 } 189 190 @DontCompile 191 public void test7_verifier(boolean warmup) { 192 MyValue v = test7(rI, rL, rD); 193 double result = v.x + v.y + v.z; 194 Asserts.assertEQ(result, rI + rL + rD); 195 } 196 197 // Merge value types created from two branches 198 @Test(failOn = ALLOC + STORE) 199 public double test8(boolean b) { 200 MyValue v; 201 if (b) { 202 v = MyValue.createInline(rI, rL, rD); 203 } else { 204 v = MyValue.createDontInline(rI + 1, rL + 1, rD + 1); 205 } 206 return v.x + v.y + v.z; 207 } 208 209 @DontCompile 210 public void test8_verifier(boolean warmup) { 211 Asserts.assertEQ(test8(true), rI + rL + rD); 212 Asserts.assertEQ(test8(false), rI + 1 + rL + 1 + ((double)rD + 1)); 213 } 214 215 // Merge value types created from two branches 216 @Test(match = {ALLOC, STORE}, matchCount = {1, 3}, failOn = LOAD) 217 public MyValue test9(boolean b) { 218 MyValue v; 219 if (b) { 220 // Value type is not allocated 221 v = MyValue.createInline(rI, rL, rD); 222 } else { 223 // Value type is allocated by the callee 224 v = MyValue.createDontInline(rI + 1, rL + 1, rD + 1); 225 } 226 // Need to allocate value type if 'b' is true 227 double sum = sumValue(v); 228 if (b) { 229 v = MyValue.createDontInline(rI, rL, sum); 230 } else { 231 v = MyValue.createDontInline(rI, rL, sum + 1); 232 } 233 // Don't need to allocate value type because both branches allocate 234 return v; 235 } 236 237 @DontCompile 238 public void test9_verifier(boolean warmup) { 239 MyValue v = test9(true); 240 Asserts.assertEQ(v.x, rI); 241 Asserts.assertEQ(v.y, rL); 242 Asserts.assertEQ(v.z, rI + rL + rD); 243 244 v = test9(false); 245 Asserts.assertEQ(v.x, rI); 246 Asserts.assertEQ(v.y, rL); 247 Asserts.assertEQ(v.z, rI + rL + ((double)rD + 1)); 248 } 249 250 // Merge value types created in a loop (not inlined) 251 @Test(failOn = ALLOC + STORE) 252 public double test10(int x, long y, double z) { 253 MyValue v = MyValue.createDontInline(x, y, z); 254 for (int i = 0; i < 10; ++i) { 255 v = MyValue.createDontInline(v.x + 1, v.y + 1, v.z + 1); 256 } 257 return v.x + v.y + v.z; 258 } 259 260 @DontCompile 261 public void test10_verifier(boolean warmup) { 262 double result = test10(rI, rL, rD); 263 Asserts.assertEQ(result, rI + rL + 20 + ((double)rD + 10)); 264 } 265 266 // Merge value types created in a loop (inlined) 267 @Test(failOn = ALLOC + LOAD + STORE + LOOP) 268 public double test11(int x, long y, double z) { 269 MyValue v = MyValue.createInline(x, y, z); 270 for (int i = 0; i < 10; ++i) { 271 v = MyValue.createInline(v.x + 1, v.y + 1, v.z + 1); 272 } 273 return v.x + v.y + v.z; 274 } 275 276 @DontCompile 277 public void test11_verifier(boolean warmup) { 278 double result = test11(rI, rL, rD); 279 Asserts.assertEQ(result, rI + rL + 20 + ((double)rD + 10)); 280 } 281 282 // Test loop with uncommon trap referencing a value type 283 @Test(match = {TRAP, SCOBJ}, matchCount = {1, 1}, failOn = ALLOC + LOAD + STORE) 284 public double test12(boolean b) { 285 MyValue v = MyValue.createInline(rI, rL, rD); 286 double result = 42; 287 for (int i = 0; i < 1000; ++i) { 288 if (b) { 289 result += v.x; 290 } else { 291 // Uncommon trap referencing v. We delegate allocation to the 292 // interpreter by adding a SafePointScalarObjectNode. 293 result = sumValue(v); 294 } 295 } 296 return result; 297 } 298 299 @DontCompile 300 public void test12_verifier(boolean warmup) { 301 double result = test12(warmup); 302 Asserts.assertEQ(result, warmup ? 42 + (1000*(double)rI) : (rI + rL + rD)); 303 } 304 305 // Test loop with uncommon trap referencing a value type 306 @Test(match = {TRAP, LOAD}, matchCount = {1, 1}, failOn = ALLOC + STORE + SCOBJ) 307 public double test13(boolean b) { 308 MyValue v = MyValue.createDontInline(rI, rL, rD); 309 double result = 42; 310 for (int i = 0; i < 1000; ++i) { 311 if (b) { 312 result += v.x; 313 } else { 314 // Uncommon trap referencing v. Should not allocate 315 // but just pass the existing oop to the uncommon trap. 316 result = sumValue(v); 317 } 318 } 319 return result; 320 } 321 322 @DontCompile 323 public void test13_verifier(boolean warmup) { 324 double result = test13(warmup); 325 Asserts.assertEQ(result, warmup ? 42 + (1000*(double)rI) : (rI + rL + rD)); 326 } 327 328 // Create a value type in a non-inlined method and then call a 329 // non-inlined method on that value type. 330 @Test(failOn = (ALLOC + STORE)) 331 public String test14() { 332 MyValue v = MyValue.createDontInline(32, 64L, 128.0); 333 String s = v.toStringDontInline(); 334 return s; 335 } 336 337 @DontCompile 338 public void test14_verifier(boolean b) { 339 String s = test14(); 340 System.out.println("Result is: " + s); 341 } 342 343 // Create a value type in an inlined method and then call a 344 // non-inlined method on that value type. 345 @Test(match = {ALLOC}, matchCount = {1}) 346 public String test15() { 347 MyValue v = MyValue.createInline(65, 129L, 257.0); 348 String s = v.toStringDontInline(); 349 return s; 350 } 351 352 @DontCompile 353 public void test15_verifier(boolean b) { 354 String s = test15(); 355 System.out.println("Result is: " + s); 356 } 357 358 // Create a value type in a non-inlined method and then call an 359 // inlined method on that value type. Allocations are due to building 360 // String objects and not due to allocating value types. 361 @Test(match = {ALLOC}, matchCount = {2}) 362 public String test16() { 363 MyValue v = MyValue.createDontInline(130, 258L, 514.0); 364 String s = v.toStringInline(); 365 return s; 366 } 367 368 @DontCompile 369 public void test16_verifier(boolean b) { 370 String s = test16(); 371 System.out.println("Result is: " + s); 372 } 373 374 // Create a value type in an inlined method and then call an 375 // inlined method on that value type. Allocations are due to 376 // building String objects and not due to allocating value types. 377 @Test(match = {ALLOC}, matchCount = {2}) 378 public String test17() { 379 MyValue v = MyValue.createInline(259, 515L, 1027.0); 380 String s = v.toStringInline(); 381 return s; 382 } 383 384 @DontCompile 385 public void test17_verifier(boolean b) { 386 String s = test17(); 387 System.out.println("Result is: " + s); 388 } 389 390 // Create a value type in compiled code and pass it to the 391 // interpreter via a call. The value is live at the first call so 392 // debug info should include a reference to all its fields. 393 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) 394 public double test18() { 395 MyValue v = MyValue.createInline(rI, rL, rD); 396 sumValue(v); 397 return sumValue(v); 398 } 399 400 @DontCompile 401 public void test18_verifier(boolean warmup) { 402 double result = test18(); 403 Asserts.assertEQ(result, rI + rL + rD); 404 } 405 406 // Create a value type in compiled code and pass it to the 407 // interpreter via a call. The value type is passed twice but 408 // should only be allocated once. 409 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) 410 public double test19() { 411 MyValue v = MyValue.createInline(rI, rL, rD); 412 return sumValue(v, v); 413 } 414 415 @DontCompile 416 public void test19_verifier(boolean warmup) { 417 double result = test19(); 418 Asserts.assertEQ(result, rI + rL + rD); 419 } 420 421 // Create a value type in compiled code and pass it to the 422 // interpreter via a call. The value type is live at the uncommon 423 // trap: verify that deoptimization causes the value type to be 424 // correctly allocated. 425 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) 426 public double test20(boolean flag) { 427 MyValue v = MyValue.createInline(rI, rL, rD); 428 if (flag) { 429 // uncommon trap 430 WHITE_BOX.deoptimizeMethod(tests.get("ValueTypeTestBench::test16")); 431 } 432 return sumValue(v); 433 } 434 435 @DontCompile 436 public void test20_verifier(boolean warmup) { 437 double result = test20(false); 438 Asserts.assertEQ(result, rI + rL + rD); 439 if (!warmup) { 440 result = test20(true); 441 Asserts.assertEQ(result, rI + rL + rD); 442 } 443 } 444 445 // ========== Helper methods ========== 446 447 @DontCompile 448 public double sumValue(MyValue v) { 449 return v.x + v.y + v.z; 450 } 451 452 @DontCompile 453 public double sumValue(MyValue v, MyValue dummy) { 454 return v.x + v.y + v.z; 455 } 456 457 // ========== Test infrastructure ========== 458 459 private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); 460 private static final int COMP_LEVEL_ANY = -1; 461 private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; 462 private static final Hashtable<String, Method> tests = new Hashtable<String, Method>(); 463 private static final int WARMUP = 10; 464 465 // Regular expressions used to match nodes in the PrintIdeal output 466 private static final String START = "(\\d+\\t(.*"; 467 private static final String MID = ".*)+\\t===.*"; 468 private static final String END = ")|"; 469 private static final String ALLOC = START + "CallStaticJava" + MID + "_new_instance_Java" + END; 470 private static final String LOAD = START + "Load" + MID + "valuetype\\*" + END; 471 private static final String STORE = START + "Store" + MID + "valuetype\\*" + END; 472 private static final String LOOP = START + "Loop" + MID + "" + END; 473 private static final String TRAP = START + "CallStaticJava" + MID + "uncommon_trap" + END; 474 // TODO: match field values of scalar replaced object 475 private static final String SCOBJ = "(.*# ScObj.*" + END; 476 477 478 // Random test values 479 private static final int rI = Utils.getRandomInstance().nextInt(); 480 private static final long rL = Utils.getRandomInstance().nextLong(); 481 private static final double rD = Utils.getRandomInstance().nextDouble(); 482 483 static { 484 // Gather all test methods and put them in Hashtable 485 for (Method m : ValueTypeTestBench.class.getDeclaredMethods()) { 486 if (m.isAnnotationPresent(Test.class)) { 487 tests.put("ValueTypeTestBench::" + m.getName(), m); 488 } 489 } 490 } 491 492 public static void main(String[] args) throws Throwable { 493 if (args.length == 0) { 494 // Run tests in own process and verify output 495 OutputAnalyzer oa = ProcessTools.executeTestJvm("-noverify", 496 "-XX:+UnlockDiagnosticVMOptions", "-Xbootclasspath/a:.", "-XX:+WhiteBoxAPI", 497 "-XX:-TieredCompilation", "-XX:-BackgroundCompilation", "-XX:-UseOnStackReplacement", 498 "-XX:CompileCommand=quiet", "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly", 499 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.ValueTypeTestBench::*", 500 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue::*", 501 ValueTypeTestBench.class.getName(), "run"); 502 String output = oa.getOutput(); 503 oa.shouldHaveExitValue(0); 504 parseOutput(output); 505 } else { 506 // Execute tests 507 ValueTypeTestBench bench = new ValueTypeTestBench(); 508 bench.run(); 509 } 510 } 511 512 public static void parseOutput(String output) throws Exception { 513 String split = "b compiler.valhalla.valuetypes."; 514 String[] compilations = output.split(split); 515 // Print header 516 System.out.println(compilations[0]); 517 // Iterate over compilation output 518 for (String graph : compilations) { 519 String[] lines = graph.split("\\n"); 520 String testName = lines[0].split(" ")[0]; 521 Method test = tests.get(testName); 522 if (test == null) { 523 // Skip helper methods 524 continue; 525 } 571 572 public void setup(Method[] methods) { 573 for (Method m : methods) { 574 if (m.isAnnotationPresent(Test.class)) { 575 // Don't inline tests 576 WHITE_BOX.testSetDontInlineMethod(m, true); 577 } 578 if (m.isAnnotationPresent(DontCompile.class)) { 579 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, true); 580 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, false); 581 } 582 if (m.isAnnotationPresent(ForceInline.class)) { 583 WHITE_BOX.testSetForceInlineMethod(m, true); 584 } else if (m.isAnnotationPresent(DontInline.class)) { 585 WHITE_BOX.testSetDontInlineMethod(m, true); 586 } 587 } 588 } 589 590 public void run() throws Exception { 591 System.out.format("rI = %d, rL = %d, rD = %f\n", rI, rL, rD); 592 setup(this.getClass().getDeclaredMethods()); 593 setup(MyValue.class.getDeclaredMethods()); 594 595 // Execute tests 596 for (Method test : tests.values()) { 597 Method verifier = getClass().getDeclaredMethod(test.getName() + "_verifier", boolean.class); 598 // Warmup using verifier method 599 for (int i = 0; i < WARMUP; ++i) { 600 verifier.invoke(this, true); 601 } 602 // Trigger compilation 603 WHITE_BOX.enqueueMethodForCompilation(test, COMP_LEVEL_FULL_OPTIMIZATION); 604 Asserts.assertTrue(WHITE_BOX.isMethodCompiled(test, false)); 605 // Check result 606 verifier.invoke(this, false); 607 } 608 } 609 } 610 611 // Mark method as test 612 @Retention(RetentionPolicy.RUNTIME) 613 @interface Test { 614 // Regular expression used to match forbidden IR nodes 615 // in the C2 IR emitted for this test. 616 String failOn() default ""; 617 // Regular expressions used to match and count IR nodes. 618 String[] match() default { }; 619 int[] matchCount() default { }; 620 } 621 622 // Force method inlining during compilation 623 @Retention(RetentionPolicy.RUNTIME) 624 @interface ForceInline { } | 35 36 package compiler.valhalla.valuetypes; 37 38 import compiler.whitebox.CompilerWhiteBoxTest; 39 import jdk.internal.misc.Unsafe; 40 import jdk.test.lib.Asserts; 41 import jdk.test.lib.Platform; 42 import jdk.test.lib.ProcessTools; 43 import jdk.test.lib.OutputAnalyzer; 44 import jdk.test.lib.Utils; 45 import sun.hotspot.WhiteBox; 46 47 import java.lang.annotation.Retention; 48 import java.lang.annotation.RetentionPolicy; 49 import java.lang.reflect.Method; 50 import java.util.ArrayList; 51 import java.util.Hashtable; 52 import java.util.regex.Matcher; 53 import java.util.regex.Pattern; 54 55 // Test value types 56 __ByValue final class MyValue1 { 57 final int x; 58 final long y; 59 final MyValue2 v1; 60 final MyValue2 v2; 61 final int c; 62 63 private MyValue1(int x, long y, MyValue2 v1, MyValue2 v2, int c) { 64 this.x = x; 65 this.y = y; 66 this.v1 = v1; 67 this.v2 = v2; 68 this.c = c; 69 } 70 71 @DontInline 72 public static MyValue1 createDontInline(int x, long y) { 73 return __Make MyValue1(x, y, MyValue2.createInline(x, x < y), MyValue2.createInline(x, x > y), ValueTypeTestBench.rI); 74 } 75 76 @ForceInline 77 public static MyValue1 createInline(int x, long y) { 78 return __Make MyValue1(x, y, MyValue2.createInline(x, x < y), MyValue2.createInline(x, x > y), ValueTypeTestBench.rI); 79 } 80 81 @ForceInline 82 public long hash() { 83 return x + y + c + v1.hash() + v2.hash(); 84 } 85 86 @DontCompile 87 public long hashInterpreted() { 88 return x + y + c + v1.hash() + v2.hash(); 89 } 90 } 91 92 __ByValue final class MyValue2 { 93 final int x; 94 final boolean b; 95 final long c; 96 97 private MyValue2(int x, boolean b, long c) { 98 this.x = x; 99 this.b = b; 100 this.c = c; 101 } 102 103 @ForceInline 104 public static MyValue2 createInline(int x, boolean b) { 105 return __Make MyValue2(x, b, ValueTypeTestBench.rL); 106 } 107 108 @ForceInline 109 public long hash() { 110 return x + (b ? 0 : 1) + c; 111 } 112 } 113 114 public class ValueTypeTestBench { 115 // Print ideal graph after execution of each test 116 private static final boolean PRINT_GRAPH = true; 117 118 // ========== Helper methods ========== 119 120 public long hash() { 121 return hash(rI, rL); 122 } 123 124 public long hash(int x, long y) { 125 return MyValue1.createInline(x, y).hash(); 126 } 127 128 // ========== Test definitions ========== 129 130 // Receive value type through call to interpreter 131 @Test(failOn = ALLOC + STORE + TRAP) 132 public long test1() { 133 MyValue1 v = MyValue1.createDontInline(rI, rL); 134 return v.hash(); 135 } 136 137 @DontCompile 138 public void test1_verifier(boolean warmup) { 139 long result = test1(); 140 Asserts.assertEQ(result, hash()); 141 } 142 143 // Receive value type from interpreter via parameter 144 @Test(failOn = ALLOC + STORE + TRAP) 145 public long test2(MyValue1 v) { 146 return v.hash(); 147 } 148 149 @DontCompile 150 public void test2_verifier(boolean warmup) { 151 MyValue1 v = MyValue1.createDontInline(rI, rL); 152 long result = test2(v); 153 Asserts.assertEQ(result, hash()); 154 } 155 156 // Return incoming value type without accessing fields 157 @Test(failOn = ALLOC + LOAD + STORE + TRAP) 158 public MyValue1 test3(MyValue1 v) { 159 return v; 160 } 161 162 @DontCompile 163 public void test3_verifier(boolean warmup) { 164 MyValue1 v1 = MyValue1.createDontInline(rI, rL); 165 MyValue1 v2 = test3(v1); 166 Asserts.assertEQ(v1.x, v2.x); 167 Asserts.assertEQ(v1.y, v2.y); 168 } 169 170 // Create a value type in compiled code and only use fields. 171 // Allocation should go away because value type does not escape. 172 @Test(failOn = ALLOC + LOAD + STORE + TRAP) 173 public long test4() { 174 MyValue1 v = MyValue1.createInline(rI, rL); 175 return v.hash(); 176 } 177 178 @DontCompile 179 public void test4_verifier(boolean warmup) { 180 long result = test4(); 181 Asserts.assertEQ(result, hash()); 182 } 183 184 // Create a value type in compiled code and pass it to 185 // an inlined compiled method via a call. 186 @Test(failOn = ALLOC + LOAD + STORE + TRAP) 187 public long test5() { 188 MyValue1 v = MyValue1.createInline(rI, rL); 189 return test5Inline(v); 190 } 191 192 @ForceInline 193 public long test5Inline(MyValue1 v) { 194 return v.hash(); 195 } 196 197 @DontCompile 198 public void test5_verifier(boolean warmup) { 199 long result = test5(); 200 Asserts.assertEQ(result, hash()); 201 } 202 203 // Create a value type in compiled code and pass it to 204 // the interpreter via a call. 205 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) 206 public long test6() { 207 MyValue1 v = MyValue1.createInline(rI, rL); 208 // Pass to interpreter 209 return v.hashInterpreted(); 210 } 211 212 @DontCompile 213 public void test6_verifier(boolean warmup) { 214 long result = test6(); 215 Asserts.assertEQ(result, hash()); 216 } 217 218 // Create a value type in compiled code and pass it to 219 // the interpreter by returning. 220 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) 221 public MyValue1 test7(int x, long y) { 222 return MyValue1.createInline(x, y); 223 } 224 225 @DontCompile 226 public void test7_verifier(boolean warmup) { 227 MyValue1 v = test7(rI, rL); 228 Asserts.assertEQ(v.hash(), hash()); 229 } 230 231 // Merge value types created from two branches 232 @Test(failOn = ALLOC + STORE + TRAP) 233 public long test8(boolean b) { 234 MyValue1 v; 235 if (b) { 236 v = MyValue1.createInline(rI, rL); 237 } else { 238 v = MyValue1.createDontInline(rI + 1, rL + 1); 239 } 240 return v.hash(); 241 } 242 243 @DontCompile 244 public void test8_verifier(boolean warmup) { 245 Asserts.assertEQ(test8(true), hash()); 246 Asserts.assertEQ(test8(false), hash(rI + 1, rL + 1)); 247 } 248 249 // Merge value types created from two branches 250 @Test(match = {ALLOC, STORE}, matchCount = {1, 9}, failOn = LOAD + TRAP) 251 public MyValue1 test9(boolean b) { 252 MyValue1 v; 253 if (b) { 254 // Value type is not allocated 255 v = MyValue1.createInline(rI, rL); 256 } else { 257 // Value type is allocated by the callee 258 v = MyValue1.createDontInline(rI + 1, rL + 1); 259 } 260 // Need to allocate value type if 'b' is true 261 long sum = v.hashInterpreted(); 262 if (b) { 263 v = MyValue1.createDontInline(rI, sum); 264 } else { 265 v = MyValue1.createDontInline(rI, sum + 1); 266 } 267 // Don't need to allocate value type because both branches allocate 268 return v; 269 } 270 271 @DontCompile 272 public void test9_verifier(boolean warmup) { 273 MyValue1 v = test9(true); 274 Asserts.assertEQ(v.x, rI); 275 Asserts.assertEQ(v.y, hash()); 276 v = test9(false); 277 Asserts.assertEQ(v.x, rI); 278 Asserts.assertEQ(v.y, hash(rI + 1, rL + 1) + 1); 279 } 280 281 // Merge value types created in a loop (not inlined) 282 @Test(failOn = ALLOC + STORE + TRAP) 283 public long test10(int x, long y) { 284 MyValue1 v = MyValue1.createDontInline(x, y); 285 for (int i = 0; i < 10; ++i) { 286 v = MyValue1.createDontInline(v.x + 1, v.y + 1); 287 } 288 return v.hash(); 289 } 290 291 @DontCompile 292 public void test10_verifier(boolean warmup) { 293 long result = test10(rI, rL); 294 Asserts.assertEQ(result, hash(rI + 10, rL + 10)); 295 } 296 297 // Merge value types created in a loop (inlined) 298 @Test(failOn = ALLOC + LOAD + STORE + LOOP + TRAP) 299 public long test11(int x, long y) { 300 MyValue1 v = MyValue1.createInline(x, y); 301 for (int i = 0; i < 10; ++i) { 302 v = MyValue1.createInline(v.x + 1, v.y + 1); 303 } 304 return v.hash(); 305 } 306 307 @DontCompile 308 public void test11_verifier(boolean warmup) { 309 long result = test11(rI, rL); 310 Asserts.assertEQ(result, hash(rI + 10, rL + 10)); 311 } 312 313 // Test loop with uncommon trap referencing a value type 314 @Test(match = {TRAP, SCOBJ}, matchCount = {1, 1}, failOn = ALLOC + LOAD + STORE) 315 public long test12(boolean b) { 316 MyValue1 v = MyValue1.createInline(rI, rL); 317 long result = 42; 318 for (int i = 0; i < 1000; ++i) { 319 if (b) { 320 result += v.x; 321 } else { 322 // Uncommon trap referencing v. We delegate allocation to the 323 // interpreter by adding a SafePointScalarObjectNode. 324 result = v.hashInterpreted(); 325 } 326 } 327 return result; 328 } 329 330 @DontCompile 331 public void test12_verifier(boolean warmup) { 332 long result = test12(warmup); 333 Asserts.assertEQ(result, warmup ? 42 + (1000*rI) : hash()); 334 } 335 336 // Test loop with uncommon trap referencing a value type 337 @Test(match = {TRAP, LOAD}, matchCount = {1, 1}, failOn = ALLOC + STORE + SCOBJ) 338 public long test13(boolean b) { 339 MyValue1 v = MyValue1.createDontInline(rI, rL); 340 long result = 42; 341 for (int i = 0; i < 1000; ++i) { 342 if (b) { 343 result += v.x; 344 } else { 345 // Uncommon trap referencing v. Should not allocate 346 // but just pass the existing oop to the uncommon trap. 347 result = v.hashInterpreted(); 348 } 349 } 350 return result; 351 } 352 353 @DontCompile 354 public void test13_verifier(boolean warmup) { 355 long result = test13(warmup); 356 Asserts.assertEQ(result, warmup ? 42 + (1000*rI) : hash()); 357 } 358 359 // Create a value type in a non-inlined method and then call a 360 // non-inlined method on that value type. 361 @Test(failOn = (ALLOC + LOAD + STORE + TRAP)) 362 public long test14() { 363 MyValue1 v = MyValue1.createDontInline(rI, rL); 364 return v.hashInterpreted(); 365 } 366 367 @DontCompile 368 public void test14_verifier(boolean b) { 369 long result = test14(); 370 Asserts.assertEQ(result, hash()); 371 } 372 373 // Create a value type in an inlined method and then call a 374 // non-inlined method on that value type. 375 @Test(failOn = (LOAD + TRAP), match = {ALLOC}, matchCount = {1}) 376 public long test15() { 377 MyValue1 v = MyValue1.createInline(rI, rL); 378 return v.hashInterpreted(); 379 } 380 381 @DontCompile 382 public void test15_verifier(boolean b) { 383 long result = test15(); 384 Asserts.assertEQ(result, hash()); 385 } 386 387 // Create a value type in a non-inlined method and then call an 388 // inlined method on that value type. 389 @Test(failOn = (ALLOC + STORE + TRAP)) 390 public long test16() { 391 MyValue1 v = MyValue1.createDontInline(rI, rL); 392 return v.hash(); 393 } 394 395 @DontCompile 396 public void test16_verifier(boolean b) { 397 long result = test16(); 398 Asserts.assertEQ(result, hash()); 399 } 400 401 // Create a value type in an inlined method and then call an 402 // inlined method on that value type. 403 @Test(failOn = (ALLOC + LOAD + STORE + TRAP)) 404 public long test17() { 405 MyValue1 v = MyValue1.createInline(rI, rL); 406 return v.hash(); 407 } 408 409 @DontCompile 410 public void test17_verifier(boolean b) { 411 long result = test17(); 412 Asserts.assertEQ(result, hash()); 413 } 414 415 // Create a value type in compiled code and pass it to the 416 // interpreter via a call. The value is live at the first call so 417 // debug info should include a reference to all its fields. 418 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) 419 public long test18() { 420 MyValue1 v = MyValue1.createInline(rI, rL); 421 v.hashInterpreted(); 422 return v.hashInterpreted(); 423 } 424 425 @DontCompile 426 public void test18_verifier(boolean warmup) { 427 long result = test18(); 428 Asserts.assertEQ(result, hash()); 429 } 430 431 // Create a value type in compiled code and pass it to the 432 // interpreter via a call. The value type is passed twice but 433 // should only be allocated once. 434 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) 435 public long test19() { 436 MyValue1 v = MyValue1.createInline(rI, rL); 437 return sumValue(v, v); 438 } 439 440 @DontCompile 441 public long sumValue(MyValue1 v, MyValue1 dummy) { 442 return v.hash(); 443 } 444 445 @DontCompile 446 public void test19_verifier(boolean warmup) { 447 long result = test19(); 448 Asserts.assertEQ(result, hash()); 449 } 450 451 // Create a value type in compiled code and pass it to the 452 // interpreter via a call. The value type is live at the uncommon 453 // trap: verify that deoptimization causes the value type to be 454 // correctly allocated. 455 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) 456 public long test20(boolean flag) { 457 MyValue1 v = MyValue1.createInline(rI, rL); 458 if (flag) { 459 // uncommon trap 460 WHITE_BOX.deoptimizeMethod(tests.get("ValueTypeTestBench::test16")); 461 } 462 return v.hashInterpreted(); 463 } 464 465 @DontCompile 466 public void test20_verifier(boolean warmup) { 467 long result = test20(false); 468 Asserts.assertEQ(result, hash()); 469 if (!warmup) { 470 result = test20(true); 471 Asserts.assertEQ(result, hash()); 472 } 473 } 474 475 // Value type fields in regular object 476 MyValue1 val1; 477 MyValue2 val2; 478 479 // Test value type fields in objects 480 @Test(failOn = (ALLOC + TRAP)) 481 public long test21(int x, long y) { 482 // Compute hash of value type fields 483 long result = val1.hash() + val2.hash(); 484 // Update fields 485 val1 = MyValue1.createInline(x, y); 486 val2 = MyValue2.createInline(x, true); 487 return result; 488 } 489 490 @DontCompile 491 public void test21_verifier(boolean warmup) { 492 // Check if hash computed by test18 is correct 493 val1 = MyValue1.createInline(rI, rL); 494 val2 = val1.v2; 495 long hash = val1.hash() + val2.hash(); 496 long result = test21(rI + 1, rL + 1); 497 Asserts.assertEQ(result, hash); 498 // Check if value type fields were updated 499 Asserts.assertEQ(val1.hash(), hash(rI + 1, rL + 1)); 500 Asserts.assertEQ(val2.hash(), MyValue2.createInline(rI + 1, true).hash()); 501 } 502 503 504 // ========== Test infrastructure ========== 505 506 private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); 507 private static final int COMP_LEVEL_ANY = -1; 508 private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; 509 private static final Hashtable<String, Method> tests = new Hashtable<String, Method>(); 510 private static final int WARMUP = 10; 511 private static boolean USE_COMPILER = WHITE_BOX.getBooleanVMFlag("UseCompiler"); 512 private static boolean PRINT_IDEAL = WHITE_BOX.getBooleanVMFlag("PrintIdeal"); 513 514 // Regular expressions used to match nodes in the PrintIdeal output 515 private static final String START = "(\\d+\\t(.*"; 516 private static final String MID = ".*)+\\t===.*"; 517 private static final String END = ")|"; 518 private static final String ALLOC = START + "CallStaticJava" + MID + "_new_instance_Java" + END; 519 private static final String LOAD = START + "Load" + MID + "valuetype\\*" + END; 520 private static final String STORE = START + "Store" + MID + "valuetype\\*" + END; 521 private static final String LOOP = START + "Loop" + MID + "" + END; 522 private static final String TRAP = START + "CallStaticJava" + MID + "uncommon_trap" + END; 523 // TODO: match field values of scalar replaced object 524 private static final String SCOBJ = "(.*# ScObj.*" + END; 525 526 // Random test values 527 public static final int rI = Utils.getRandomInstance().nextInt() % 1000; 528 public static final long rL = Utils.getRandomInstance().nextLong() % 1000; 529 530 static { 531 // Gather all test methods and put them in Hashtable 532 for (Method m : ValueTypeTestBench.class.getDeclaredMethods()) { 533 if (m.isAnnotationPresent(Test.class)) { 534 tests.put("ValueTypeTestBench::" + m.getName(), m); 535 } 536 } 537 } 538 539 public static void main(String[] args) throws Throwable { 540 if (args.length == 0) { 541 // Run tests in own process and verify output 542 OutputAnalyzer oa = ProcessTools.executeTestJvm("-noverify", 543 "-XX:+UnlockDiagnosticVMOptions", "-Xbootclasspath/a:.", "-XX:+WhiteBoxAPI", 544 "-XX:-TieredCompilation", "-XX:-BackgroundCompilation", "-XX:-UseOnStackReplacement", 545 "-XX:+IgnoreUnrecognizedVMOptions", "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly", 546 "-XX:CompileCommand=quiet", "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.ValueTypeTestBench::*", 547 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue1::*", 548 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue2::*", 549 ValueTypeTestBench.class.getName(), "run"); 550 // If ideal graph printing is enabled/supported, verify output 551 String output = oa.getOutput(); 552 oa.shouldHaveExitValue(0); 553 if (output.contains("PrintIdeal enabled")) { 554 parseOutput(output); 555 } else { 556 System.out.println("WARNING: test methods not compiled or PrintIdeal disabled! Running with -Xint or release build?"); 557 } 558 } else { 559 if (USE_COMPILER && PRINT_IDEAL) { 560 System.out.println("PrintIdeal enabled"); 561 } 562 // Execute tests 563 ValueTypeTestBench bench = new ValueTypeTestBench(); 564 bench.run(); 565 } 566 } 567 568 public static void parseOutput(String output) throws Exception { 569 String split = "b compiler.valhalla.valuetypes."; 570 String[] compilations = output.split(split); 571 // Print header 572 System.out.println(compilations[0]); 573 // Iterate over compilation output 574 for (String graph : compilations) { 575 String[] lines = graph.split("\\n"); 576 String testName = lines[0].split(" ")[0]; 577 Method test = tests.get(testName); 578 if (test == null) { 579 // Skip helper methods 580 continue; 581 } 627 628 public void setup(Method[] methods) { 629 for (Method m : methods) { 630 if (m.isAnnotationPresent(Test.class)) { 631 // Don't inline tests 632 WHITE_BOX.testSetDontInlineMethod(m, true); 633 } 634 if (m.isAnnotationPresent(DontCompile.class)) { 635 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, true); 636 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, false); 637 } 638 if (m.isAnnotationPresent(ForceInline.class)) { 639 WHITE_BOX.testSetForceInlineMethod(m, true); 640 } else if (m.isAnnotationPresent(DontInline.class)) { 641 WHITE_BOX.testSetDontInlineMethod(m, true); 642 } 643 } 644 } 645 646 public void run() throws Exception { 647 System.out.format("rI = %d, rL = %d\n", rI, rL); 648 setup(this.getClass().getDeclaredMethods()); 649 setup(MyValue1.class.getDeclaredMethods()); 650 setup(MyValue2.class.getDeclaredMethods()); 651 652 // Execute tests 653 for (Method test : tests.values()) { 654 Method verifier = getClass().getDeclaredMethod(test.getName() + "_verifier", boolean.class); 655 // Warmup using verifier method 656 for (int i = 0; i < WARMUP; ++i) { 657 verifier.invoke(this, true); 658 } 659 // Trigger compilation 660 WHITE_BOX.enqueueMethodForCompilation(test, COMP_LEVEL_FULL_OPTIMIZATION); 661 Asserts.assertTrue(!USE_COMPILER || WHITE_BOX.isMethodCompiled(test, false), test + " not compiled"); 662 // Check result 663 verifier.invoke(this, false); 664 } 665 } 666 } 667 668 // Mark method as test 669 @Retention(RetentionPolicy.RUNTIME) 670 @interface Test { 671 // Regular expression used to match forbidden IR nodes 672 // in the C2 IR emitted for this test. 673 String failOn() default ""; 674 // Regular expressions used to match and count IR nodes. 675 String[] match() default { }; 676 int[] matchCount() default { }; 677 } 678 679 // Force method inlining during compilation 680 @Retention(RetentionPolicy.RUNTIME) 681 @interface ForceInline { } |