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 }