1 /*
   2  * Copyright (c) 2015, 2019, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jfr.event.compiler;
  27 
  28 import jdk.internal.org.objectweb.asm.*;
  29 import jdk.jfr.Recording;
  30 import jdk.jfr.consumer.RecordedEvent;
  31 import jdk.jfr.consumer.RecordedMethod;
  32 import jdk.jfr.consumer.RecordedObject;
  33 import com.oracle.java.testlibrary.Asserts;
  34 import com.oracle.java.testlibrary.Platform;
  35 import com.oracle.java.testlibrary.jfr.EventNames;
  36 import com.oracle.java.testlibrary.jfr.Events;
  37 import sun.hotspot.WhiteBox;
  38 
  39 import java.io.IOException;
  40 import java.lang.reflect.Constructor;
  41 import java.lang.reflect.Executable;
  42 import java.lang.reflect.Method;
  43 import java.util.*;
  44 import java.util.stream.IntStream;
  45 
  46 /*
  47  * @test CompilerInliningTest
  48  * @bug 8073607
  49  * @summary Verifies that corresponding JFR events are emitted in case of inlining.
  50  *
  51  * @requires vm.opt.Inline == true | vm.opt.Inline == null
  52  * @library /testlibrary /testlibrary/whitebox
  53  * @modules java.base/jdk.internal.org.objectweb.asm
  54  *          jdk.jfr
  55  *
  56  * @build sun.hotspot.WhiteBox
  57  * @run main ClassFileInstaller sun.hotspot.WhiteBox
  58  *     sun.hotspot.WhiteBox$WhiteBoxPermission
  59  * @run main/othervm -Xbootclasspath/a:.
  60  *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
  61  *     -Xbatch -XX:+EnableJFR jfr.event.compiler.TestCompilerInlining
  62  */
  63 public class TestCompilerInlining {
  64     private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
  65     private static final int LEVEL_SIMPLE = 1;
  66     private static final int LEVEL_FULL_OPTIMIZATION = 4;
  67     private static final Executable ENTRY_POINT = getConstructor(TestCase.class);
  68     private static final String TEST_CASE_CLASS_NAME = TestCase.class.getName().replace('.', '/');
  69 
  70     public static void main(String[] args) throws Exception {
  71         InlineCalls inlineCalls = new InlineCalls(TestCase.class);
  72         inlineCalls.disableInline(getConstructor(Object.class));
  73         inlineCalls.disableInline(getMethod(TestCase.class, "qux", boolean.class));
  74         inlineCalls.forceInline(getMethod(TestCase.class, "foo"));
  75         inlineCalls.forceInline(getMethod(TestCase.class, "foo", int.class));
  76         inlineCalls.forceInline(getMethod(TestCase.class, "bar"));
  77         inlineCalls.forceInline(getMethod(TestCase.class, "baz"));
  78 
  79         Map<Call, Boolean> result = inlineCalls.getExpected(ENTRY_POINT);
  80         for (int level : determineAvailableLevels()) {
  81             testLevel(result, level);
  82         }
  83     }
  84 
  85     private static void testLevel(Map<Call, Boolean> expectedResult, int level) throws IOException {
  86         System.out.println("****** Testing level " + level + " *******");
  87         Recording r = new Recording();
  88         r.enable(EventNames.CompilerInlining);
  89         r.start();
  90         WHITE_BOX.enqueueMethodForCompilation(ENTRY_POINT, level);
  91         WHITE_BOX.deoptimizeMethod(ENTRY_POINT);
  92         r.stop();
  93         System.out.println("Expected:");
  94 
  95         List<RecordedEvent> events = Events.fromRecording(r);
  96         Set<Call> foundEvents = new HashSet<>();
  97         int foundRelevantEvent = 0;
  98         for (RecordedEvent event : events) {
  99             RecordedMethod callerObject = event.getValue("caller");
 100             RecordedObject calleeObject = event.getValue("callee");
 101             MethodDesc caller = methodToMethodDesc(callerObject);
 102             MethodDesc callee = ciMethodToMethodDesc(calleeObject);
 103             // only TestCase.* -> TestCase.* OR TestCase.* -> Object.<init> are tested/filtered
 104             if (caller.className.equals(TEST_CASE_CLASS_NAME) && (callee.className.equals(TEST_CASE_CLASS_NAME)
 105                     || (callee.className.equals("java/lang/Object") && callee.methodName.equals("<init>")))) {
 106                 System.out.println(event);
 107                 boolean succeeded = (boolean) event.getValue("succeeded");
 108                 int bci = Events.assertField(event, "bci").atLeast(0).getValue();
 109                 Call call = new Call(caller, callee, bci);
 110                 foundRelevantEvent++;
 111                 Boolean expected = expectedResult.get(call);
 112                 Asserts.assertNotNull(expected, "Unexpected inlined call : " + call);
 113                 Asserts.assertEquals(expected, succeeded, "Incorrect result for " + call);
 114                 Asserts.assertTrue(foundEvents.add(call), "repeated event for " + call);
 115             }
 116         }
 117         Asserts.assertEquals(foundRelevantEvent, expectedResult.size(), String.format("not all events found at lavel %d. " + "found = '%s'. expected = '%s'", level, events, expectedResult.keySet()));
 118         System.out.println();
 119         System.out.println();
 120     }
 121 
 122     private static int[] determineAvailableLevels() {
 123         if (WHITE_BOX.getBooleanVMFlag("TieredCompilation")) {
 124             return IntStream.rangeClosed(LEVEL_SIMPLE, WHITE_BOX.getIntxVMFlag("TieredStopAtLevel").intValue()).toArray();
 125         }
 126         if (Platform.isServer() && !Platform.isEmulatedClient()) {
 127             return new int[] { LEVEL_FULL_OPTIMIZATION };
 128         }
 129         if (Platform.isClient() || Platform.isEmulatedClient()) {
 130             return new int[] { LEVEL_SIMPLE };
 131         }
 132         throw new Error("TESTBUG: unknown VM");
 133     }
 134 
 135     private static MethodDesc methodToMethodDesc(RecordedMethod method) {
 136         String internalClassName = method.getType().getName().replace('.', '/');
 137         String methodName = method.getValue("name");
 138         String methodDescriptor = method.getValue("descriptor");
 139         return new MethodDesc(internalClassName, methodName, methodDescriptor);
 140     }
 141 
 142     private static MethodDesc ciMethodToMethodDesc(RecordedObject ciMethod) {
 143         String internalClassName = ciMethod.getValue("type");
 144         String methodName = ciMethod.getValue("name");
 145         String methodDescriptor = ciMethod.getValue("descriptor");
 146         return new MethodDesc(internalClassName, methodName, methodDescriptor);
 147     }
 148 
 149     private static Method getMethod(Class<?> aClass, String name, Class<?>... params) {
 150         try {
 151             return aClass.getDeclaredMethod(name, params);
 152         } catch (NoSuchMethodException | SecurityException e) {
 153             throw new Error("TESTBUG : cannot get method " + name + Arrays.toString(params), e);
 154         }
 155     }
 156 
 157     private static Constructor<?> getConstructor(Class<?> aClass, Class<?>... params) {
 158         try {
 159             return aClass.getDeclaredConstructor(params);
 160         } catch (NoSuchMethodException | SecurityException e) {
 161             throw new Error("TESTBUG : cannot get constructor" + Arrays.toString(params), e);
 162         }
 163     }
 164 }
 165 
 166 class TestCase {
 167     public TestCase() {
 168         foo();
 169     }
 170 
 171     public void foo() {
 172         qux(true);
 173         bar();
 174         foo(2);
 175     }
 176 
 177     private void foo(int i) {
 178     }
 179 
 180     private void bar() {
 181         baz();
 182         qux(false);
 183         qux(true);
 184     }
 185 
 186     protected static double baz() {
 187         qux(false);
 188         return .0;
 189     }
 190 
 191     private static int qux(boolean b) {
 192         qux(b);
 193         return 0;
 194     }
 195 }
 196 
 197 /**
 198  * data structure for method call
 199  */
 200 class Call {
 201     public final MethodDesc caller;
 202     public final MethodDesc callee;
 203     public final int bci;
 204 
 205     @Override
 206     public boolean equals(Object o) {
 207         if (this == o)
 208             return true;
 209         if (o == null || !(o instanceof Call))
 210             return false;
 211 
 212         Call call = (Call) o;
 213 
 214         if (bci != call.bci)
 215             return false;
 216         if (!callee.equals(call.callee))
 217             return false;
 218         if (!caller.equals(call.caller))
 219             return false;
 220 
 221         return true;
 222     }
 223 
 224     @Override
 225     public int hashCode() {
 226         int result = caller.hashCode();
 227         result = 31 * result + callee.hashCode();
 228         result = 47 * result + bci;
 229         return result;
 230     }
 231 
 232     public Call(MethodDesc caller, MethodDesc callee, int bci) {
 233         Objects.requireNonNull(caller);
 234         Objects.requireNonNull(callee);
 235         this.caller = caller;
 236         this.callee = callee;
 237         this.bci = bci;
 238     }
 239 
 240     @Override
 241     public String toString() {
 242         return String.format("Call{caller='%s', callee='%s', bci=%d}", caller, callee, bci);
 243     }
 244 }
 245 
 246 /**
 247  * data structure for method description
 248  */
 249 class MethodDesc {
 250     public final String className;
 251     public final String methodName;
 252     public final String descriptor;
 253 
 254     public MethodDesc(Class<?> aClass, String methodName, String descriptor) {
 255         this(aClass.getName().replace('.', '/'), methodName, descriptor);
 256     }
 257 
 258     public MethodDesc(String className, String methodName, String descriptor) {
 259         Objects.requireNonNull(className);
 260         Objects.requireNonNull(methodName);
 261         Objects.requireNonNull(descriptor);
 262         this.className = className.replace('.', '/');
 263         this.methodName = methodName;
 264         this.descriptor = descriptor;
 265     }
 266 
 267     public MethodDesc(Executable executable) {
 268         Class<?> aClass = executable.getDeclaringClass();
 269         className = Type.getInternalName(aClass).replace('.', '/');
 270 
 271         if (executable instanceof Constructor<?>) {
 272             methodName = "<init>";
 273             descriptor = Type.getConstructorDescriptor((Constructor<?>) executable);
 274         } else {
 275             methodName = executable.getName();
 276             descriptor = Type.getMethodDescriptor((Method) executable);
 277         }
 278 
 279     }
 280 
 281     @Override
 282     public boolean equals(Object o) {
 283         if (this == o)
 284             return true;
 285         if (o == null || getClass() != o.getClass())
 286             return false;
 287 
 288         MethodDesc that = (MethodDesc) o;
 289 
 290         if (!className.equals(that.className))
 291             return false;
 292         if (!methodName.equals(that.methodName))
 293             return false;
 294         if (!descriptor.equals(that.descriptor))
 295             return false;
 296 
 297         return true;
 298     }
 299 
 300     @Override
 301     public int hashCode() {
 302         int result = className.hashCode();
 303         result = 31 * result + methodName.hashCode();
 304         result = 47 * result + descriptor.hashCode();
 305         return result;
 306     }
 307 
 308     @Override
 309     public String toString() {
 310         return String.format("MethodDesc{className='%s', methodName='%s', descriptor='%s'}", className, methodName, descriptor);
 311     }
 312 }
 313 
 314 /**
 315  * Aux class to get all calls in an arbitrary class.
 316  */
 317 class InlineCalls {
 318     private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
 319 
 320     private final Collection<Call> calls;
 321     private final Map<Call, Boolean> inline;
 322 
 323     public InlineCalls(Class<?> aClass) {
 324         calls = getCalls(aClass);
 325         inline = new HashMap<>();
 326     }
 327 
 328     /**
 329      * @return expected inline events
 330      */
 331     public Map<Call, Boolean> getExpected(Executable entry) {
 332         Map<Call, Boolean> result = new HashMap<>();
 333         Queue<MethodDesc> methods = new ArrayDeque<>();
 334         Set<MethodDesc> finished = new HashSet<>();
 335         methods.add(new MethodDesc(entry));
 336         while (!methods.isEmpty()) {
 337             MethodDesc method = methods.poll();
 338             if (finished.add(method)) {
 339                 inline.entrySet().stream().filter(k -> k.getKey().caller.equals(method)).forEach(k -> {
 340                     result.put(k.getKey(), k.getValue());
 341                     if (k.getValue()) {
 342                         methods.add(k.getKey().callee);
 343                     }
 344                 });
 345             }
 346         }
 347 
 348         return result;
 349     }
 350 
 351     public void disableInline(Executable executable) {
 352         WHITE_BOX.testSetDontInlineMethod(executable, true);
 353         MethodDesc md = new MethodDesc(executable);
 354         calls.stream().filter(c -> c.callee.equals(md)).forEach(c -> inline.put(c, false));
 355     }
 356 
 357     public void forceInline(Executable executable) {
 358         WHITE_BOX.testSetForceInlineMethod(executable, true);
 359         MethodDesc md = new MethodDesc(executable);
 360         calls.stream().filter(c -> c.callee.equals(md)).forEach(c -> inline.putIfAbsent(c, true));
 361     }
 362 
 363     private static Collection<Call> getCalls(Class<?> aClass) {
 364         List<Call> calls = new ArrayList<>();
 365         ClassWriter cw;
 366         ClassReader cr;
 367         try {
 368             cr = new ClassReader(aClass.getName());
 369         } catch (IOException e) {
 370             throw new Error("TESTBUG : unexpected IOE during class reading", e);
 371         }
 372         cw = new ClassWriter(cr, 0);
 373         ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, cw) {
 374             @Override
 375             public MethodVisitor visitMethod(int access, String name, String desc, String descriptor, String[] exceptions) {
 376                 System.out.println("Method: " +name);
 377                 MethodVisitor mv = super.visitMethod(access, name, desc, descriptor, exceptions);
 378                 return new CallTracer(aClass, name, desc, mv, calls);
 379             }
 380         };
 381         cr.accept(cv, 0);
 382 
 383         return calls;
 384     }
 385 
 386     private static class CallTracer extends MethodVisitor {
 387         private final MethodDesc caller;
 388         private Collection<Call> calls;
 389 
 390         public CallTracer(Class<?> aClass, String name, String desc, MethodVisitor mv, Collection<Call> calls) {
 391             super(Opcodes.ASM5, mv);
 392             caller = new MethodDesc(aClass.getName(), name, desc);
 393             this.calls = calls;
 394         }
 395 
 396         @Override
 397         public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
 398             Label label = new Label();
 399             visitLabel(label);
 400             super.visitMethodInsn(opcode, owner, name, desc, itf);
 401             calls.add(new Call(caller, new MethodDesc(owner, name, desc), label.getOffset()));
 402         }
 403     }
 404 }