1 /*
   2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2020, Arm Limited. All rights reserved.
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * This code is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License version 2 only, as
   8  * published by the Free Software Foundation.
   9  *
  10  * This code is distributed in the hope that it will be useful, but WITHOUT
  11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  13  * version 2 for more details (a copy is included in the LICENSE file that
  14  * accompanied this code).
  15  *
  16  * You should have received a copy of the GNU General Public License version
  17  * 2 along with this work; if not, write to the Free Software Foundation,
  18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  19  *
  20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  21  * or visit www.oracle.com if you need additional information or have any
  22  * questions.
  23  */
  24 
  25 package compiler.vectorization.aarch64;
  26 
  27 import java.lang.annotation.ElementType;
  28 import java.lang.annotation.Retention;
  29 import java.lang.annotation.RetentionPolicy;
  30 import java.lang.annotation.Target;
  31 import java.lang.reflect.Method;
  32 
  33 import jdk.test.lib.process.OutputAnalyzer;
  34 import jdk.test.lib.process.ProcessTools;
  35 
  36 import sun.hotspot.WhiteBox;
  37 import sun.hotspot.cpuinfo.CPUInfo;
  38 
  39 import compiler.whitebox.CompilerWhiteBoxTest;
  40 
  41 public class CodegenTestRunner {
  42 
  43     private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
  44 
  45     @Target(ElementType.METHOD)
  46     @Retention(RetentionPolicy.RUNTIME)
  47     protected @interface CallMath {}
  48 
  49     @Target(ElementType.METHOD)
  50     @Retention(RetentionPolicy.RUNTIME)
  51     protected @interface CheckOpto {
  52         String neon() default "";
  53         String sve() default "";
  54         String sve2() default "";
  55     }
  56 
  57     private void checkMethod(Method method, int sveLevel) throws Exception {
  58         String className = getClass().getName();
  59         String methodName = method.getName();
  60 
  61         // Check if test method is valid
  62         if (method.getParameterCount() > 0) {
  63             throw new RuntimeException("Test method should have zero parameter");
  64         }
  65 
  66         // Find expected NEON/SVE instructions from the annotation
  67         CheckOpto optos = method.getAnnotation(CheckOpto.class);
  68         String opcodes, suffix;
  69         switch (sveLevel) {
  70             case 0:
  71                 opcodes = optos.neon();
  72                 suffix = "# vector \\([0-9]+[A-Z]+\\)"; // 16B, 8H, 4S, 2D, ...
  73                 break;
  74             case 1:
  75                 opcodes = optos.sve();
  76                 suffix = "\\(sve\\)";
  77                 break;
  78             case 2:
  79                 opcodes = optos.sve2();
  80                 suffix = "\\(sve2\\)";
  81                 break;
  82             default:
  83                 throw new RuntimeException("Unexpected SVE level");
  84         }
  85 
  86         // Skip this check if the expected instruction is not defined
  87         if (opcodes.equals("")) {
  88             return;
  89         }
  90 
  91         // Create a process to compile specified method
  92         String[] procArgs = {
  93             "-Xbootclasspath/a:.",
  94             "-XX:+IgnoreUnrecognizedVMOptions",
  95             "-XX:+WhiteBoxAPI",
  96             "-XX:-BackgroundCompilation",
  97             "-XX:+PrintOptoAssembly",
  98             "-XX:UseSVE=" + sveLevel,
  99             "-XX:CompileOnly=" + className + "::" + methodName,
 100             className,
 101             methodName
 102         };
 103         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(procArgs);
 104         OutputAnalyzer output = new OutputAnalyzer(pb.start());
 105 
 106         // The sub-process should exit with no error
 107         output.shouldHaveExitValue(0);
 108         output.stderrShouldBeEmptyIgnoreVMWarnings();
 109 
 110         // Pattern match of the printed OptoAssembly to check the existence of
 111         // expected SIMD instructions
 112         // E.g.
 113         //   27c     sve_add  V16, V17, V16  # vector (sve)
 114         // matches
 115         //   "{SPACES}{opc}{SPACES}{ANYTHING}{suffix}"
 116         for (String opc : opcodes.split("\\|")) {
 117             // Check comma-separated opto instructions
 118             String regex = "\\s+" + opc + "\\s+.+" + suffix;
 119             output.stdoutShouldMatch(regex);
 120         }
 121     }
 122 
 123     private void testMethodsWithSVELevel(int sveLevel) {
 124         Class testClass = getClass();
 125         for (Method testMethod : testClass.getDeclaredMethods()) {
 126             // Check all methods with CheckOpto annotation
 127             if (testMethod.isAnnotationPresent(CheckOpto.class)) {
 128                 try {
 129                     checkMethod(testMethod, sveLevel);
 130                 } catch (Exception e) {
 131                     // Report failed case and exit
 132                     System.err.println("Test failed in " + testClass.getName() +
 133                             "." + testMethod.getName() + ": " + e.getMessage());
 134                     System.exit(1);
 135                 }
 136             }
 137         }
 138     }
 139 
 140     private void checkCPUFeatureAndRun() {
 141        if (CPUInfo.hasFeature("sve2")) {
 142            testMethodsWithSVELevel(2);
 143        }
 144        if (CPUInfo.hasFeature("sve")) {
 145            testMethodsWithSVELevel(1);
 146        }
 147        // always check NEON
 148        testMethodsWithSVELevel(0);
 149     }
 150 
 151     private void compileMethodByName(String methodName) {
 152         try {
 153             Method method = getClass().getDeclaredMethod(methodName);
 154             if (method.isAnnotationPresent(CallMath.class)) {
 155                 // For method that calls intrinsified math methods, the class
 156                 // java.lang.Math should be loaded before the compilation to
 157                 // avoid C2 generating unexpected uncommon traps
 158                 try {
 159                     Class.forName("java.lang.Math");
 160                 } catch (ClassNotFoundException e) {
 161                     System.err.println("Failed to load class java.lang.Math");
 162                 }
 163             }
 164             WHITE_BOX.enqueueMethodForCompilation(
 165                     method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION);
 166         } catch (NoSuchMethodException e) {
 167             System.err.println("Method " + methodName + " not found");
 168         }
 169     }
 170 
 171     protected void run(String[] args) {
 172         if (args.length == 0) {
 173             // We are in the main java process running the test runner
 174             // Create sub-processes to run the case with all supported SVE level
 175             checkCPUFeatureAndRun();
 176         } else if (args.length == 1) {
 177             // We are in the forked java process running the case
 178             // Compile the method specified by args[0] for SIMD instruction check
 179             compileMethodByName(args[0]);
 180         } else {
 181             System.err.println("Unexpected args[] length");
 182             System.exit(1);
 183         }
 184     }
 185 }
 186