1 /*
   2  * Copyright (c) 2017, 2018, 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 package compiler.valhalla.valuetypes;
  25 
  26 import compiler.whitebox.CompilerWhiteBoxTest;
  27 import jdk.test.lib.Asserts;
  28 import jdk.test.lib.management.InputArguments;
  29 import jdk.test.lib.Platform;
  30 import jdk.test.lib.process.ProcessTools;
  31 import jdk.test.lib.process.OutputAnalyzer;
  32 import jdk.test.lib.Utils;
  33 import sun.hotspot.WhiteBox;
  34 
  35 import java.lang.annotation.Retention;
  36 import java.lang.annotation.RetentionPolicy;
  37 import java.lang.annotation.Repeatable;
  38 import java.lang.invoke.*;
  39 import java.lang.reflect.Method;
  40 import java.util.ArrayList;
  41 import java.util.Arrays;
  42 import java.util.Hashtable;
  43 import java.util.LinkedHashMap;
  44 import java.util.List;
  45 import java.util.Map;
  46 import java.util.regex.Matcher;
  47 import java.util.regex.Pattern;
  48 import java.util.TreeMap;
  49 
  50 // Mark method as test
  51 @Retention(RetentionPolicy.RUNTIME)
  52 @Repeatable(Tests.class)
  53 @interface Test {
  54     // Regular expression used to match forbidden IR nodes
  55     // in the C2 IR emitted for this test.
  56     String failOn() default "";
  57     // Regular expressions used to match and count IR nodes.
  58     String[] match() default { };
  59     int[] matchCount() default { };
  60     int valid() default ValueTypeTest.AllFlags;
  61 }
  62 
  63 @Retention(RetentionPolicy.RUNTIME)
  64 @interface Tests {
  65     Test[] value();
  66 }
  67 
  68 // Force method inlining during compilation
  69 @Retention(RetentionPolicy.RUNTIME)
  70 @interface ForceInline { }
  71 
  72 // Prevent method inlining during compilation
  73 @Retention(RetentionPolicy.RUNTIME)
  74 @interface DontInline { }
  75 
  76 // Prevent method compilation
  77 @Retention(RetentionPolicy.RUNTIME)
  78 @interface DontCompile { }
  79 
  80 // Number of warmup iterations
  81 @Retention(RetentionPolicy.RUNTIME)
  82 @interface Warmup {
  83     int value();
  84 }
  85 
  86 public abstract class ValueTypeTest {
  87     // Random test values
  88     public static final int  rI = Utils.getRandomInstance().nextInt() % 1000;
  89     public static final long rL = Utils.getRandomInstance().nextLong() % 1000;
  90 
  91     // User defined settings
  92     private static final boolean PRINT_GRAPH = true;
  93     private static final boolean PRINT_TIMES = Boolean.parseBoolean(System.getProperty("PrintTimes", "false"));
  94     private static       boolean VERIFY_IR = Boolean.parseBoolean(System.getProperty("VerifyIR", "true"));
  95     private static final boolean VERIFY_VM = Boolean.parseBoolean(System.getProperty("VerifyVM", "false"));
  96     private static final String TESTLIST = System.getProperty("Testlist", "");
  97     private static final String EXCLUDELIST = System.getProperty("Exclude", "");
  98     private static final int WARMUP = Integer.parseInt(System.getProperty("Warmup", "251"));
  99     private static final boolean DUMP_REPLAY = Boolean.parseBoolean(System.getProperty("DumpReplay", "false"));
 100 
 101     // Pre-defined settings
 102     private static final List<String> defaultFlags = Arrays.asList(
 103         "-XX:-BackgroundCompilation", "-XX:CICompilerCount=1",
 104         "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly",
 105         "-XX:CompileCommand=quiet",
 106         "-XX:CompileCommand=compileonly,java.lang.invoke.*::*",
 107         "-XX:CompileCommand=compileonly,java.lang.Long::sum",
 108         "-XX:CompileCommand=compileonly,java.lang.Object::<init>",
 109         "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.*::*");
 110     private static final List<String> verifyFlags = Arrays.asList(
 111         "-XX:+VerifyOops", "-XX:+VerifyStack", "-XX:+VerifyLastFrame", "-XX:+VerifyBeforeGC", "-XX:+VerifyAfterGC",
 112         "-XX:+VerifyDuringGC", "-XX:+VerifyAdapterSharing", "-XX:+StressValueTypeReturnedAsFields");
 113 
 114     protected static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
 115     protected static final int ValueTypePassFieldsAsArgsOn = 0x1;
 116     protected static final int ValueTypePassFieldsAsArgsOff = 0x2;
 117     protected static final int ValueTypeArrayFlattenOn = 0x4;
 118     protected static final int ValueTypeArrayFlattenOff = 0x8;
 119     protected static final int ValueTypeReturnedAsFieldsOn = 0x10;
 120     protected static final int ValueTypeReturnedAsFieldsOff = 0x20;
 121     static final int AllFlags = ValueTypePassFieldsAsArgsOn | ValueTypePassFieldsAsArgsOff | ValueTypeArrayFlattenOn | ValueTypeArrayFlattenOff | ValueTypeReturnedAsFieldsOn;
 122     protected static final boolean ValueTypePassFieldsAsArgs = (Boolean)WHITE_BOX.getVMFlag("ValueTypePassFieldsAsArgs");
 123     protected static final boolean ValueTypeArrayFlatten = (Boolean)WHITE_BOX.getVMFlag("ValueArrayFlatten");
 124     protected static final boolean ValueTypeReturnedAsFields = (Boolean)WHITE_BOX.getVMFlag("ValueTypeReturnedAsFields");
 125     protected static final int COMP_LEVEL_ANY = -2;
 126     protected static final int COMP_LEVEL_FULL_OPTIMIZATION = 4;
 127     protected static final Hashtable<String, Method> tests = new Hashtable<String, Method>();
 128     protected static final boolean USE_COMPILER = WHITE_BOX.getBooleanVMFlag("UseCompiler");
 129     protected static final boolean PRINT_IDEAL  = WHITE_BOX.getBooleanVMFlag("PrintIdeal");
 130     protected static final boolean XCOMP = Platform.isComp();
 131 
 132     // Regular expressions used to match nodes in the PrintIdeal output
 133     protected static final String START = "(\\d+\\t(.*";
 134     protected static final String MID = ".*)+\\t===.*";
 135     protected static final String END = ")|";
 136     protected static final String ALLOC  = "(.*precise klass compiler/valhalla/valuetypes/MyValue.*\\R(.*(nop|spill).*\\R)*.*_new_instance_Java" + END;
 137     protected static final String ALLOCA = "(.*precise klass \\[Lcompiler/valhalla/valuetypes/MyValue.*\\R(.*(nop|spill).*\\R)*.*_new_array_Java" + END;
 138     protected static final String LOAD   = START + "Load(B|S|I|L|F|D|P|N)" + MID + "@compiler/valhalla/valuetypes/MyValue.*" + END;
 139     protected static final String LOADK  = START + "LoadK" + MID + END;
 140     protected static final String STORE  = START + "Store(B|S|I|L|F|D|P|N)" + MID + "@compiler/valhalla/valuetypes/MyValue.*" + END;
 141     protected static final String LOOP   = START + "Loop" + MID + "" + END;
 142     protected static final String TRAP   = START + "CallStaticJava" + MID + "uncommon_trap.*(unstable_if|predicate)" + END;
 143     protected static final String RETURN = START + "Return" + MID + "returns" + END;
 144     protected static final String LINKTOSTATIC = START + "CallStaticJava" + MID + "linkToStatic" + END;
 145     protected static final String NPE = START + "CallStaticJava" + MID + "null_check" + END;
 146     protected static final String CALL = START + "CallStaticJava" + MID + END;
 147     protected static final String STOREVALUETYPEFIELDS = START + "CallStaticJava" + MID + "store_value_type_fields" + END;
 148     protected static final String SCOBJ = "(.*# ScObj.*" + END;
 149 
 150 
 151     protected ValueTypeTest() {
 152         List<String> list = null;
 153         List<String> exclude = null;
 154         if (!TESTLIST.isEmpty()) {
 155            list = Arrays.asList(TESTLIST.split(","));
 156         }
 157         if (!EXCLUDELIST.isEmpty()) {
 158            exclude = Arrays.asList(EXCLUDELIST.split(","));
 159         }
 160         // Gather all test methods and put them in Hashtable
 161         for (Method m : getClass().getMethods()) {
 162             Test[] annos = m.getAnnotationsByType(Test.class);
 163             if (annos.length != 0 &&
 164                 ((list == null || list.contains(m.getName())) && (exclude == null || !exclude.contains(m.getName())))) {
 165                 tests.put(getClass().getSimpleName() + "::" + m.getName(), m);
 166             }
 167         }
 168     }
 169 
 170     protected void run(String[] args, Class<?>... classes) throws Throwable {
 171         if (args.length == 0) {
 172             // Spawn a new VM instance
 173             execute_vm();
 174         } else {
 175             // Execute tests
 176             run(classes);
 177         }
 178     }
 179 
 180     private void execute_vm() throws Throwable {
 181         Asserts.assertFalse(tests.isEmpty(), "no tests to execute");
 182         ArrayList<String> args = new ArrayList<String>(defaultFlags);
 183         String[] vmInputArgs = InputArguments.getVmInputArgs();
 184         if (VERIFY_IR) {
 185             for (String arg : vmInputArgs) {
 186                 if (arg.startsWith("-XX:CompileThreshold")) {
 187                     // Disable IR verification if
 188                     VERIFY_IR = false;
 189                 }
 190                 // Check if the JVM supports value type specific default arguments from the test's run commands
 191                 if (arg.startsWith("-XX:+ValueTypePassFieldsAsArgs") ||
 192                     arg.startsWith("-XX:+ValueTypeReturnedAsFields")) {
 193                     Boolean value = (Boolean)WHITE_BOX.getVMFlag(arg.substring(5));
 194                     if (!value) {
 195                         System.out.println("WARNING: could not enable " + arg.substring(5) + ". Skipping IR verification.");
 196                         VERIFY_IR = false;
 197                     }
 198                 } else if (arg.startsWith("-XX:-ValueTypePassFieldsAsArgs") ||
 199                            arg.startsWith("-XX:-ValueTypeReturnedAsFields")) {
 200                     Boolean value = (Boolean)WHITE_BOX.getVMFlag(arg.substring(5));
 201                     if (value) {
 202                         System.out.println("WARNING: could not disable " + arg.substring(5) + ". Skipping IR verification.");
 203                         VERIFY_IR = false;
 204                     }
 205                 }
 206             }
 207             // Always trap for exception throwing to not confuse IR verification
 208             args.add("-XX:-OmitStackTraceInFastThrow");
 209         }
 210         if (VERIFY_VM) {
 211             args.addAll(verifyFlags);
 212         }
 213         // Run tests in own process and verify output
 214         args.add(getClass().getName());
 215         args.add("run");
 216         // Spawn process with default JVM options from the test's run command
 217         String[] cmds = Arrays.copyOf(vmInputArgs, vmInputArgs.length + args.size());
 218         System.arraycopy(args.toArray(), 0, cmds, vmInputArgs.length, args.size());
 219         OutputAnalyzer oa = ProcessTools.executeTestJvm(cmds);
 220         // If ideal graph printing is enabled/supported, verify output
 221         String output = oa.getOutput();
 222         oa.shouldHaveExitValue(0);
 223         if (VERIFY_IR) {
 224             if (output.contains("PrintIdeal enabled")) {
 225                 parseOutput(output);
 226             } else {
 227                 System.out.println(output);
 228                 System.out.println("WARNING: IR verification failed! Running with -Xint, -Xcomp or release build?");
 229             }
 230         }
 231     }
 232 
 233     private void parseOutput(String output) throws Exception {
 234         Pattern comp_re = Pattern.compile("\\n\\s+\\d+\\s+\\d+\\s+(%| )(s| )(!| )b(n| )\\s+\\S+\\.(?<name>[^.]+::\\S+)\\s+(?<osr>@ \\d+\\s+)?[(]\\d+ bytes[)]\\n");
 235         Matcher m = comp_re.matcher(output);
 236         Map<String,String> compilations = new LinkedHashMap<>();
 237         int prev = 0;
 238         String methodName = null;
 239         while (m.find()) {
 240             if (prev == 0) {
 241                 // Print header
 242                 System.out.print(output.substring(0, m.start()+1));
 243             } else if (methodName != null) {
 244                 compilations.put(methodName, output.substring(prev, m.start()+1));
 245             }
 246             if (m.group("osr") != null) {
 247                 methodName = null;
 248             } else {
 249                 methodName = m.group("name");
 250             }
 251             prev = m.end();
 252         }
 253         if (prev == 0) {
 254             // Print header
 255             System.out.print(output);
 256         } else if (methodName != null) {
 257             compilations.put(methodName, output.substring(prev));
 258         }
 259         // Iterate over compilation output
 260         for (String testName : compilations.keySet()) {
 261             Method test = tests.get(testName);
 262             if (test == null) {
 263                 // Skip helper methods
 264                 continue;
 265             }
 266             String graph = compilations.get(testName);
 267             if (PRINT_GRAPH) {
 268                 System.out.println("\nGraph for " + testName + "\n" + graph);
 269             }
 270             // Parse graph using regular expressions to determine if it contains forbidden nodes
 271             Test[] annos = test.getAnnotationsByType(Test.class);
 272             Test anno = null;
 273             for (Test a : annos) {
 274                 if ((a.valid() & ValueTypePassFieldsAsArgsOn) != 0 && ValueTypePassFieldsAsArgs) {
 275                     assert anno == null;
 276                     anno = a;
 277                 } else if ((a.valid() & ValueTypePassFieldsAsArgsOff) != 0 && !ValueTypePassFieldsAsArgs) {
 278                     assert anno == null;
 279                     anno = a;
 280                 } else if ((a.valid() & ValueTypeArrayFlattenOn) != 0 && ValueTypeArrayFlatten) {
 281                     assert anno == null;
 282                     anno = a;
 283                 } else if ((a.valid() & ValueTypeArrayFlattenOff) != 0 && !ValueTypeArrayFlatten) {
 284                     assert anno == null;
 285                     anno = a;
 286                 } else if ((a.valid() & ValueTypeReturnedAsFieldsOn) != 0 && ValueTypeReturnedAsFields) {
 287                     assert anno == null;
 288                     anno = a;
 289                 } else if ((a.valid() & ValueTypeReturnedAsFieldsOff) != 0 && !ValueTypeReturnedAsFields) {
 290                     assert anno == null;
 291                     anno = a;
 292                 }
 293             }
 294             assert anno != null;
 295             String regexFail = anno.failOn();
 296             if (!regexFail.isEmpty()) {
 297                 Pattern pattern = Pattern.compile(regexFail.substring(0, regexFail.length()-1));
 298                 Matcher matcher = pattern.matcher(graph);
 299                 boolean found = matcher.find();
 300                 Asserts.assertFalse(found, "Graph for '" + testName + "' contains forbidden node:\n" + (found ? matcher.group() : ""));
 301             }
 302             String[] regexMatch = anno.match();
 303             int[] matchCount = anno.matchCount();
 304             for (int i = 0; i < regexMatch.length; ++i) {
 305                 Pattern pattern = Pattern.compile(regexMatch[i].substring(0, regexMatch[i].length()-1));
 306                 Matcher matcher = pattern.matcher(graph);
 307                 int count = 0;
 308                 String nodes = "";
 309                 while (matcher.find()) {
 310                     count++;
 311                     nodes += matcher.group() + "\n";
 312                 }
 313                 if (matchCount[i] < 0) {
 314                     Asserts.assertLTE(Math.abs(matchCount[i]), count, "Graph for '" + testName + "' contains different number of match nodes:\n" + nodes);
 315                 } else {
 316                     Asserts.assertEQ(matchCount[i], count, "Graph for '" + testName + "' contains different number of match nodes:\n" + nodes);
 317                 }
 318             }
 319             tests.remove(testName);
 320             System.out.println(testName + " passed");
 321         }
 322         // Check if all tests were compiled
 323         if (tests.size() != 0) {
 324             for (String name : tests.keySet()) {
 325                 System.out.println("Test '" + name + "' not compiled!");
 326             }
 327             throw new RuntimeException("Not all tests were compiled");
 328         }
 329     }
 330 
 331     private void setup(Class<?> clazz) {
 332         if (XCOMP) {
 333             // Don't control compilation if -Xcomp is enabled
 334             return;
 335         }
 336         if (DUMP_REPLAY) {
 337             // Generate replay compilation files
 338             String directive = "[{ match: \"*.*\", DumpReplay: true }]";
 339             if (WHITE_BOX.addCompilerDirective(directive) != 1) {
 340                 throw new RuntimeException("Failed to add compiler directive");
 341             }
 342         }
 343 
 344         Method[] methods = clazz.getDeclaredMethods();
 345         for (Method m : methods) {
 346             if (m.isAnnotationPresent(Test.class)) {
 347                 // Don't inline tests
 348                 WHITE_BOX.testSetDontInlineMethod(m, true);
 349             }
 350             if (m.isAnnotationPresent(DontCompile.class)) {
 351                 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, true);
 352                 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, false);
 353                 WHITE_BOX.testSetDontInlineMethod(m, true);
 354             }
 355             if (m.isAnnotationPresent(ForceInline.class)) {
 356                 WHITE_BOX.testSetForceInlineMethod(m, true);
 357             } else if (m.isAnnotationPresent(DontInline.class)) {
 358                 WHITE_BOX.testSetDontInlineMethod(m, true);
 359             }
 360         }
 361 
 362         // Compile class initializers
 363         WHITE_BOX.enqueueInitializerForCompilation(clazz, COMP_LEVEL_FULL_OPTIMIZATION);
 364     }
 365 
 366     private void run(Class<?>... classes) throws Exception {
 367         if (USE_COMPILER && PRINT_IDEAL && !XCOMP) {
 368             System.out.println("PrintIdeal enabled");
 369         }
 370         System.out.format("rI = %d, rL = %d\n", rI, rL);
 371 
 372         setup(getClass());
 373         for (Class<?> clazz : classes) {
 374             setup(clazz);
 375         }
 376 
 377         // Execute tests
 378         TreeMap<Long, String> durations = PRINT_TIMES ? new TreeMap<Long, String>() : null;
 379         for (Method test : tests.values()) {
 380             long startTime = System.nanoTime();
 381             Method verifier = getClass().getMethod(test.getName() + "_verifier", boolean.class);
 382             // Warmup using verifier method
 383             Warmup anno = test.getAnnotation(Warmup.class);
 384             int warmup = anno == null ? WARMUP : anno.value();
 385             for (int i = 0; i < warmup; ++i) {
 386                 verifier.invoke(this, true);
 387             }
 388             // Trigger compilation
 389             WHITE_BOX.enqueueMethodForCompilation(test, COMP_LEVEL_FULL_OPTIMIZATION);
 390             Asserts.assertTrue(!USE_COMPILER || WHITE_BOX.isMethodCompiled(test, false), test + " not compiled");
 391             // Check result
 392             verifier.invoke(this, false);
 393             if (PRINT_TIMES) {
 394                 long endTime = System.nanoTime();
 395                 long duration = (endTime - startTime);
 396                 durations.put(duration, test.getName());
 397             }
 398         }
 399 
 400         // Print execution times
 401         if (PRINT_TIMES) {
 402           System.out.println("\n\nTest execution times:");
 403           for (Map.Entry<Long, String> entry : durations.entrySet()) {
 404               System.out.format("%-10s%15d ns\n", entry.getValue() + ":", entry.getKey());
 405           }
 406         }
 407     }
 408 }