1 /* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 // TODO add bugid and summary 25 26 /* 27 * @test 28 * @library /testlibrary /test/lib /compiler/whitebox / 29 * @build compiler.valhalla.valuetypes.ValueTypeTestBench 30 * @run main ClassFileInstaller sun.hotspot.WhiteBox 31 * @run main ClassFileInstaller jdk.test.lib.Platform 32 * @run main/othervm -noverify -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 33 * compiler.valhalla.valuetypes.ValueTypeTestBench 34 */ 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 // ========== Helper methods ========== 391 392 @DontCompile 393 public double sumValue(MyValue v) { 394 return v.x + v.y + v.z; 395 } 396 397 // ========== Test infrastructure ========== 398 399 private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); 400 private static final int COMP_LEVEL_ANY = -1; 401 private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; 402 private static final Hashtable<String, Method> tests = new Hashtable<String, Method>(); 403 private static final int WARMUP = 10; 404 405 // Regular expressions used to match nodes in the PrintIdeal output 406 private static final String START = "(\\d+\\t(.*"; 407 private static final String MID = ".*)+\\t===.*"; 408 private static final String END = ")|"; 409 private static final String ALLOC = START + "CallStaticJava" + MID + "_new_instance_Java" + END; 410 private static final String LOAD = START + "Load" + MID + "valuetype\\*" + END; 411 private static final String STORE = START + "Store" + MID + "valuetype\\*" + END; 412 private static final String LOOP = START + "Loop" + MID + "" + END; 413 private static final String TRAP = START + "CallStaticJava" + MID + "uncommon_trap" + END; 414 // TODO: match field values of scalar replaced object 415 private static final String SCOBJ = "(.*# ScObj.*" + END; 416 417 418 // Random test values 419 private static final int rI = Utils.getRandomInstance().nextInt(); 420 private static final long rL = Utils.getRandomInstance().nextLong(); 421 private static final double rD = Utils.getRandomInstance().nextDouble(); 422 423 static { 424 // Gather all test methods and put them in Hashtable 425 for (Method m : ValueTypeTestBench.class.getDeclaredMethods()) { 426 if (m.isAnnotationPresent(Test.class)) { 427 tests.put("ValueTypeTestBench::" + m.getName(), m); 428 } 429 } 430 } 431 432 public static void main(String[] args) throws Throwable { 433 if (args.length == 0) { 434 // Run tests in own process and verify output 435 OutputAnalyzer oa = ProcessTools.executeTestJvm("-noverify", 436 "-XX:+UnlockDiagnosticVMOptions", "-Xbootclasspath/a:.", "-XX:+WhiteBoxAPI", 437 "-XX:-TieredCompilation", "-XX:-BackgroundCompilation", "-XX:-UseOnStackReplacement", 438 "-XX:CompileCommand=quiet", "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly", 439 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.ValueTypeTestBench::*", 440 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue::*", 441 ValueTypeTestBench.class.getName(), "run"); 442 String output = oa.getOutput(); 443 oa.shouldHaveExitValue(0); 444 parseOutput(output); 445 } else { 446 // Execute tests 447 ValueTypeTestBench bench = new ValueTypeTestBench(); 448 bench.run(); 449 } 450 } 451 452 public static void parseOutput(String output) throws Exception { 453 String split = "b compiler.valhalla.valuetypes."; 454 String[] compilations = output.split(split); 455 // Print header 456 System.out.println(compilations[0]); 457 // Iterate over compilation output 458 for (String graph : compilations) { 459 String[] lines = graph.split("\\n"); 460 String testName = lines[0].split(" ")[0]; 461 Method test = tests.get(testName); 462 if (test == null) { 463 // Skip helper methods 464 continue; 465 } 466 if (PRINT_GRAPH) { 467 System.out.println("\nGraph for " + graph); 468 } 469 // Parse graph using regular expressions to determine if it contains forbidden nodes 470 Test anno = test.getAnnotation(Test.class); 471 String regexFail = anno.failOn(); 472 if (!regexFail.isEmpty()) { 473 Pattern pattern = Pattern.compile(regexFail.substring(0, regexFail.length()-1)); 474 Matcher matcher = pattern.matcher(graph); 475 boolean fail = false; 476 while (matcher.find()) { 477 System.out.println("Graph for '" + testName + "' contains forbidden node:"); 478 System.out.println(matcher.group()); 479 fail = true; 480 } 481 Asserts.assertFalse(fail, "Graph for '" + testName + "' contains forbidden nodes"); 482 } 483 String[] regexMatch = anno.match(); 484 int[] matchCount = anno.matchCount(); 485 for (int i = 0; i < regexMatch.length; ++i) { 486 Pattern pattern = Pattern.compile(regexMatch[i].substring(0, regexMatch[i].length()-1)); 487 Matcher matcher = pattern.matcher(graph); 488 int count = 0; 489 String nodes = ""; 490 while (matcher.find()) { 491 count++; 492 nodes += matcher.group() + "\n"; 493 } 494 if (matchCount[i] != count) { 495 System.out.println("Graph for '" + testName + "' contains different number of match nodes:"); 496 System.out.println(nodes); 497 } 498 Asserts.assertEQ(matchCount[i], count, "Graph for '" + testName + "' contains different number of match nodes"); 499 } 500 tests.remove(testName); 501 System.out.println(testName + " passed"); 502 } 503 // Check if all tests were compiled 504 if (tests.size() != 0) { 505 for (String name : tests.keySet()) { 506 System.out.println("Test '" + name + "' not compiled!"); 507 } 508 throw new RuntimeException("Not all tests were compiled"); 509 } 510 } 511 512 public void setup(Method[] methods) { 513 for (Method m : methods) { 514 if (m.isAnnotationPresent(Test.class)) { 515 // Don't inline tests 516 WHITE_BOX.testSetDontInlineMethod(m, true); 517 } 518 if (m.isAnnotationPresent(DontCompile.class)) { 519 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, true); 520 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, false); 521 } 522 if (m.isAnnotationPresent(ForceInline.class)) { 523 WHITE_BOX.testSetForceInlineMethod(m, true); 524 } else if (m.isAnnotationPresent(DontInline.class)) { 525 WHITE_BOX.testSetDontInlineMethod(m, true); 526 } 527 } 528 } 529 530 public void run() throws Exception { 531 System.out.format("rI = %d, rL = %d, rD = %f\n", rI, rL, rD); 532 setup(this.getClass().getDeclaredMethods()); 533 setup(MyValue.class.getDeclaredMethods()); 534 535 // Execute tests 536 for (Method test : tests.values()) { 537 Method verifier = getClass().getDeclaredMethod(test.getName() + "_verifier", boolean.class); 538 // Warmup using verifier method 539 for (int i = 0; i < WARMUP; ++i) { 540 verifier.invoke(this, true); 541 } 542 // Trigger compilation 543 WHITE_BOX.enqueueMethodForCompilation(test, COMP_LEVEL_FULL_OPTIMIZATION); 544 Asserts.assertTrue(WHITE_BOX.isMethodCompiled(test, false)); 545 // Check result 546 verifier.invoke(this, false); 547 } 548 } 549 } 550 551 // Mark method as test 552 @Retention(RetentionPolicy.RUNTIME) 553 @interface Test { 554 // Regular expression used to match forbidden IR nodes 555 // in the C2 IR emitted for this test. 556 String failOn() default ""; 557 // Regular expressions used to match and count IR nodes. 558 String[] match() default { }; 559 int[] matchCount() default { }; 560 } 561 562 // Force method inlining during compilation 563 @Retention(RetentionPolicy.RUNTIME) 564 @interface ForceInline { } 565 566 // Prevent method inlining during compilation 567 @Retention(RetentionPolicy.RUNTIME) 568 @interface DontInline { } 569 570 // Prevent method compilation 571 @Retention(RetentionPolicy.RUNTIME) 572 @interface DontCompile { }