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 { }