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