1 /* 2 * Copyright (c) 2018, 2023, 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.internal.vm; 27 28 import jdk.internal.misc.Unsafe; 29 import jdk.internal.vm.annotation.DontInline; 30 import jdk.internal.vm.annotation.IntrinsicCandidate; 31 import sun.security.action.GetPropertyAction; 32 33 import java.util.EnumSet; 34 import java.util.Set; 35 import java.util.function.Supplier; 36 import jdk.internal.access.SharedSecrets; 37 import jdk.internal.vm.annotation.Hidden; 38 39 /** 40 * A one-shot delimited continuation. 41 */ 42 public class Continuation { 43 private static final Unsafe U = Unsafe.getUnsafe(); 44 private static final long MOUNTED_OFFSET = U.objectFieldOffset(Continuation.class, "mounted"); 45 private static final boolean PRESERVE_SCOPED_VALUE_CACHE; 46 private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); 47 static { 48 // Android-removed: This check during startup isn't necessary on ART. 49 // ContinuationSupport.ensureSupported(); 50 StackChunk.init()51 StackChunk.init(); // ensure StackChunk class is initialized 52 53 // Android-changed: Remove ScopedValue until the feature is finalized. 54 // String value = GetPropertyAction.privilegedGetProperty("jdk.preserveScopedValueCache"); 55 // PRESERVE_SCOPED_VALUE_CACHE = (value == null) || Boolean.parseBoolean(value); 56 PRESERVE_SCOPED_VALUE_CACHE = false; 57 } 58 59 /** Reason for pinning */ 60 public enum Pinned { 61 /** Native frame on stack */ NATIVE, 62 /** Monitor held */ MONITOR, 63 /** In critical section */ CRITICAL_SECTION } 64 65 /** Preemption attempt result */ 66 public enum PreemptStatus { 67 /** Success */ SUCCESS(null), 68 /** Permanent failure */ PERM_FAIL_UNSUPPORTED(null), 69 /** Permanent failure: continuation already yielding */ PERM_FAIL_YIELDING(null), 70 /** Permanent failure: continuation not mounted on the thread */ PERM_FAIL_NOT_MOUNTED(null), 71 /** Transient failure: continuation pinned due to a held CS */ TRANSIENT_FAIL_PINNED_CRITICAL_SECTION(Pinned.CRITICAL_SECTION), 72 /** Transient failure: continuation pinned due to native frame */ TRANSIENT_FAIL_PINNED_NATIVE(Pinned.NATIVE), 73 /** Transient failure: continuation pinned due to a held monitor */ TRANSIENT_FAIL_PINNED_MONITOR(Pinned.MONITOR); 74 75 final Pinned pinned; PreemptStatus(Pinned reason)76 private PreemptStatus(Pinned reason) { this.pinned = reason; } 77 /** 78 * Whether or not the continuation is pinned. 79 * @return whether or not the continuation is pinned 80 **/ pinned()81 public Pinned pinned() { return pinned; } 82 } 83 pinnedReason(int reason)84 private static Pinned pinnedReason(int reason) { 85 return switch (reason) { 86 case 2 -> Pinned.CRITICAL_SECTION; 87 case 3 -> Pinned.NATIVE; 88 case 4 -> Pinned.MONITOR; 89 default -> throw new AssertionError("Unknown pinned reason: " + reason); 90 }; 91 } 92 currentCarrierThread()93 private static Thread currentCarrierThread() { 94 // TODO: Get it from java.lang.Thread. 95 // return JLA.currentCarrierThread(); 96 return null; 97 } 98 99 static { 100 try { 101 // Android-removed: Not needed on Android. 102 // registerNatives(); 103 104 // init Pinned to avoid classloading during mounting 105 pinnedReason(2); 106 } catch (Exception e) { 107 throw new InternalError(e); 108 } 109 } 110 111 private final Runnable target; 112 113 /* While the native JVM code is aware that every continuation has a scope, it is, for the most part, 114 * oblivious to the continuation hierarchy. The only time this hierarchy is traversed in native code 115 * is when a hierarchy of continuations is mounted on the native stack. 116 */ 117 private final ContinuationScope scope; 118 private Continuation parent; // null for native stack 119 private Continuation child; // non-null when we're yielded in a child continuation 120 121 private StackChunk tail; 122 123 private boolean done; 124 private volatile boolean mounted; 125 private Object yieldInfo; 126 private boolean preempted; 127 128 private Object[] scopedValueCache; 129 130 /** 131 * Constructs a continuation 132 * @param scope the continuation's scope, used in yield 133 * @param target the continuation's body 134 */ Continuation(ContinuationScope scope, Runnable target)135 public Continuation(ContinuationScope scope, Runnable target) { 136 this.scope = scope; 137 this.target = target; 138 } 139 140 @Override toString()141 public String toString() { 142 return super.toString() + " scope: " + scope; 143 } 144 getScope()145 public ContinuationScope getScope() { 146 return scope; 147 } 148 getParent()149 public Continuation getParent() { 150 return parent; 151 } 152 153 /** 154 * Returns the current innermost continuation with the given scope 155 * @param scope the scope 156 * @return the continuation 157 */ getCurrentContinuation(ContinuationScope scope)158 public static Continuation getCurrentContinuation(ContinuationScope scope) { 159 // TODO: Implement this. 160 // Continuation cont = JLA.getContinuation(currentCarrierThread()); 161 Continuation cont = null; 162 while (cont != null && cont.scope != scope) 163 cont = cont.parent; 164 return cont; 165 } 166 167 /** 168 * Creates a StackWalker for this continuation 169 * @return a new StackWalker 170 */ stackWalker()171 public StackWalker stackWalker() { 172 return stackWalker(EnumSet.noneOf(StackWalker.Option.class)); 173 } 174 175 /** 176 * Creates a StackWalker for this continuation 177 * @param options the StackWalker's configuration options 178 * @return a new StackWalker 179 */ stackWalker(Set<StackWalker.Option> options)180 public StackWalker stackWalker(Set<StackWalker.Option> options) { 181 return stackWalker(options, this.scope); 182 } 183 184 /** 185 * Creates a StackWalker for this continuation and enclosing ones up to the given scope 186 * @param options the StackWalker's configuration options 187 * @param scope the delimiting continuation scope for the stack 188 * @return a new StackWalker 189 */ stackWalker(Set<StackWalker.Option> options, ContinuationScope scope)190 public StackWalker stackWalker(Set<StackWalker.Option> options, ContinuationScope scope) { 191 // TODO: Implement this. 192 // return JLA.newStackWalkerInstance(options, scope, innermost()); 193 return null; 194 } 195 196 /** 197 * Obtains a stack trace for this unmounted continuation 198 * @return the stack trace 199 * @throws IllegalStateException if the continuation is mounted 200 */ getStackTrace()201 public StackTraceElement[] getStackTrace() { 202 return stackWalker(EnumSet.of(StackWalker.Option.SHOW_REFLECT_FRAMES)) 203 .walk(s -> s.map(StackWalker.StackFrame::toStackTraceElement) 204 .toArray(StackTraceElement[]::new)); 205 } 206 207 /// Support for StackWalker wrapWalk(Continuation inner, ContinuationScope scope, Supplier<R> walk)208 public static <R> R wrapWalk(Continuation inner, ContinuationScope scope, Supplier<R> walk) { 209 try { 210 for (Continuation c = inner; c != null && c.scope != scope; c = c.parent) 211 c.mount(); 212 return walk.get(); 213 } finally { 214 for (Continuation c = inner; c != null && c.scope != scope; c = c.parent) 215 c.unmount(); 216 } 217 } 218 innermost()219 private Continuation innermost() { 220 Continuation c = this; 221 while (c.child != null) 222 c = c.child; 223 return c; 224 } 225 mount()226 private void mount() { 227 if (!compareAndSetMounted(false, true)) 228 throw new IllegalStateException("Mounted!!!!"); 229 } 230 unmount()231 private void unmount() { 232 setMounted(false); 233 } 234 235 /** 236 * Mounts and runs the continuation body. If suspended, continues it from the last suspend point. 237 */ run()238 public final void run() { 239 while (true) { 240 mount(); 241 // Android-removed: Remove ScopedValue until the feature is finalized. 242 // JLA.setScopedValueCache(scopedValueCache); 243 244 if (done) 245 throw new IllegalStateException("Continuation terminated"); 246 247 Thread t = currentCarrierThread(); 248 // TODO: Implement this. 249 /* 250 if (parent != null) { 251 if (parent != JLA.getContinuation(t)) 252 throw new IllegalStateException(); 253 } else 254 this.parent = JLA.getContinuation(t); 255 JLA.setContinuation(t, this); 256 */ 257 258 try { 259 // TODO: Implement this. 260 // boolean isVirtualThread = (scope == JLA.virtualThreadContinuationScope()); 261 boolean isVirtualThread = true; 262 if (!isStarted()) { // is this the first run? (at this point we know !done) 263 enterSpecial(this, false, isVirtualThread); 264 } else { 265 assert !isEmpty(); 266 enterSpecial(this, true, isVirtualThread); 267 } 268 } finally { 269 fence(); 270 try { 271 assert isEmpty() == done : "empty: " + isEmpty() + " done: " + done + " cont: " + Integer.toHexString(System.identityHashCode(this)); 272 // TODO: Implement this. 273 // JLA.setContinuation(currentCarrierThread(), this.parent); 274 if (parent != null) 275 parent.child = null; 276 277 postYieldCleanup(); 278 279 unmount(); 280 // Android-removed: Remove ScopedValue until the feature is finalized. 281 /* 282 if (PRESERVE_SCOPED_VALUE_CACHE) { 283 scopedValueCache = JLA.scopedValueCache(); 284 } else { 285 scopedValueCache = null; 286 } 287 JLA.setScopedValueCache(null); 288 */ 289 } catch (Throwable e) { e.printStackTrace(); System.exit(1); } 290 } 291 // we're now in the parent continuation 292 293 assert yieldInfo == null || yieldInfo instanceof ContinuationScope; 294 if (yieldInfo == null || yieldInfo == scope) { 295 this.parent = null; 296 this.yieldInfo = null; 297 return; 298 } else { 299 parent.child = this; 300 parent.yield0((ContinuationScope)yieldInfo, this); 301 parent.child = null; 302 } 303 } 304 } 305 postYieldCleanup()306 private void postYieldCleanup() { 307 if (done) { 308 this.tail = null; 309 } 310 } 311 finish()312 private void finish() { 313 done = true; 314 assert isEmpty(); 315 } 316 317 @IntrinsicCandidate doYield()318 private native static int doYield(); 319 320 @IntrinsicCandidate enterSpecial(Continuation c, boolean isContinue, boolean isVirtualThread)321 private native static void enterSpecial(Continuation c, boolean isContinue, boolean isVirtualThread); 322 323 324 @Hidden 325 @DontInline 326 @IntrinsicCandidate enter(Continuation c, boolean isContinue)327 private static void enter(Continuation c, boolean isContinue) { 328 // This method runs in the "entry frame". 329 // A yield jumps to this method's caller as if returning from this method. 330 try { 331 c.enter0(); 332 } finally { 333 c.finish(); 334 } 335 } 336 337 @Hidden enter0()338 private void enter0() { 339 target.run(); 340 } 341 isStarted()342 private boolean isStarted() { 343 return tail != null; 344 } 345 isEmpty()346 private boolean isEmpty() { 347 for (StackChunk c = tail; c != null; c = c.parent()) { 348 if (!c.isEmpty()) 349 return false; 350 } 351 return true; 352 } 353 354 /** 355 * Suspends the current continuations up to the given scope 356 * 357 * @param scope The {@link ContinuationScope} to suspend 358 * @return {@code true} for success; {@code false} for failure 359 * @throws IllegalStateException if not currently in the given {@code scope}, 360 */ 361 @Hidden yield(ContinuationScope scope)362 public static boolean yield(ContinuationScope scope) { 363 // TODO: Implement this. 364 // Continuation cont = JLA.getContinuation(currentCarrierThread()); 365 Continuation cont = null; 366 Continuation c; 367 for (c = cont; c != null && c.scope != scope; c = c.parent) 368 ; 369 if (c == null) 370 throw new IllegalStateException("Not in scope " + scope); 371 372 return cont.yield0(scope, null); 373 } 374 375 @Hidden yield0(ContinuationScope scope, Continuation child)376 private boolean yield0(ContinuationScope scope, Continuation child) { 377 preempted = false; 378 379 if (scope != this.scope) 380 this.yieldInfo = scope; 381 int res = doYield(); 382 U.storeFence(); // needed to prevent certain transformations by the compiler 383 384 assert scope != this.scope || yieldInfo == null : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res; 385 assert yieldInfo == null || scope == this.scope || yieldInfo instanceof Integer : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res; 386 387 if (child != null) { // TODO: ugly 388 if (res != 0) { 389 child.yieldInfo = res; 390 } else if (yieldInfo != null) { 391 assert yieldInfo instanceof Integer; 392 child.yieldInfo = yieldInfo; 393 } else { 394 child.yieldInfo = res; 395 } 396 this.yieldInfo = null; 397 } else { 398 if (res == 0 && yieldInfo != null) { 399 res = (Integer)yieldInfo; 400 } 401 this.yieldInfo = null; 402 403 if (res == 0) 404 onContinue(); 405 else 406 onPinned0(res); 407 } 408 assert yieldInfo == null; 409 410 return res == 0; 411 } 412 onPinned0(int reason)413 private void onPinned0(int reason) { 414 onPinned(pinnedReason(reason)); 415 } 416 417 /** 418 * Called when suspending if the continuation is pinned 419 * @param reason the reason for pinning 420 */ onPinned(Pinned reason)421 protected void onPinned(Pinned reason) { 422 throw new IllegalStateException("Pinned: " + reason); 423 } 424 425 /** 426 * Called when the continuation continues 427 */ onContinue()428 protected void onContinue() { 429 } 430 431 /** 432 * Tests whether this continuation is completed 433 * @return whether this continuation is completed 434 */ isDone()435 public boolean isDone() { 436 return done; 437 } 438 439 /** 440 * Tests whether this unmounted continuation was unmounted by forceful preemption (a successful tryPreempt) 441 * @return whether this unmounted continuation was unmounted by forceful preemption 442 */ isPreempted()443 public boolean isPreempted() { 444 return preempted; 445 } 446 447 /** 448 * Pins the current continuation (enters a critical section). 449 * This increments an internal semaphore that, when greater than 0, pins the continuation. 450 */ 451 // Android-removed: unused 452 // public static native void pin(); 453 454 /** 455 * Unpins the current continuation (exits a critical section). 456 * This decrements an internal semaphore that, when equal 0, unpins the current continuation 457 * if pinned with {@link #pin()}. 458 */ 459 // public static native void unpin(); 460 461 /** 462 * Tests whether the given scope is pinned. 463 * This method is slow. 464 * 465 * @param scope the continuation scope 466 * @return {@code} true if we're in the give scope and are pinned; {@code false otherwise} 467 */ isPinned(ContinuationScope scope)468 public static boolean isPinned(ContinuationScope scope) { 469 int res = isPinned0(scope); 470 return res != 0; 471 } 472 isPinned0(ContinuationScope scope)473 static private native int isPinned0(ContinuationScope scope); 474 fence()475 private boolean fence() { 476 U.storeFence(); // needed to prevent certain transformations by the compiler 477 return true; 478 } 479 compareAndSetMounted(boolean expectedValue, boolean newValue)480 private boolean compareAndSetMounted(boolean expectedValue, boolean newValue) { 481 // TODO: Implement Unsafe.compareAndSetBoolean or replace it with VarHandle. 482 // return U.compareAndSetBoolean(this, MOUNTED_OFFSET, expectedValue, newValue); 483 return false; 484 } 485 setMounted(boolean newValue)486 private void setMounted(boolean newValue) { 487 mounted = newValue; // MOUNTED.setVolatile(this, newValue); 488 } 489 490 // Android-removed: unused. 491 /* 492 private String id() { 493 return Integer.toHexString(System.identityHashCode(this)) 494 + " [" + currentCarrierThread().threadId() + "]"; 495 } 496 */ 497 498 /** 499 * Tries to forcefully preempt this continuation if it is currently mounted on the given thread 500 * Subclasses may throw an {@link UnsupportedOperationException}, but this does not prevent 501 * the continuation from being preempted on a parent scope. 502 * 503 * @param thread the thread on which to forcefully preempt this continuation 504 * @return the result of the attempt 505 * @throws UnsupportedOperationException if this continuation does not support preemption 506 */ tryPreempt(Thread thread)507 public PreemptStatus tryPreempt(Thread thread) { 508 throw new UnsupportedOperationException("Not implemented"); 509 } 510 511 // native method 512 // Android-removed: Not needed on Android. 513 // private static native void registerNatives(); 514 515 // Android-removed: unused. 516 /* 517 private void dump() { 518 System.out.println("Continuation@" + Long.toHexString(System.identityHashCode(this))); 519 System.out.println("\tparent: " + parent); 520 int i = 0; 521 for (StackChunk c = tail; c != null; c = c.parent()) { 522 System.out.println("\tChunk " + i); 523 System.out.println(c); 524 } 525 } 526 */ 527 528 // Android-removed: unused. 529 /* 530 private static boolean isEmptyOrTrue(String property) { 531 String value = GetPropertyAction.privilegedGetProperty(property); 532 if (value == null) 533 return false; 534 return value.isEmpty() || Boolean.parseBoolean(value); 535 } 536 */ 537 } 538