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