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 78 public class ValueTypeTestBench { 79 // Print ideal graph after execution of each test 80 private static final boolean PRINT_GRAPH = true; 81 82 // ========== Test definitions ========== 83 84 // Receive value type through call to interpreter 85 @Test(failOn = ALLOC + STORE) 86 public double test1() { 87 MyValue v = MyValue.createDontInline(rI, rL, rD); 88 return v.x + v.y + v.z; 89 } 90 91 @DontCompile 92 public void test1_verifier(boolean warmup) { 93 double result = test1(); 94 Asserts.assertEQ(result, rI + rL + rD); 95 } 96 97 // Receive value type from interpreter via parameter 98 @Test(failOn = ALLOC + STORE) 99 public double test2(MyValue v) { 100 return v.x + v.y + v.z; 101 } 102 103 @DontCompile 104 public void test2_verifier(boolean warmup) { 105 MyValue v = MyValue.createDontInline(rI, rL, rD); 106 double result = test2(v); 107 Asserts.assertEQ(result, rI + rL + rD); 108 } 109 110 // Return incoming value type without accessing fields 111 @Test(failOn = ALLOC + LOAD + STORE) 112 public MyValue test3(MyValue v) { 113 return v; 114 } 115 116 @DontCompile 117 public void test3_verifier(boolean warmup) { 118 MyValue v1 = MyValue.createDontInline(rI, rL, rD); 119 MyValue v2 = test3(v1); 120 Asserts.assertEQ(v1.x, v2.x); 121 Asserts.assertEQ(v1.y, v2.y); 122 Asserts.assertEQ(v1.z, v2.z); 123 } 124 125 // Create a value type in compiled code and only use fields. 126 // Allocation should go away because value type does not escape. 127 @Test(failOn = ALLOC + LOAD + STORE) 128 public double test4() { 129 MyValue v = MyValue.createInline(rI, rL, rD); 130 return v.x + v.y + v.z; 131 } 132 133 @DontCompile 134 public void test4_verifier(boolean warmup) { 135 double result = test4(); 136 Asserts.assertEQ(result, rI + rL + rD); 137 } 138 139 // Create a value type in compiled code and pass it to 140 // an inlined compiled method via a call. 141 @Test(failOn = ALLOC + LOAD + STORE) 142 public double test5() { 143 MyValue v = MyValue.createInline(rI, rL, rD); 144 return test5Inline(v); 145 } 146 147 @ForceInline 148 public double test5Inline(MyValue v) { 149 return v.x + v.y + v.z; 150 } 151 152 @DontCompile 153 public void test5_verifier(boolean warmup) { 154 double result = test5(); 155 Asserts.assertEQ(result, rI + rL + rD); 156 } 157 158 // Create a value type in compiled code and pass it to 159 // the interpreter via a call. 160 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) 161 public double test6() { 162 MyValue v = MyValue.createInline(rI, rL, rD); 163 // Pass to interpreter 164 return sumValue(v); 165 } 166 167 @DontCompile 168 public void test6_verifier(boolean warmup) { 169 double result = test6(); 170 Asserts.assertEQ(result, rI + rL + rD); 171 } 172 173 // Create a value type in compiled code and pass it to 174 // the interpreter by returning. 175 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD) 176 public MyValue test7(int x, long y, double z) { 177 return MyValue.createInline(x, y, z); 178 } 179 180 @DontCompile 181 public void test7_verifier(boolean warmup) { 182 MyValue v = test7(rI, rL, rD); 183 double result = v.x + v.y + v.z; 184 Asserts.assertEQ(result, rI + rL + rD); 185 } 186 187 // Merge value types created from two branches 188 @Test(failOn = ALLOC + STORE) 189 public double test8(boolean b) { 190 MyValue v; 191 if (b) { 192 v = MyValue.createInline(rI, rL, rD); 193 } else { 194 v = MyValue.createDontInline(rI + 1, rL + 1, rD + 1); 195 } 196 return v.x + v.y + v.z; 197 } 198 199 @DontCompile 200 public void test8_verifier(boolean warmup) { 201 Asserts.assertEQ(test8(true), rI + rL + rD); 202 Asserts.assertEQ(test8(false), rI + 1 + rL + 1 + ((double)rD + 1)); 203 } 204 205 // Merge value types created from two branches 206 @Test(match = {ALLOC, STORE}, matchCount = {1, 3}, failOn = LOAD) 207 public MyValue test9(boolean b) { 208 MyValue v; 209 if (b) { 210 // Value type is not allocated 211 v = MyValue.createInline(rI, rL, rD); 212 } else { 213 // Value type is allocated by the callee 214 v = MyValue.createDontInline(rI + 1, rL + 1, rD + 1); 215 } 216 // Need to allocate value type if 'b' is true 217 double sum = sumValue(v); 218 if (b) { 219 v = MyValue.createDontInline(rI, rL, sum); 220 } else { 221 v = MyValue.createDontInline(rI, rL, sum + 1); 222 } 223 // Don't need to allocate value type because both branches allocate 224 return v; 225 } 226 227 @DontCompile 228 public void test9_verifier(boolean warmup) { 229 MyValue v = test9(true); 230 Asserts.assertEQ(v.x, rI); 231 Asserts.assertEQ(v.y, rL); 232 Asserts.assertEQ(v.z, rI + rL + rD); 233 234 v = test9(false); 235 Asserts.assertEQ(v.x, rI); 236 Asserts.assertEQ(v.y, rL); 237 Asserts.assertEQ(v.z, rI + rL + ((double)rD + 1)); 238 } 239 240 // Merge value types created in a loop (not inlined) 241 @Test(failOn = ALLOC + STORE) 242 public double test10(int x, long y, double z) { 243 MyValue v = MyValue.createDontInline(x, y, z); 244 for (int i = 0; i < 10; ++i) { 245 v = MyValue.createDontInline(v.x + 1, v.y + 1, v.z + 1); 246 } 247 return v.x + v.y + v.z; 248 } 249 250 @DontCompile 251 public void test10_verifier(boolean warmup) { 252 double result = test10(rI, rL, rD); 253 Asserts.assertEQ(result, rI + rL + 20 + ((double)rD + 10)); 254 } 255 256 // Merge value types created in a loop (inlined) 257 @Test(failOn = ALLOC + LOAD + STORE + LOOP) 258 public double test11(int x, long y, double z) { 259 MyValue v = MyValue.createInline(x, y, z); 260 for (int i = 0; i < 10; ++i) { 261 v = MyValue.createInline(v.x + 1, v.y + 1, v.z + 1); 262 } 263 return v.x + v.y + v.z; 264 } 265 266 @DontCompile 267 public void test11_verifier(boolean warmup) { 268 double result = test11(rI, rL, rD); 269 Asserts.assertEQ(result, rI + rL + 20 + ((double)rD + 10)); 270 } 271 272 // Test loop with uncommon trap referencing a value type 273 @Test(match = {TRAP, SCOBJ}, matchCount = {1, 1}, failOn = ALLOC + LOAD + STORE) 274 public double test12(boolean b) { 275 MyValue v = MyValue.createInline(rI, rL, rD); 276 double result = 42; 277 for (int i = 0; i < 1000; ++i) { 278 if (b) { 279 result += v.x; 280 } else { 281 // Uncommon trap referencing v. We delegate allocation to the 282 // interpreter by adding a SafePointScalarObjectNode. 283 result = sumValue(v); 284 } 285 } 286 return result; 287 } 288 289 @DontCompile 290 public void test12_verifier(boolean warmup) { 291 double result = test12(warmup); 292 Asserts.assertEQ(result, warmup ? 42 + (1000*(double)rI) : (rI + rL + rD)); 293 } 294 295 // Test loop with uncommon trap referencing a value type 296 @Test(match = {TRAP, LOAD}, matchCount = {1, 1}, failOn = ALLOC + STORE + SCOBJ) 297 public double test13(boolean b) { 298 MyValue v = MyValue.createDontInline(rI, rL, rD); 299 double result = 42; 300 for (int i = 0; i < 1000; ++i) { 301 if (b) { 302 result += v.x; 303 } else { 304 // Uncommon trap referencing v. Should not allocate 305 // but just pass the existing oop to the uncommon trap. 306 result = sumValue(v); 307 } 308 } 309 return result; 310 } 311 312 @DontCompile 313 public void test13_verifier(boolean warmup) { 314 double result = test13(warmup); 315 Asserts.assertEQ(result, warmup ? 42 + (1000*(double)rI) : (rI + rL + rD)); 316 } 317 318 319 // ========== Helper methods ========== 320 321 @DontCompile 322 public double sumValue(MyValue v) { 323 return v.x + v.y + v.z; 324 } 325 326 // ========== Test infrastructure ========== 327 328 private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); 329 private static final int COMP_LEVEL_ANY = -1; 330 private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; 331 private static final Hashtable<String, Method> tests = new Hashtable<String, Method>(); 332 private static final int WARMUP = 10; 333 334 // Regular expressions used to match nodes in the PrintIdeal output 335 private static final String START = "(\\d+\\t(.*"; 336 private static final String MID = ".*)+\\t===.*"; 337 private static final String END = ")|"; 338 private static final String ALLOC = START + "CallStaticJava" + MID + "_new_instance_Java" + END; 339 private static final String LOAD = START + "Load" + MID + "valuetype\\*" + END; 340 private static final String STORE = START + "Store" + MID + "valuetype\\*" + END; 341 private static final String LOOP = START + "Loop" + MID + "" + END; 342 private static final String TRAP = START + "CallStaticJava" + MID + "uncommon_trap" + END; 343 // TODO: match field values of scalar replaced object 344 private static final String SCOBJ = "(.*# ScObj.*" + END; 345 346 347 // Random test values 348 private static final int rI = Utils.getRandomInstance().nextInt(); 349 private static final long rL = Utils.getRandomInstance().nextLong(); 350 private static final double rD = Utils.getRandomInstance().nextDouble(); 351 352 static { 353 // Gather all test methods and put them in Hashtable 354 for (Method m : ValueTypeTestBench.class.getDeclaredMethods()) { 355 if (m.isAnnotationPresent(Test.class)) { 356 tests.put("ValueTypeTestBench::" + m.getName(), m); 357 } 358 } 359 } 360 361 public static void main(String[] args) throws Throwable { 362 if (args.length == 0) { 363 // Run tests in own process and verify output 364 OutputAnalyzer oa = ProcessTools.executeTestJvm("-noverify", 365 "-XX:+UnlockDiagnosticVMOptions", "-Xbootclasspath/a:.", "-XX:+WhiteBoxAPI", 366 "-XX:-TieredCompilation", "-XX:-BackgroundCompilation", "-XX:-UseOnStackReplacement", 367 "-XX:CompileCommand=quiet", "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly", 368 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.ValueTypeTestBench::*", 369 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue::*", 370 ValueTypeTestBench.class.getName(), "run"); 371 String output = oa.getOutput(); 372 oa.shouldHaveExitValue(0); 373 parseOutput(output); 374 } else { 375 // Execute tests 376 ValueTypeTestBench bench = new ValueTypeTestBench(); 377 bench.run(); 378 } 379 } 380 381 public static void parseOutput(String output) throws Exception { 382 String split = "b compiler.valhalla.valuetypes."; 383 String[] compilations = output.split(split); 384 // Print header 385 System.out.println(compilations[0]); 386 // Iterate over compilation output 387 for (String graph : compilations) { 388 String[] lines = graph.split("\\n"); 389 String testName = lines[0].split(" ")[0]; 390 Method test = tests.get(testName); 391 if (test == null) { 392 // Skip helper methods 393 continue; 394 } 395 if (PRINT_GRAPH) { 396 System.out.println("\nGraph for " + graph); 397 } 398 // Parse graph using regular expressions to determine if it contains forbidden nodes 399 Test anno = test.getAnnotation(Test.class); 400 String regexFail = anno.failOn(); 401 if (!regexFail.isEmpty()) { 402 Pattern pattern = Pattern.compile(regexFail.substring(0, regexFail.length()-1)); 403 Matcher matcher = pattern.matcher(graph); 404 boolean fail = false; 405 while (matcher.find()) { 406 System.out.println("Graph for '" + testName + "' contains forbidden node:"); 407 System.out.println(matcher.group()); 408 fail = true; 409 } 410 Asserts.assertFalse(fail, "Graph for '" + testName + "' contains forbidden nodes"); 411 } 412 String[] regexMatch = anno.match(); 413 int[] matchCount = anno.matchCount(); 414 for (int i = 0; i < regexMatch.length; ++i) { 415 Pattern pattern = Pattern.compile(regexMatch[i].substring(0, regexMatch[i].length()-1)); 416 Matcher matcher = pattern.matcher(graph); 417 int count = 0; 418 String nodes = ""; 419 while (matcher.find()) { 420 count++; 421 nodes += matcher.group() + "\n"; 422 } 423 if (matchCount[i] != count) { 424 System.out.println("Graph for '" + testName + "' contains different number of match nodes:"); 425 System.out.println(nodes); 426 } 427 Asserts.assertEQ(matchCount[i], count, "Graph for '" + testName + "' contains different number of match nodes"); 428 } 429 tests.remove(testName); 430 System.out.println(testName + " passed"); 431 } 432 // Check if all tests were compiled 433 if (tests.size() != 0) { 434 for (String name : tests.keySet()) { 435 System.out.println("Test '" + name + "' not compiled!"); 436 } 437 throw new RuntimeException("Not all tests were compiled"); 438 } 439 } 440 441 public void setup(Method[] methods) { 442 for (Method m : methods) { 443 if (m.isAnnotationPresent(Test.class)) { 444 // Don't inline tests 445 WHITE_BOX.testSetDontInlineMethod(m, true); 446 } 447 if (m.isAnnotationPresent(DontCompile.class)) { 448 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, true); 449 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, false); 450 } 451 if (m.isAnnotationPresent(ForceInline.class)) { 452 WHITE_BOX.testSetForceInlineMethod(m, true); 453 } else if (m.isAnnotationPresent(DontInline.class)) { 454 WHITE_BOX.testSetDontInlineMethod(m, true); 455 } 456 } 457 } 458 459 public void run() throws Exception { 460 System.out.format("rI = %d, rL = %d, rD = %f\n", rI, rL, rD); 461 setup(this.getClass().getDeclaredMethods()); 462 setup(MyValue.class.getDeclaredMethods()); 463 464 // Execute tests 465 for (Method test : tests.values()) { 466 Method verifier = getClass().getDeclaredMethod(test.getName() + "_verifier", boolean.class); 467 // Warmup using verifier method 468 for (int i = 0; i < WARMUP; ++i) { 469 verifier.invoke(this, true); 470 } 471 // Trigger compilation 472 WHITE_BOX.enqueueMethodForCompilation(test, COMP_LEVEL_FULL_OPTIMIZATION); 473 Asserts.assertTrue(WHITE_BOX.isMethodCompiled(test, false)); 474 // Check result 475 verifier.invoke(this, false); 476 } 477 } 478 } 479 480 // Mark method as test 481 @Retention(RetentionPolicy.RUNTIME) 482 @interface Test { 483 // Regular expression used to match forbidden IR nodes 484 // in the C2 IR emitted for this test. 485 String failOn() default ""; 486 // Regular expressions used to match and count IR nodes. 487 String[] match() default { }; 488 int[] matchCount() default { }; 489 } 490 491 // Force method inlining during compilation 492 @Retention(RetentionPolicy.RUNTIME) 493 @interface ForceInline { } 494 495 // Prevent method inlining during compilation 496 @Retention(RetentionPolicy.RUNTIME) 497 @interface DontInline { } 498 499 // Prevent method compilation 500 @Retention(RetentionPolicy.RUNTIME) 501 @interface DontCompile { }