1 /* 2 * Copyright (c) 2016, 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 package org.graalvm.compiler.hotspot.test; 24 25 import java.lang.management.ManagementFactory; 26 import java.lang.management.MonitorInfo; 27 import java.lang.management.ThreadInfo; 28 import java.lang.management.ThreadMXBean; 29 import java.util.Collection; 30 import java.util.HashMap; 31 import java.util.HashSet; 32 33 import org.graalvm.compiler.api.directives.GraalDirectives; 34 import org.graalvm.compiler.core.phases.HighTier; 35 import org.graalvm.compiler.debug.DebugEnvironment; 36 import org.graalvm.compiler.debug.GraalError; 37 import org.graalvm.compiler.debug.TTY; 38 import org.graalvm.compiler.hotspot.phases.OnStackReplacementPhase; 39 import org.graalvm.compiler.options.OptionKey; 40 import org.graalvm.compiler.options.OptionValues; 41 import org.graalvm.util.EconomicMap; 42 import org.junit.Assert; 43 import org.junit.BeforeClass; 44 import org.junit.Test; 45 46 import jdk.vm.ci.meta.ResolvedJavaMethod; 47 48 /** 49 * Test on-stack-replacement with locks. 50 */ 51 public class GraalOSRLockTest extends GraalOSRTestBase { 52 53 private static boolean TestInSeparateThread = false; 54 55 // testing only 56 public static boolean isMonitorLockHeld(Object o) { 57 return isMonitorLockHeldByThread(o, null); 58 } 59 60 public static boolean isMonitorLockHeldByThread(Object o, Thread t) { 61 int oihc = System.identityHashCode(o); 62 ThreadMXBean tmxbean = ManagementFactory.getThreadMXBean(); 63 ThreadInfo[] tinfos = tmxbean.dumpAllThreads(true, false); 64 65 for (ThreadInfo ti : tinfos) { 66 if (!(t != null && t.getId() != ti.getThreadId())) { 67 for (MonitorInfo mi : ti.getLockedMonitors()) { 68 if (mi.getIdentityHashCode() == oihc) { 69 return true; 70 } 71 } 72 } 73 } 74 return false; 75 } 76 77 protected static void run(Runnable r) { 78 if (TestInSeparateThread) { 79 Thread t = new Thread(new Runnable() { 80 @Override 81 public void run() { 82 beforeOSRLockTest(); 83 r.run(); 84 afterOSRLockTest(); 85 } 86 }); 87 t.start(); 88 try { 89 t.join(); 90 } catch (Throwable t1) { 91 throw new GraalError(t1); 92 } 93 } else { 94 beforeOSRLockTest(); 95 r.run(); 96 afterOSRLockTest(); 97 } 98 } 99 100 private static boolean wasLocked() { 101 return isMonitorLockHeld(lock) || isMonitorLockHeld(lock1); 102 } 103 104 protected static EconomicMap<OptionKey<?>, Object> osrLockNoDeopt() { 105 EconomicMap<OptionKey<?>, Object> overrides = OptionValues.newOptionMap(); 106 overrides.put(OnStackReplacementPhase.Options.DeoptAfterOSR, false); 107 overrides.put(OnStackReplacementPhase.Options.SupportOSRWithLocks, true); 108 return overrides; 109 } 110 111 protected static EconomicMap<OptionKey<?>, Object> osrLockDeopt() { 112 EconomicMap<OptionKey<?>, Object> overrides = OptionValues.newOptionMap(); 113 overrides.put(OnStackReplacementPhase.Options.SupportOSRWithLocks, true); 114 return overrides; 115 } 116 117 public static int SideEffectI; 118 119 private static void lockOnObject(Object o, String msg) { 120 Thread t = new Thread(new Runnable() { 121 @Override 122 public void run() { 123 synchronized (o) { 124 SideEffectI = 1; 125 } 126 } 127 }); 128 t.start(); 129 try { 130 t.join(1000); 131 } catch (InterruptedException e) { 132 Assert.fail("Object " + msg + " was locked"); 133 } 134 } 135 136 private static void beforeOSRLockTest() { 137 // try lock both objects 138 lockOnObject(lock, "lock"); 139 lockOnObject(lock1, "lock1"); 140 Assert.assertFalse(wasLocked()); 141 } 142 143 private static void afterOSRLockTest() { 144 // try lock both objects 145 lockOnObject(lock, "lock"); 146 lockOnObject(lock1, "lock1"); 147 Assert.assertFalse(wasLocked()); 148 // force a safepoint and hope the inflated locks are deflated 149 System.gc(); 150 } 151 152 @BeforeClass 153 public static void init() { 154 DebugEnvironment.ensureInitialized(getInitialOptions()); 155 } 156 157 // @Test 158 @SuppressWarnings("try") 159 public void testLockOSROuterImmediateDeoptAfter() { 160 run(() -> { 161 OptionValues options = new OptionValues(getInitialOptions(), osrLockDeopt()); 162 testOSR(options, "testOuterLockImmediateDeoptAfter"); 163 }); 164 } 165 166 static class A { 167 168 } 169 170 static class B { 171 @SuppressWarnings("unused") 172 B(A a) { 173 174 } 175 } 176 177 HashMap<String, HashSet<A>> listeners = new HashMap<>(); 178 179 public synchronized ReturnValue synchronizedSnippet() { 180 /* 181 * Test method for which liveness would conclude the original object is no longer alive 182 * although it is. 183 */ 184 Collection<HashSet<A>> allListeners = listeners.values(); 185 for (HashSet<A> group : allListeners) { 186 GraalDirectives.blackhole(group); 187 } 188 return ReturnValue.SUCCESS; 189 } 190 191 @Test 192 @SuppressWarnings("try") 193 public void testSynchronizedSnippet() { 194 GraalOSRLockTest instance = new GraalOSRLockTest(); 195 // enough entries to trigger OSR 196 for (int i = 0; i < 100000; i++) { 197 instance.listeners.put("hello" + i, null); 198 } 199 testOSR(getInitialOptions(), "synchronizedSnippet", instance); 200 Assert.assertFalse(isMonitorLockHeld(instance)); 201 } 202 203 @Test 204 @SuppressWarnings("try") 205 public void testOSRTrivialLoop() { 206 run(() -> { 207 OptionValues options = new OptionValues(getInitialOptions(), osrLockDeopt()); 208 try { 209 testOSR(options, "testReduceOSRTrivialLoop"); 210 } catch (Throwable t) { 211 Assert.assertEquals("OSR compilation without OSR entry loop.", t.getMessage()); 212 } 213 }); 214 } 215 216 @Test 217 @SuppressWarnings("try") 218 public void testLockOSROuterInnerImmediateDeoptAfter() { 219 run(() -> { 220 OptionValues options = new OptionValues(getInitialOptions(), osrLockDeopt()); 221 testOSR(options, "testOuterInnerLockImmediateDeoptAfter"); 222 }); 223 } 224 225 @Test 226 @SuppressWarnings("try") 227 public void testLockOSROuterCompileRestOfMethod() { 228 run(() -> { 229 EconomicMap<OptionKey<?>, Object> overrides = osrLockNoDeopt(); 230 overrides.put(HighTier.Options.Inline, false); 231 OptionValues options = new OptionValues(getInitialOptions(), overrides); 232 testOSR(options, "testOuterLockCompileRestOfMethod"); 233 }); 234 } 235 236 @Test 237 @SuppressWarnings("try") 238 public void testLockOSROuterInnerCompileRestOfMethod() { 239 run(() -> { 240 OptionValues options = new OptionValues(getInitialOptions(), osrLockNoDeopt()); 241 testOSR(options, "testOuterInnerLockCompileRestOfMethod"); 242 }); 243 } 244 245 @Test 246 @SuppressWarnings("try") 247 public void testLockOSROuterInnerLockDepthCompileRestOfMethod() { 248 run(() -> { 249 EconomicMap<OptionKey<?>, Object> overrides = osrLockNoDeopt(); 250 overrides.put(HighTier.Options.Inline, false); 251 OptionValues options = new OptionValues(getInitialOptions(), overrides); 252 testOSR(options, "testOuterInnerLockDepth1CompileRestOfMethod"); 253 }); 254 } 255 256 @Test 257 @SuppressWarnings("try") 258 public void testLockOSROuterInnerLockDepthDeopt() { 259 run(() -> { 260 EconomicMap<OptionKey<?>, Object> overrides = osrLockNoDeopt(); 261 overrides.put(HighTier.Options.Inline, false); 262 OptionValues options = new OptionValues(getInitialOptions(), overrides); 263 testOSR(options, "testOuterInnerLockDepth1DeoptAfter"); 264 }); 265 } 266 267 @Test 268 @SuppressWarnings("try") 269 public void testLockOSROuterInnerLockDepthRecursiveCompileRestOfMethod0() { 270 run(() -> { 271 OptionValues options = new OptionValues(getInitialOptions(), osrLockNoDeopt()); 272 testOSR(options, "testOuterInnerLockDepth1RecursiveCompileRestOfMethod1"); 273 }); 274 } 275 276 @Test 277 @SuppressWarnings("try") 278 public void testLockOSROuterInnerLockDepthRecursiveCompileRestOfMethod1() { 279 run(() -> { 280 OptionValues options = new OptionValues(getInitialOptions(), osrLockNoDeopt()); 281 testOSR(options, "testOuterInnerLockDepth1RecursiveCompileRestOfMethod2"); 282 }); 283 } 284 285 @Test 286 @SuppressWarnings("try") 287 public void testLockOSROuterCompileRestOfMethodSubsequentLock() { 288 run(() -> { 289 OptionValues options = new OptionValues(getInitialOptions(), osrLockNoDeopt()); 290 testOSR(options, "testOuterLockCompileRestOfMethodSubsequentLock"); 291 }); 292 } 293 294 @Test 295 @SuppressWarnings("try") 296 public void testLockOSROuterInnerSameLockCompileRestOfMethod() { 297 run(() -> { 298 OptionValues options = new OptionValues(getInitialOptions(), osrLockNoDeopt()); 299 testOSR(options, "testOuterInnerSameLockCompileRestOfMethod"); 300 }); 301 } 302 303 @Test 304 @SuppressWarnings("try") 305 public void testLockOSRRecursive() { 306 run(() -> { 307 // call it 308 testRecursiveLockingLeaf(); 309 ResolvedJavaMethod leaf = getResolvedJavaMethod("testRecursiveLockingLeaf"); 310 // profile it 311 leaf.reprofile(); 312 testRecursiveLockingLeaf(); 313 EconomicMap<OptionKey<?>, Object> overrides = osrLockNoDeopt(); 314 overrides.put(HighTier.Options.Inline, false); 315 OptionValues options = new OptionValues(getInitialOptions(), overrides); 316 compile(options, leaf, -1); 317 testOSR(options, "testRecursiveLockingRoot"); 318 }); 319 } 320 321 @Test 322 @SuppressWarnings("try") 323 public void testLockOSRRecursiveLeafOSR() { 324 run(() -> { 325 testRecursiveRootNoOSR(); 326 ResolvedJavaMethod root = getResolvedJavaMethod("testRecursiveRootNoOSR"); 327 EconomicMap<OptionKey<?>, Object> overrides = osrLockNoDeopt(); 328 overrides.put(HighTier.Options.Inline, false); 329 OptionValues options = new OptionValues(getInitialOptions(), overrides); 330 compile(options, root, -1); 331 testOSR(options, "testRecursiveLeafOSR"); 332 // force a safepoint and hope the inflated locks are deflated 333 System.gc(); 334 // call the root to call into the leaf and enter the osr-ed code 335 testRecursiveRootNoOSR(); 336 337 }); 338 } 339 340 protected static int limit = 10000; 341 protected static Object lock = new Object(); 342 protected static Object lock1 = new Object(); 343 private static final boolean LOG = false; 344 345 static { 346 // force identity hash code for easy displaced mark identification 347 int h1 = System.identityHashCode(lock); 348 int h2 = System.identityHashCode(lock1); 349 if (LOG) { 350 TTY.println("Forcing a system identity hashcode on lock object " + h1); 351 TTY.println("Forcing a system identity hashcode on lock1 object " + h2); 352 } 353 } 354 355 public static ReturnValue testReduceOSRTrivialLoop() { 356 for (int i = 0; i < limit * limit; i++) { 357 GraalDirectives.blackhole(i); 358 if (GraalDirectives.inCompiledCode()) { 359 return ReturnValue.SUCCESS; 360 } 361 } 362 return ReturnValue.FAILURE; 363 } 364 365 public static ReturnValue testOuterLockImmediateDeoptAfter() { 366 ReturnValue ret = ReturnValue.FAILURE; 367 synchronized (lock) { 368 for (int i = 1; i < 10 * limit; i++) { 369 GraalDirectives.blackhole(i); 370 if (i % 33 == 0) { 371 ret = ReturnValue.SUCCESS; 372 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 373 GraalDirectives.blackhole(ret); 374 } 375 } 376 } 377 GraalDirectives.controlFlowAnchor(); 378 if (GraalDirectives.inCompiledCode()) { 379 throw new Error("Must not be part of compiled code"); 380 } 381 return ret; 382 } 383 } 384 385 public static ReturnValue testOuterInnerLockImmediateDeoptAfter() { 386 ReturnValue ret = ReturnValue.FAILURE; 387 synchronized (lock) { 388 for (int i = 1; i < 10 * limit; i++) { 389 synchronized (lock1) { 390 GraalDirectives.blackhole(i); 391 if (i % 33 == 0) { 392 ret = ReturnValue.SUCCESS; 393 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 394 GraalDirectives.blackhole(ret); 395 } 396 } 397 } 398 } 399 GraalDirectives.controlFlowAnchor(); 400 GraalDirectives.deoptimize(); 401 return ret; 402 } 403 } 404 405 public static ReturnValue testOuterLockCompileRestOfMethod() { 406 ReturnValue ret = ReturnValue.FAILURE; 407 synchronized (lock) { 408 for (int i = 1; i < limit; i++) { 409 GraalDirectives.blackhole(i); 410 if (i % 1001 == 0) { 411 ret = ReturnValue.SUCCESS; 412 System.gc(); 413 } 414 } 415 return ret; 416 } 417 } 418 419 public static ReturnValue testOuterInnerLockCompileRestOfMethod() { 420 ReturnValue ret = ReturnValue.FAILURE; 421 synchronized (lock) { 422 for (int i = 1; i < 10 * limit; i++) { 423 synchronized (lock1) { 424 GraalDirectives.blackhole(i); 425 if (i % 33 == 0) { 426 ret = ReturnValue.SUCCESS; 427 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 428 GraalDirectives.blackhole(ret); 429 System.gc(); 430 } 431 } 432 } 433 } 434 GraalDirectives.controlFlowAnchor(); 435 if (!GraalDirectives.inCompiledCode()) { 436 throw new Error("Must part of compiled code"); 437 } 438 return ret; 439 } 440 } 441 442 public static ReturnValue testOuterInnerLockDepth1CompileRestOfMethod() { 443 // testing the order of the lock releasing 444 ReturnValue ret = ReturnValue.FAILURE; 445 synchronized (lock) { 446 synchronized (lock1) { 447 for (int i = 1; i < limit; i++) { 448 GraalDirectives.blackhole(i); 449 if (i % 1001 == 0) { 450 ret = ReturnValue.SUCCESS; 451 if (GraalDirectives.inCompiledCode() && i + 33 > (limit)) { 452 GraalDirectives.blackhole(ret); 453 System.gc(); 454 } 455 } 456 } 457 } 458 GraalDirectives.controlFlowAnchor(); 459 if (!GraalDirectives.inCompiledCode()) { 460 throw new Error("Must part of compiled code"); 461 } else { 462 // lock 1 must be free 463 if (isMonitorLockHeld(lock1)) { 464 throw new Error("Lock 1 must have been released already"); 465 } 466 467 // lock 2 must still be locked and cannot be acquired by another thread 468 if (!isMonitorLockHeldByThread(lock, Thread.currentThread())) { 469 throw new Error("Lock must not have been released already"); 470 } 471 } 472 return ret; 473 } 474 } 475 476 public static ReturnValue testOuterInnerLockDepth1DeoptAfter() { 477 // testing the order of the lock releasing 478 ReturnValue ret = ReturnValue.FAILURE; 479 synchronized (lock) { 480 synchronized (lock1) { 481 for (int i = 1; i < 10 * limit; i++) { 482 GraalDirectives.blackhole(i); 483 if (i % 33 == 0) { 484 ret = ReturnValue.SUCCESS; 485 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 486 GraalDirectives.blackhole(ret); 487 } 488 } 489 } 490 GraalDirectives.controlFlowAnchor(); 491 GraalDirectives.deoptimize(); 492 if (GraalDirectives.inCompiledCode()) { 493 throw new Error("Must not part of compiled code"); 494 } 495 } 496 } 497 return ret; 498 } 499 500 public static ReturnValue testOuterInnerLockDepth1RecursiveCompileRestOfMethod1() { 501 // testing the order of the lock releasing 502 ReturnValue ret = ReturnValue.FAILURE; 503 synchronized (lock) { 504 synchronized (lock) { 505 for (int i = 1; i < 10 * limit; i++) { 506 GraalDirectives.blackhole(i); 507 if (i % 33 == 0) { 508 ret = ReturnValue.SUCCESS; 509 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 510 GraalDirectives.blackhole(ret); 511 } 512 } 513 } 514 } 515 GraalDirectives.controlFlowAnchor(); 516 if (!GraalDirectives.inCompiledCode()) { 517 throw new Error("Must part of compiled code"); 518 } 519 return ret; 520 } 521 } 522 523 public static ReturnValue testOuterInnerLockDepth1RecursiveCompileRestOfMethod2() { 524 // testing the order of the lock releasing 525 final Object l = lock; 526 ReturnValue ret = ReturnValue.FAILURE; 527 synchronized (l) { 528 synchronized (l) { 529 for (int i = 1; i < 10 * limit; i++) { 530 GraalDirectives.blackhole(i); 531 if (i % 33 == 0) { 532 ret = ReturnValue.SUCCESS; 533 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 534 GraalDirectives.blackhole(ret); 535 } 536 } 537 } 538 } 539 GraalDirectives.controlFlowAnchor(); 540 if (!GraalDirectives.inCompiledCode()) { 541 throw new Error("Must part of compiled code"); 542 } 543 return ret; 544 } 545 } 546 547 public static ReturnValue testRecursiveLockingRoot() { 548 // testing the order of the lock releasing 549 final Object l = lock; 550 ReturnValue ret = ReturnValue.FAILURE; 551 synchronized (l) { 552 synchronized (l) { 553 for (int i = 1; i < limit; i++) { 554 GraalDirectives.blackhole(i); 555 testRecursiveLockingLeaf(); 556 if (i % 33 == 0) { 557 ret = ReturnValue.SUCCESS; 558 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 559 GraalDirectives.blackhole(ret); 560 } 561 } 562 } 563 } 564 GraalDirectives.controlFlowAnchor(); 565 if (!GraalDirectives.inCompiledCode()) { 566 throw new Error("Must part of compiled code"); 567 } 568 return ret; 569 } 570 } 571 572 public static ReturnValue testRecursiveLockingLeaf() { 573 // testing the order of the lock releasing 574 final Object l = lock; 575 ReturnValue ret = ReturnValue.FAILURE; 576 synchronized (l) { 577 synchronized (l) { 578 for (int i = 1; i < limit; i++) { 579 GraalDirectives.blackhole(i); 580 if (i % 33 == 0) { 581 ret = ReturnValue.SUCCESS; 582 } 583 } 584 } 585 return ret; 586 } 587 } 588 589 public static ReturnValue testRecursiveRootNoOSR() { 590 // testing the order of the lock releasing 591 final Object l = lock; 592 synchronized (l) { 593 ReturnValue ret = ReturnValue.FAILURE; 594 for (int i = 0; i < 5; i++) { 595 if (GraalDirectives.inCompiledCode()) { 596 ret = testRecursiveLeafOSR(); 597 } 598 GraalDirectives.controlFlowAnchor(); 599 if (ret == ReturnValue.FAILURE) { 600 return ret; 601 } 602 } 603 GraalDirectives.controlFlowAnchor(); 604 return ret; 605 } 606 } 607 608 public static ReturnValue testRecursiveLeafOSR() { 609 ReturnValue ret = ReturnValue.FAILURE; 610 // lock is already locked by the caller 611 synchronized (lock) { 612 for (int i = 1; i < 10 * limit; i++) { 613 GraalDirectives.blackhole(i); 614 if (i % 33 == 0) { 615 ret = ReturnValue.SUCCESS; 616 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 617 GraalDirectives.blackhole(ret); 618 } 619 } 620 } 621 GraalDirectives.controlFlowAnchor(); 622 return ret; 623 } 624 } 625 626 // test cases for optimizations 627 public static ReturnValue testOuterLockCompileRestOfMethodSubsequentLock() { 628 final Object monitor = lock; 629 ReturnValue ret = ReturnValue.FAILURE; 630 synchronized (monitor) { 631 for (int i = 1; i < 10 * limit; i++) { 632 GraalDirectives.blackhole(i); 633 if (i % 33 == 0) { 634 ret = ReturnValue.SUCCESS; 635 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 636 GraalDirectives.blackhole(ret); 637 } 638 } 639 } 640 } 641 synchronized (monitor) { 642 GraalDirectives.controlFlowAnchor(); 643 if (!GraalDirectives.inCompiledCode()) { 644 throw new Error("Must part of compiled code"); 645 } 646 } 647 return ret; 648 649 } 650 651 public static ReturnValue testOuterInnerSameLockCompileRestOfMethod() { 652 final Object monitor = lock; 653 ReturnValue ret = ReturnValue.FAILURE; 654 synchronized (monitor) { 655 for (int i = 1; i < 10 * limit; i++) { 656 synchronized (monitor) { 657 GraalDirectives.blackhole(i); 658 if (i % 33 == 0) { 659 ret = ReturnValue.SUCCESS; 660 if (GraalDirectives.inCompiledCode() && i + 33 > (10 * limit)) { 661 GraalDirectives.blackhole(ret); 662 } 663 } 664 } 665 } 666 GraalDirectives.controlFlowAnchor(); 667 if (!GraalDirectives.inCompiledCode()) { 668 throw new Error("Must part of compiled code"); 669 } 670 return ret; 671 } 672 } 673 674 }