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