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