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.
 376     @Test(match = {ALLOC}, matchCount = {2})
 377     public String test17() {
 378         MyValue v = MyValue.createInline(259, 515L, 1027.0);
 379         String s = v.toStringInline();
 380         return s;
 381     }
 382 
 383     @DontCompile
 384     public void test17_verifier(boolean b) {
 385         String s = test17();
 386         System.out.println("Result is: " + s);
 387     }
 388 
 389     // ========== Helper methods ==========
 390 
 391     @DontCompile
 392     public double sumValue(MyValue v) {
 393         return v.x + v.y + v.z;
 394     }    
 395 
 396     // ========== Test infrastructure ==========
 397 
 398     private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
 399     private static final int COMP_LEVEL_ANY = -1;
 400     private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4;
 401     private static final Hashtable<String, Method> tests = new Hashtable<String, Method>();
 402     private static final int WARMUP = 10;
 403 
 404     // Regular expressions used  to match nodes in the PrintIdeal output
 405     private static final String START = "(\\d+\\t(.*";
 406     private static final String MID = ".*)+\\t===.*";
 407     private static final String END = ")|";
 408     private static final String ALLOC = START + "CallStaticJava" + MID + "_new_instance_Java" + END;
 409     private static final String LOAD  = START + "Load" + MID + "valuetype\\*" + END;
 410     private static final String STORE = START + "Store" + MID + "valuetype\\*" + END;
 411     private static final String LOOP  = START + "Loop" + MID + "" + END;
 412     private static final String TRAP  = START + "CallStaticJava" + MID + "uncommon_trap" + END;
 413     // TODO: match field values of scalar replaced object
 414     private static final String SCOBJ = "(.*# ScObj.*" + END;
 415 
 416 
 417     // Random test values
 418     private static final int    rI = Utils.getRandomInstance().nextInt();
 419     private static final long   rL = Utils.getRandomInstance().nextLong();
 420     private static final double rD = Utils.getRandomInstance().nextDouble();
 421 
 422     static {
 423         // Gather all test methods and put them in Hashtable
 424         for (Method m : ValueTypeTestBench.class.getDeclaredMethods()) {
 425             if (m.isAnnotationPresent(Test.class)) {
 426                 tests.put("ValueTypeTestBench::" + m.getName(), m);
 427             }
 428         }
 429     }
 430 
 431     public static void main(String[] args) throws Throwable {
 432         if (args.length == 0) {
 433             // Run tests in own process and verify output
 434             OutputAnalyzer oa = ProcessTools.executeTestJvm("-noverify",
 435                                                             "-XX:+UnlockDiagnosticVMOptions", "-Xbootclasspath/a:.", "-XX:+WhiteBoxAPI",
 436                                                             "-XX:-TieredCompilation", "-XX:-BackgroundCompilation", "-XX:-UseOnStackReplacement",
 437                                                             "-XX:CompileCommand=quiet", "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly",
 438                                                             "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.ValueTypeTestBench::*",
 439                                                             "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue::*",
 440                                                             ValueTypeTestBench.class.getName(), "run");
 441             String output = oa.getOutput();
 442             oa.shouldHaveExitValue(0);
 443             parseOutput(output);
 444         } else {
 445             // Execute tests
 446             ValueTypeTestBench bench = new ValueTypeTestBench();
 447             bench.run();
 448         }
 449     }
 450 
 451     public static void parseOutput(String output) throws Exception {
 452         String split = "b        compiler.valhalla.valuetypes.";
 453         String[] compilations = output.split(split);
 454         // Print header
 455         System.out.println(compilations[0]);
 456         // Iterate over compilation output
 457         for (String graph : compilations) {
 458             String[] lines = graph.split("\\n");
 459             String testName = lines[0].split(" ")[0];
 460             Method test = tests.get(testName);
 461             if (test == null) {
 462                 // Skip helper methods
 463                 continue;
 464             }
 465             if (PRINT_GRAPH) {
 466                 System.out.println("\nGraph for " + graph);
 467             }
 468             // Parse graph using regular expressions to determine if it contains forbidden nodes
 469             Test anno = test.getAnnotation(Test.class);
 470             String regexFail = anno.failOn();
 471             if (!regexFail.isEmpty()) {
 472                 Pattern pattern = Pattern.compile(regexFail.substring(0, regexFail.length()-1));
 473                 Matcher matcher = pattern.matcher(graph);
 474                 boolean fail = false;
 475                 while (matcher.find()) {
 476                     System.out.println("Graph for '" + testName + "' contains forbidden node:");
 477                     System.out.println(matcher.group());
 478                     fail = true;
 479                 }
 480                 Asserts.assertFalse(fail, "Graph for '" + testName + "' contains forbidden nodes");
 481             }
 482             String[] regexMatch = anno.match();
 483             int[] matchCount = anno.matchCount();
 484             for (int i = 0; i < regexMatch.length; ++i) {
 485                 Pattern pattern = Pattern.compile(regexMatch[i].substring(0, regexMatch[i].length()-1));
 486                 Matcher matcher = pattern.matcher(graph);
 487                 int count = 0;
 488                 String nodes = "";
 489                 while (matcher.find()) {
 490                     count++;
 491                     nodes += matcher.group() + "\n";
 492                 }
 493                 if (matchCount[i] != count) {
 494                     System.out.println("Graph for '" + testName + "' contains different number of match nodes:");
 495                     System.out.println(nodes);
 496                 }
 497                 Asserts.assertEQ(matchCount[i], count, "Graph for '" + testName + "' contains different number of match nodes");
 498             }
 499             tests.remove(testName);
 500             System.out.println(testName + " passed");
 501         }
 502         // Check if all tests were compiled
 503         if (tests.size() != 0) {
 504             for (String name : tests.keySet()) {
 505                 System.out.println("Test '" + name + "' not compiled!");
 506             }
 507             throw new RuntimeException("Not all tests were compiled");
 508         }
 509     }
 510 
 511     public void setup(Method[] methods) {
 512         for (Method m : methods) {
 513             if (m.isAnnotationPresent(Test.class)) {
 514                 // Don't inline tests
 515                 WHITE_BOX.testSetDontInlineMethod(m, true);
 516             }
 517             if (m.isAnnotationPresent(DontCompile.class)) {
 518                 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, true);
 519                 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, false);
 520             }
 521             if (m.isAnnotationPresent(ForceInline.class)) {
 522                 WHITE_BOX.testSetForceInlineMethod(m, true);
 523             } else if (m.isAnnotationPresent(DontInline.class)) {
 524                 WHITE_BOX.testSetDontInlineMethod(m, true);
 525             }
 526         }
 527     }
 528 
 529     public void run() throws Exception {
 530         System.out.format("rI = %d, rL = %d, rD = %f\n", rI, rL, rD);
 531         setup(this.getClass().getDeclaredMethods());
 532         setup(MyValue.class.getDeclaredMethods());
 533 
 534         // Execute tests
 535         for (Method test : tests.values()) {
 536             Method verifier = getClass().getDeclaredMethod(test.getName() + "_verifier", boolean.class);
 537             // Warmup using verifier method
 538             for (int i = 0; i < WARMUP; ++i) {
 539                 verifier.invoke(this, true);
 540             }
 541             // Trigger compilation
 542             WHITE_BOX.enqueueMethodForCompilation(test, COMP_LEVEL_FULL_OPTIMIZATION);
 543             Asserts.assertTrue(WHITE_BOX.isMethodCompiled(test, false));
 544             // Check result
 545             verifier.invoke(this, false);
 546         }
 547     }
 548 }
 549 
 550 // Mark method as test
 551 @Retention(RetentionPolicy.RUNTIME)
 552 @interface Test {
 553     // Regular expression used to match forbidden IR nodes
 554     // in the C2 IR emitted for this test.
 555     String failOn() default "";
 556     // Regular expressions used to match and count IR nodes.
 557     String[] match() default { };
 558     int[] matchCount() default { };
 559 }
 560 
 561 // Force method inlining during compilation
 562 @Retention(RetentionPolicy.RUNTIME)
 563 @interface ForceInline { }
 564 
 565 // Prevent method inlining during compilation
 566 @Retention(RetentionPolicy.RUNTIME)
 567 @interface DontInline { }
 568 
 569 // Prevent method compilation
 570 @Retention(RetentionPolicy.RUNTIME)
 571 @interface DontCompile { }