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 }