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 * 53 * 54 * @library /lib / 55 * 56 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.ASM5, 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.ASM5, 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 }