/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.scheduler; import static org.jikesrvm.runtime.ExitStatus.EXIT_STATUS_DUMP_STACK_AND_DIE; import static org.jikesrvm.runtime.ExitStatus.EXIT_STATUS_DYING_WITH_UNCAUGHT_EXCEPTION; import static org.jikesrvm.runtime.ExitStatus.EXIT_STATUS_MAIN_THREAD_COULD_NOT_LAUNCH; import static org.jikesrvm.runtime.ExitStatus.EXIT_STATUS_RECURSIVELY_SHUTTING_DOWN; import static org.jikesrvm.runtime.SysCall.sysCall; import static org.jikesrvm.objectmodel.ThinLockConstants.TL_THREAD_ID_SHIFT; import java.security.AccessController; import java.security.PrivilegedAction; import org.jikesrvm.VM; import org.jikesrvm.adaptive.OSRListener; import org.jikesrvm.adaptive.OnStackReplacementEvent; import org.jikesrvm.adaptive.measurements.RuntimeMeasurements; import org.jikesrvm.architecture.AbstractRegisters; import org.jikesrvm.architecture.ArchitectureFactory; import org.jikesrvm.architecture.StackFrameLayout; import org.jikesrvm.classloader.MemberReference; import org.jikesrvm.classloader.NormalMethod; import org.jikesrvm.classloader.RVMMethod; import org.jikesrvm.compilers.baseline.BaselineCompiledMethod; import org.jikesrvm.compilers.common.CodeArray; import org.jikesrvm.compilers.common.CompiledMethod; import org.jikesrvm.compilers.common.CompiledMethods; import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod; import org.jikesrvm.compilers.opt.runtimesupport.OptEncodedCallSiteTree; import org.jikesrvm.compilers.opt.runtimesupport.OptMachineCodeMap; import org.jikesrvm.jni.JNIEnvironment; import org.jikesrvm.mm.mminterface.CollectorThread; import org.jikesrvm.mm.mminterface.MemoryManager; import org.jikesrvm.mm.mminterface.ThreadContext; import org.jikesrvm.objectmodel.ObjectModel; import org.jikesrvm.osr.ObjectHolder; import org.jikesrvm.runtime.BootRecord; import org.jikesrvm.runtime.Entrypoints; import org.jikesrvm.runtime.Magic; import org.jikesrvm.runtime.Memory; import org.jikesrvm.runtime.RuntimeEntrypoints; import org.jikesrvm.runtime.Time; import org.jikesrvm.tuningfork.Feedlet; import org.jikesrvm.tuningfork.TraceEngine; import org.jikesrvm.util.Services; import org.jikesrvm.util.UnimplementedError; import org.vmmagic.pragma.BaselineNoRegisters; import org.vmmagic.pragma.BaselineSaveLSRegisters; import org.vmmagic.pragma.Entrypoint; import org.vmmagic.pragma.Inline; import org.vmmagic.pragma.Interruptible; import org.vmmagic.pragma.NoCheckStore; import org.vmmagic.pragma.NoInline; import org.vmmagic.pragma.NoOptCompile; import org.vmmagic.pragma.NonMoving; import org.vmmagic.pragma.Uninterruptible; import org.vmmagic.pragma.UninterruptibleNoWarn; import org.vmmagic.pragma.Unpreemptible; import org.vmmagic.pragma.UnpreemptibleNoWarn; import org.vmmagic.pragma.Untraced; import org.vmmagic.unboxed.Address; import org.vmmagic.unboxed.Offset; import org.vmmagic.unboxed.Word; /** * A generic java thread's execution context. * <p> * Threads use a state machine to indicate to other threads, as well as VM * services, how this thread should be treated in the case of an asynchronous * request, for example in the case of GC. The state machine uses the * following states: * <ul> * <li>NEW</li> * <li>IN_JAVA</li> * <li>IN_NATIVE</li> * <li>IN_JNI</li> * <li>IN_JAVA_TO_BLOCK</li> * <li>BLOCKED_IN_NATIVE</li> * <li>BLOCKED_IN_JNI</li> * <li>TERMINATED</li> * </ul> * The following state transitions are legal: * <ul> * <li>NEW to IN_JAVA: occurs when the thread is actually started. At this * point it is safe to expect that the thread will reach a safe point in * some bounded amount of time, at which point it will have a complete * execution context, and this will be able to have its stack traces by GC.</li> * <li>IN_JAVA to IN_JAVA_TO_BLOCK: occurs when an asynchronous request is * made, for example to stop for GC, do a mutator flush, or do an isync on PPC.</li> * <li>IN_JAVA to IN_NATIVE: occurs when the code opts to run in privileged mode, * without synchronizing with GC. This state transition is only performed by * HeavyCondLock, in cases where the thread is about to go idle while waiting * for notifications (such as in the case of park, wait, or sleep).</li> * <li>IN_JAVA to IN_JNI: occurs in response to a JNI downcall, or return from a JNI * upcall.</li> * <li>IN_JAVA_TO_BLOCK to BLOCKED_IN_NATIVE: occurs when a thread that had been * asked to perform an async activity decides to go idle instead. This state * always corresponds to a notification being sent to other threads, letting * them know that this thread is idle. When the thread is idle, any asynchronous * requests (such as mutator flushes) can instead be performed on behalf of this * thread by other threads, since this thread is guaranteed not to be running * any user Java code, and will not be able to return to running Java code without * first blocking, and waiting to be unblocked (see BLOCKED_IN_NATIVE to IN_JAVA * transition.</li> * <li>IN_JAVA_TO_BLOCK to BLOCKED_IN_JNI: occurs when a thread that had been * asked to perform an async activity decides to make a JNI downcall, or return * from a JNI upcall, instead. In all other regards, this is identical to the * IN_JAVA_TO_BLOCK to BLOCKED_IN_NATIVE transition.</li> * <li>IN_NATIVE to IN_JAVA: occurs when a thread returns from idling or running * privileged code to running Java code.</li> * <li>BLOCKED_IN_NATIVE to IN_JAVA: occurs when a thread that had been asked to * perform an async activity while running privileged code or idling decides to * go back to running Java code. The actual transition is preceded by the * thread first performing any requested actions (such as mutator flushes) and * waiting for a notification that it is safe to continue running (for example, * the thread may wait until GC is finished).</li> * <li>IN_JNI to IN_JAVA: occurs when a thread returns from a JNI downcall, or makes * a JNI upcall.</li> * <li>BLOCKED_IN_JNI to IN_JAVA: same as BLOCKED_IN_NATIVE to IN_JAVA, except that * this occurs in response to a return from a JNI downcall, or as the thread * makes a JNI upcall.</li> * <li>IN_JAVA to TERMINATED: the thread has terminated, and will never reach any * more safe points, and thus will not be able to respond to any more requests * for async activities.</li> * </ul> * Observe that the transitions from BLOCKED_IN_NATIVE and BLOCKED_IN_JNI to IN_JAVA * constitute a safe point. Code running in BLOCKED_IN_NATIVE or BLOCKED_IN_JNI is * "GC safe" but is not quite at a safe point; safe points are special in that * they allow the thread to perform async activities (such as mutator flushes or * isyncs), while GC safe code will not necessarily perform either. * * @see org.jikesrvm.mm.mminterface.CollectorThread * @see FinalizerThread * @see org.jikesrvm.adaptive.measurements.organizers.Organizer */ @Uninterruptible @NonMoving public final class RVMThread extends ThreadContext { /* * debug and statistics */ /** Trace thread blockage */ protected static final boolean traceBlock = false; /** Trace when a thread is really blocked */ protected static final boolean traceReallyBlock = false || traceBlock; protected static final boolean traceAboutToTerminate = false; protected static final boolean dumpStackOnBlock = false; // DANGEROUS! can lead to crashes! protected static final boolean traceBind = false; /** Trace thread start/stop */ protected static final boolean traceAcct = false; /** Trace execution */ protected static final boolean trace = false; /** Trace thread termination */ private static final boolean traceTermination = false; /** Trace adjustments to stack size */ private static final boolean traceAdjustments = false; /** Trace thread priority */ private static final boolean tracePriority = false; /** Never kill threads. Useful for testing bugs related to interaction of thread death with for example MMTk. For production, this should never be set to true. */ private static final boolean neverKillThreads = false; /** Generate statistics? */ private static final boolean STATS = Lock.STATS; /** Number of wait operations */ static long waitOperations; /** Number of timed wait operations */ static long timedWaitOperations; /** total number of milliseconds this thread has waited */ static long totalWaitTime; /** start time of the last wait */ static long waitTimeStart; /** Number of notify operations */ static int notifyOperations; /** Number of notifyAll operations */ static int notifyAllOperations; public static final boolean ALWAYS_LOCK_ON_STATE_TRANSITION = false; /* * definitions for thread status for interaction of Java-native transitions * and requests for threads to stop. THESE ARE PRIVATE TO THE SCHEDULER, and * are only used deep within the stack. * Note: If you change the assignments, update READABLE_EXEC_STATUS to ensure * correct debug output. */ /** * Thread has not yet started. This state holds right up until just before we * call pthread_create(). */ public static final int NEW = 0; /** Thread is executing "normal" Java bytecode */ public static final int IN_JAVA = 1; /** * A state used by the scheduler to mark that a thread is in privileged code * that does not need to synchronize with the collector. This is a special * state, similar to the IN_JNI state but requiring different interaction with * the collector (as there is no JNI stack frame, the registers have to be * saved in contextRegisters). As well, this state should only be entered * from privileged code in the org.jikesrvm.scheduler package. Typically, * this state is entered using a call to enterNative() just prior to idling * the thread; though it would not be wrong to enter it prior to any other * long-running activity that does not require interaction with the GC. */ public static final int IN_NATIVE = 2; /** * Same as IN_NATIVE, except that we're executing JNI code and thus have a * JNI stack frame and JNI environment, and thus the GC can load registers * from there rather than using contextRegisters. */ public static final int IN_JNI = 3; /** * thread is in Java code but is expected to block. the transition from IN_JAVA * to IN_jAVA_TO_BLOCK happens as a result of an asynchronous call by the GC * or any other internal VM code that requires this thread to perform an * asynchronous activity (another example is the request to do an isync on PPC). * the point is that we're waiting for the thread to reach a safe point and * expect this to happen in bounded time; but if the thread were to escape to * native we want to know about it. thus, transitions into native code while * in the IN_JAVA_TO_BLOCK state result in a notification (broadcast on the * thread's monitor) and a state change to BLOCKED_IN_NATIVE. Observe that it * is always safe to conservatively change IN_JAVA to IN_JAVA_TO_BLOCK. */ public static final int IN_JAVA_TO_BLOCK = 4; /** * thread is in native code, and is to block before returning to Java code. * the transition from IN_NATIVE to BLOCKED_IN_NATIVE happens as a result * of an asynchronous call by the GC or any other internal VM code that * requires this thread to perform an asynchronous activity (another example * is the request to do an isync on PPC). as well, code entering privileged * code that would otherwise go from IN_JAVA to IN_NATIVE will go to * BLOCKED_IN_NATIVE instead, if the state was IN_JAVA_TO_BLOCK. * <p> * the point of this state is that the thread is guaranteed not to execute * any Java code until: * <ol> * <li>The state changes to IN_NATIVE, and * <li>The thread gets a broadcast on its monitor. * </ol> * Observe that it is always safe to conservatively change IN_NATIVE to * BLOCKED_IN_NATIVE. */ public static final int BLOCKED_IN_NATIVE = 5; /** * like BLOCKED_IN_NATIVE, but indicates that the thread is in JNI rather than * VM native code. */ public static final int BLOCKED_IN_JNI = 6; /** * Thread has died. As in, it's no longer executing any Java code and will * never do so in the future. Once this is set, the GC will no longer mark any * part of the thread as live; the thread's stack will be deleted. Note that * this is distinct from the aboutToTerminate state. */ public static final int TERMINATED = 7; /** Not actually a state but just a marker. */ public static final int LAST_EXEC_STATUS = 8; private static final String[] READABLE_EXEC_STATUS = {"NEW", "IN_JAVA", "IN_NATIVE", "IN_JNI", "IN_JAVA_TO_BLOCK", "BLOCKED_IN_NATIVE", "BLOCKED_IN_JNI", "TERMINATED", "LAST_EXEC_STATUS"}; public static boolean notRunning(int state) { return state == NEW || state == TERMINATED; } /** Registers used by return barrier trampoline */ @Entrypoint private AbstractRegisters trampolineRegisters = ArchitectureFactory.createRegisters(); /** Return address of stack frame hijacked by return barrier */ @Entrypoint private Address hijackedReturnAddress; /** Callee frame pointer for stack frame hijacked by return barrier */ private Address hijackedReturnCalleeFp = Address.zero(); /** Caller frame pointer for stack frame hijacked by return barrier */ private Address hijackedReturnCallerFp = StackFrameLayout.getStackFrameSentinelFP(); /** @return the callee frame pointer for the stack frame hijacked by the return barrier */ public Address getHijackedReturnCalleeFp() { return hijackedReturnCalleeFp; } /** debugging flag for return barrier trampoline */ public static final boolean DEBUG_STACK_TRAMPOLINE = false; /** pointer to bridge code for return barrier trampoline */ public static CodeArray stackTrampolineBridgeInstructions; /** * Thread state. Indicates if the thread is running, and if so, what mode of * execution it is using (Java, VM native, or JNI) */ @Entrypoint private int execStatus; public int getExecStatus() { observeExecStatus(); return execStatus; } private boolean attemptFastExecStatusTransition(int oldState, int newState) { if (Synchronization.tryCompareAndSwap( this, Entrypoints.execStatusField.getOffset(), oldState, newState)) { observeStateTransition(oldState,newState); return true; } else { return false; } } // call this only when holding the lock or if you really know what you're // doing. private void setExecStatus(int newState) { observeStateTransition(execStatus,newState); execStatus = newState; } /** * Is the thread about to terminate? Protected by the thread's monitor. Note * that this field is not set atomically with the entering of the thread onto * the aboutToTerminate array - in fact it happens before that. When this * field is set to true, the thread's stack will no longer be scanned by GC. * Note that this is distinct from the TERMINATED state. */ // FIXME: there should be an execStatus state called TERMINATING that // corresponds to this. that would make a lot more sense. private boolean isAboutToTerminate; public boolean getIsAboutToTerminate() { return isAboutToTerminate; } /** Is this thread in the process of blocking? */ boolean isBlocking; /** * Is the thread no longer executing user code? Protected by the Java monitor * associated with the Thread object. */ boolean isJoinable; /** * Link pointer for queues (specifically ThreadQueue). A thread can only be * on one such queue at a time. The queue that a thread is on is indicated by * <code>queuedOn</code>. */ @Untraced RVMThread next; /** * The queue that the thread is on, or null if the thread is not on a queue * (specifically ThreadQueue). If the thread is on such a queue, the * <code>next</code> field is used as a link pointer. */ @Untraced volatile ThreadQueue queuedOn; /** * @return True if this thread is currently on a queue. */ public boolean isOnQueue() { return queuedOn != null; } /** * Used to handle contention for spin locks */ @Untraced SpinLock awaitingSpinLock; @Untraced RVMThread contenderLink; /** * java.lang.Thread wrapper for this Thread. Not final so it may be assigned * during booting */ private Thread thread; /** Name of the thread (can be changed during execution) */ private String name; /** * The virtual machine terminates when the last non-daemon (user) thread * terminates. */ protected boolean daemon; /** * Scheduling priority for this thread. Note that: * {@link java.lang.Thread#MIN_PRIORITY} <= priority <= * {@link java.lang.Thread#MAX_PRIORITY}. */ private int priority; /** * Index of this thread in {@link #threadBySlot}[]. This value must be non-zero * because it is shifted and used in {@link Object} lock ownership tests. */ @Entrypoint public int threadSlot; public int lockingId; /** * Non-null indicates this is a system thread, that is one used by the system and as such * doesn't have a Runnable... */ private final SystemThread systemThread; /** * The boot thread, can't be final so as to allow initialization during boot * image writing. */ @Entrypoint public static RVMThread bootThread; /** * Is the threading system initialized? */ public static boolean threadingInitialized = false; /** * Number of timer ticks we've seen */ public static long timerTicks; private long yieldpointsTaken; private long yieldpointsTakenFully; private long nativeEnteredBlocked; private long jniEnteredBlocked; /** * Assertion checking while manipulating raw addresses -- see * {@link VM#disableGC()}/{@link VM#enableGC()}. A value of "true" means * it's an error for this thread to call "new". This is only used for * assertion checking; we do not bother to set it when * {@link VM#VerifyAssertions} is false. */ private boolean disallowAllocationsByThisThread; /** * Counts the depth of outstanding calls to {@link VM#disableGC()}. If this * is set, then we should also have {@link #disallowAllocationsByThisThread} * set. The converse also holds. */ private int disableGCDepth = 0; public int barriersEntered = 0; public int barriersExited = 0; /** * Execution stack for this thread. */ @Entrypoint private byte[] stack; /** The {@link Address} of the guard area for {@link #stack}. */ @Entrypoint public Address stackLimit; /* --------- BEGIN IA-specific fields. NOTE: NEED TO REFACTOR --------- */ // On powerpc, these values are in dedicated registers, // we don't have registers to burn on IA32, so we indirect // through the TR register to get them instead. /** * FP for current frame, saved in the prologue of every method */ @Entrypoint Address framePointer; /** * "hidden parameter" for interface invocation thru the IMT */ @Entrypoint int hiddenSignatureId; /** * "hidden parameter" from ArrayIndexOutOfBounds trap to C trap handler */ @Entrypoint int arrayIndexTrapParam; /* --------- END IA-specific fields. NOTE: NEED TO REFACTOR --------- */ /** * Is the next taken yieldpoint in response to a request to perform OSR? */ public boolean yieldToOSRRequested; /** * Is CBS enabled for 'call' yieldpoints? */ public boolean yieldForCBSCall; /** * Is CBS enabled for 'method' yieldpoints? */ public boolean yieldForCBSMethod; /** * Number of CBS samples to take in this window */ public int numCBSCallSamples; /** * Number of call yieldpoints between CBS samples */ public int countdownCBSCall; /** * round robin starting point for CBS samples */ public int firstCBSCallSample; /** * Number of CBS samples to take in this window */ public int numCBSMethodSamples; /** * Number of counter ticks between CBS samples */ public int countdownCBSMethod; /** * round robin starting point for CBS samples */ public int firstCBSMethodSample; /* --------- BEGIN PPC-specific fields. NOTE: NEED TO REFACTOR --------- */ /** * flag indicating this processor needs to execute a memory synchronization * sequence Used for code patching on SMP PowerPCs. */ public boolean codePatchSyncRequested; /* --------- END PPC-specific fields. NOTE: NEED TO REFACTOR --------- */ /** * For builds using counter-based sampling. This field holds a * processor-specific counter so that it can be updated efficiently on SMP's. */ @Entrypoint public int thread_cbs_counter; /** * Should this thread yield at yieldpoints? A value of: 1 means "yes" * (yieldpoints enabled) <= 0 means "no" (yieldpoints disabled) */ private int yieldpointsEnabledCount; /** * Is a takeYieldpoint request pending on this thread? */ boolean yieldpointRequestPending; /** * Are we at a yieldpoint right now? */ boolean atYieldpoint; /** * Is there a flush request for this thread? This is handled via a soft * handshake. */ public boolean flushRequested; /** * Is a soft handshake requested? Logically, this field is protected by the * thread's monitor - but it is typically only mucked with when both the * thread's monitor and the softHandshakeDataLock are held. */ public boolean softHandshakeRequested; /** * How many threads have not yet reached the soft handshake? (protected by * softHandshakeDataLock) */ public static int softHandshakeLeft; /** * Lock that protects soft handshake fields. */ public static Monitor softHandshakeDataLock; /** * Lock that prevents multiple (soft or hard) handshakes from proceeding * concurrently. */ public static Monitor handshakeLock; /** * Place to save register state when this thread is not actually running. */ @Entrypoint(fieldMayBeFinal = true) @Untraced public final AbstractRegisters contextRegisters; @SuppressWarnings("unused") @Entrypoint(fieldMayBeFinal = true) private final AbstractRegisters contextRegistersShadow; /** * Place to save register state when this thread is not actually running. */ @Entrypoint(fieldMayBeFinal = true) @Untraced public final AbstractRegisters contextRegistersSave; @SuppressWarnings("unused") @Entrypoint(fieldMayBeFinal = true) private final AbstractRegisters contextRegistersSaveShadow; /** * Place to save register state during hardware(C signal trap handler) or * software (RuntimeEntrypoints.athrow) trap handling. */ @Entrypoint(fieldMayBeFinal = true) @Untraced private final AbstractRegisters exceptionRegisters; @SuppressWarnings("unused") @Entrypoint(fieldMayBeFinal = true) private final AbstractRegisters exceptionRegistersShadow; /** Count of recursive uncaught exceptions, we need to bail out at some point */ private int uncaughtExceptionCount = 0; /** * A cached free lock. Not a free list; this will only ever contain 0 or 1 * locks! */ public Lock cachedFreeLock; /* * Wait/notify fields */ /** * Place to save/restore this thread's monitor state during * {@link Object#wait} and {@link Object#notify}. */ protected Object waitObject; /** Lock recursion count for this thread's monitor. */ protected int waitCount; /** * Should the thread suspend? */ boolean shouldSuspend; /** * An integer token identifying the last suspend request */ int shouldSuspendToken; /** * Is the thread suspended? */ boolean isSuspended; /** * Should the thread block for handshake? */ boolean shouldBlockForHandshake; /** * Is the thread blocked for handshake? */ boolean isBlockedForHandshake; /** * Should the thread block for a thread-to-thread communication? */ boolean shouldBlockForGC; /** * Is the thread blocked for thread-to-thread communication? */ boolean isBlockedForGC; /** * An integer token identifying the last stack trace request */ int shouldBlockForStackTraceToken; /** * Should the thread block so that another thread can get a stack trace for it? */ boolean shouldBlockForStackTrace; /** * Is the thread blocked because another thread wants to get a stack trace for it? */ boolean isBlockedForStackTrace; /** * A block adapter specifies the reason for blocking or unblocking a thread. A thread * remains blocked so long as any of the block adapters say that it should be blocked. * Block adapters are statically allocated, and store their state in instance fields of * RVMThread. */ @Uninterruptible @NonMoving public abstract static class BlockAdapter { /** * @param t a thread * @return whether the given thread should be blocked for this block * adapter. If {@code true}, the thread is guaranteed to block. */ abstract boolean isBlocked(RVMThread t); /** * Specifies that the thread is either blocked {@code (value == true)} or not * blocked {@code (value == false)} for this block adapter. This call * indicates a statement of fact by the thread itself - it's used either * to acknowledge a block request (see {@link #hasBlockRequest(RVMThread)} * below) or to respond to a request to unblock. * @param t the thread * @param value the new value of the status for blocking as described above */ abstract void setBlocked(RVMThread t, boolean value); /** * Requests that the thread block, for this block adapter, at its earliest * convenience. * <p> * Called from RVMThread.block() and associated methods. Some block adapters * allow for multiple requests to block; in that case this will return a * "token" that can be passed to hasBlockRequest() to check, not only whether * there is a block request, but whether that block request is still * associated with a particular call to requestBlock(). This is used to * prevent a suspend() call from stalling due to a concurrent resume() and * second suspend(). Note that most block adapers don't care about this * scenario, and will just return 0 (or some other meaningless number) here. * * @param t the thread that needs to block * @return a token as described above */ abstract int requestBlock(RVMThread t); /** * @param t the thread * @return whether the thread has a request to block for this * block adapter */ abstract boolean hasBlockRequest(RVMThread t); /** * @param t the thread to check for block requests * @param token a token, see {@link #requestBlock(RVMThread)} * @return whether the thread has a block request * associated with the given requestBlock() call */ abstract boolean hasBlockRequest(RVMThread t, int token); /** * Clears any blocking requests for the thread. * @param t thread whose block requests will be cleared */ abstract void clearBlockRequest(RVMThread t); } @Uninterruptible @NonMoving public static class SuspendBlockAdapter extends BlockAdapter { @Override boolean isBlocked(RVMThread t) { return t.isSuspended; } @Override void setBlocked(RVMThread t, boolean value) { t.isSuspended = value; } @Override int requestBlock(RVMThread t) { if (t.isSuspended || t.shouldSuspend) { return t.shouldSuspendToken; } else { t.shouldSuspend = true; t.shouldSuspendToken++; return t.shouldSuspendToken; } } @Override boolean hasBlockRequest(RVMThread t) { return t.shouldSuspend; } @Override boolean hasBlockRequest(RVMThread t, int token) { return t.shouldSuspend && t.shouldSuspendToken == token; } @Override void clearBlockRequest(RVMThread t) { t.shouldSuspend = false; } } public static final SuspendBlockAdapter suspendBlockAdapter = new SuspendBlockAdapter(); @Uninterruptible @NonMoving public static class ThreadStackTraceBlockAdapter extends BlockAdapter { @Override boolean isBlocked(RVMThread t) { return t.isBlockedForStackTrace; } @Override void setBlocked(RVMThread t, boolean value) { t.isBlockedForStackTrace = value; } @Override int requestBlock(RVMThread t) { if (t.isBlockedForStackTrace || t.shouldBlockForStackTrace) { return t.shouldBlockForStackTraceToken; } else { t.shouldBlockForStackTrace = true; t.shouldBlockForStackTraceToken++; return t.shouldBlockForStackTraceToken; } } @Override boolean hasBlockRequest(RVMThread t) { return t.shouldBlockForStackTrace; } @Override boolean hasBlockRequest(RVMThread t, int token) { return t.shouldBlockForStackTrace && t.shouldBlockForStackTraceToken == token; } @Override void clearBlockRequest(RVMThread t) { t.shouldBlockForStackTrace = false; } } public static final ThreadStackTraceBlockAdapter stackTraceBlockAdapter = new ThreadStackTraceBlockAdapter(); @Uninterruptible @NonMoving public static class HandshakeBlockAdapter extends BlockAdapter { @Override boolean isBlocked(RVMThread t) { return t.isBlockedForHandshake; } @Override void setBlocked(RVMThread t, boolean value) { t.isBlockedForHandshake = value; } @Override int requestBlock(RVMThread t) { if (!t.isBlockedForHandshake) { t.shouldBlockForHandshake = true; } return 0; } @Override boolean hasBlockRequest(RVMThread t) { return t.shouldBlockForHandshake; } @Override boolean hasBlockRequest(RVMThread t, int token) { return t.shouldBlockForHandshake; } @Override void clearBlockRequest(RVMThread t) { t.shouldBlockForHandshake = false; } } public static final HandshakeBlockAdapter handshakeBlockAdapter = new HandshakeBlockAdapter(); @Uninterruptible @NonMoving public static class GCBlockAdapter extends BlockAdapter { @Override boolean isBlocked(RVMThread t) { return t.isBlockedForGC; } @Override void setBlocked(RVMThread t, boolean value) { t.isBlockedForGC = value; } @Override int requestBlock(RVMThread t) { if (!t.isBlockedForGC) { t.shouldBlockForGC = true; } return 0; } @Override boolean hasBlockRequest(RVMThread t) { return t.shouldBlockForGC; } @Override boolean hasBlockRequest(RVMThread t, int token) { return t.shouldBlockForGC; } @Override void clearBlockRequest(RVMThread t) { t.shouldBlockForGC = false; } } public static final GCBlockAdapter gcBlockAdapter = new GCBlockAdapter(); static final BlockAdapter[] blockAdapters = new BlockAdapter[] { suspendBlockAdapter, handshakeBlockAdapter, gcBlockAdapter, stackTraceBlockAdapter }; /** * An enumeration that describes the different manners in which a thread might * be voluntarily waiting. */ protected enum Waiting { /** The thread is not waiting at all. In fact it's running. */ RUNNABLE, /** The thread is waiting without a timeout. */ WAITING, /** The thread is waiting with a timeout. */ TIMED_WAITING } /** * Accounting of whether or not a thread is waiting (in the Java thread state * sense), and if so, how it's waiting. * <p> * Invariant: the RVM runtime does not ever use this field for any purpose * other than updating it so that the java.lang.Thread knows the state. Thus, * if you get sloppy with this field, the worst case outcome is that some Java * program that queries the thread state will get something other than what it * may or may not have expected. */ protected Waiting waiting; /** * Exception to throw in this thread at the earliest possible point. */ Throwable asyncThrowable; /** * Has the thread been interrupted? */ boolean hasInterrupt; /** * Should the next executed yieldpoint be taken? Can be true for a variety of * reasons. See RVMThread.yieldpoint * <p> * To support efficient sampling of only prologue/epilogues we also encode * some extra information into this field. 0 means that the yieldpoint should * not be taken. >0 means that the next yieldpoint of any type should be taken * <0 means that the next prologue/epilogue yieldpoint should be taken * <p> * Note the following rules: * <ol> * <li>If takeYieldpoint is set to 0 or -1 it is perfectly safe to set it to * 1; this will have almost no effect on the system. Thus, setting * takeYieldpoint to 1 can always be done without acquiring locks.</li> * <li>Setting takeYieldpoint to any value other than 1 should be done while * holding the thread's monitor().</li> * <li>The exception to rule (2) above is that the yieldpoint itself sets * takeYieldpoint to 0 without holding a lock - but this is done after it * ensures that the yieldpoint is deferred by setting yieldpointRequestPending * to true. * </ol> */ @Entrypoint public int takeYieldpoint; /** * How many times has the "timeslice" expired? This is only used for profiling * and OSR (in particular base-to-opt OSR). */ public int timeSliceExpired; /** Is a running thread permitted to ignore the next park request */ private boolean parkingPermit; /* * JNI fields */ /** * Cached JNI environment for this thread */ @Entrypoint @Untraced private JNIEnvironment jniEnv; @SuppressWarnings("unused") private JNIEnvironment jniEnvShadow; /** Used by GC to determine collection success */ private boolean physicalAllocationFailed; /** Used by GC to determine collection success */ private int collectionAttempt; /** The OOME to throw */ private static OutOfMemoryError outOfMemoryError; /* * Enumerate different types of yield points for sampling */ public static final int PROLOGUE = 0; public static final int BACKEDGE = 1; public static final int EPILOGUE = 2; public static final int NATIVE_PROLOGUE = 3; public static final int NATIVE_EPILOGUE = 4; public static final int OSROPT = 5; /* * Fields used for on stack replacement */ /** * Only used by OSR when VM.BuildForAdaptiveSystem. Declared as an Object to * cut link to adaptive system. Ugh. */ public final Object /* OnStackReplacementEvent */onStackReplacementEvent; /** * The flag indicates whether this thread is waiting for on stack replacement * before being rescheduled. */ // flags should be packaged or replaced by other solutions public boolean isWaitingForOsr = false; /** * Before call new instructions, we need a bridge to recover register states * from the stack frame. */ public CodeArray bridgeInstructions = null; /** Foo frame pointer offset */ public Offset fooFPOffset = Offset.zero(); /** Thread switch frame pointer offset */ public Offset tsFPOffset = Offset.zero(); /** * Flag to synchronize with osr organizer, the trigger sets osr requests the * organizer clear the requests */ public boolean requesting_osr = false; /** * Flag to indicate that the last OSR request is done. */ public boolean osr_done = false; /** * The number of processors to use. */ public static int availableProcessors = -1; /** * Thread handle. Currently stores pthread_t, which we assume to be no larger * than a pointer-sized word. */ public Word pthread_id; /** * Thread priority handle. Used when manipulating the threads priority. * This may be different from pthread_id. */ public Word priority_handle; /** * Scratch area for use for gpr <=> fpr transfers by PPC baseline compiler. * Used to transfer x87 to SSE registers on IA32 */ @SuppressWarnings({ "unused" }) @Entrypoint private double scratchStorage; /** * Current index of this thread in the threads array. This may be changed by * another thread, but only while the acctLock is held. */ private int threadIdx; /** * Is the system in the process of shutting down (has System.exit been called) */ private static boolean systemShuttingDown = false; /** * Flag set by external signal to request debugger activation at next thread * switch. See also: sysSignal.c */ @Entrypoint public static volatile boolean debugRequested; public volatile boolean asyncDebugRequestedForThisThread; /** * The latch for reporting profile data. */ public static Latch doProfileReport; /** Number of times dump stack has been called recursively */ protected int inDumpStack = 0; /** Is this a "registered mutator?" */ public boolean activeMutatorContext = false; /** Lock used for dumping stack and such. */ public static Monitor dumpLock; /** In dump stack and dying */ protected static boolean exitInProgress = false; /** Extra debug from traces */ protected static final boolean traceDetails = false; /** Toggle display of frame pointer address in stack dump */ private static final boolean SHOW_FP_IN_STACK_DUMP = true; /** Index of thread in which "VM.boot()" runs */ public static final int PRIMORDIAL_THREAD_INDEX = 1; /** Maximum number of RVMThread's that we can support. */ public static final int LOG_MAX_THREADS = 10; public static final int MAX_THREADS = 1 << LOG_MAX_THREADS; /** * thread array - all threads are stored in this array according to their * threadSlot. */ public static RVMThread[] threadBySlot = new RVMThread[MAX_THREADS]; /** * Per-thread monitors. Note that this array is statically initialized. It * starts out all null. When a new thread slot is allocated, a monitor is * added for that slot. * <p> * Question: what is the outcome, if any, of taking a yieldpoint while holding * this lock? * <ol> * <li>If there is a GC request we will wait on this condition variable and * thus release it. Someone else might then acquire the lock before realizing * that there is a GC request and then do bad things.</li> * <li>The yieldpoint might acquire another thread's monitor. Thus, two * threads may get into lock inversion with each other.</li> * <li>???</li> * </ol> */ private static final NoYieldpointsMonitor[] monitorBySlot = new NoYieldpointsMonitor[MAX_THREADS]; private static final Monitor[] communicationLockBySlot = new Monitor[MAX_THREADS]; /** * Lock (mutex) used for creating and destroying threads as well as thread * accounting. This mutex should not be held while thread monitors (see monitorBySlot) * are held. Use this mutex only to protect accesses to: * <ul> * <li>the global thread lists, such as threadBySlot, aboutToTerminate, threads, and * freeLots</li> * <li>threadIdx field of RVMThread</li> * <li>numThreads, numActiveThreads, numActiveSystemThreads, numActiveDaemons static fields of RVMThread</li> * </ul> */ public static NoYieldpointsMonitor acctLock; /** * Lock (mutex) used for servicing debug requests. */ public static NoYieldpointsMonitor debugLock; /** * Lock used for generating debug output. */ private static NoYieldpointsMonitor outputLock; /** * Thread slots of threads that are about to terminate. This must be * an int array because it's accessed from code that cannot have * barriers. */ private static final int[] aboutToTerminate = new int[MAX_THREADS]; /** * Number of threads that are about to terminate. */ private static int aboutToTerminateN; /** * Free thread slots */ private static final int[] freeSlots = new int[MAX_THREADS]; /** * Number of free thread slots. */ private static int freeSlotN; /** * When there are no thread slots on the free list, this is the next one to * use. */ public static int nextSlot = 2; /** * Number of threads in the system (some of which may not be active). */ public static int numThreads; /** * Packed and unordered array or active threads. Only entries in the range 0 * to numThreads-1 (inclusive) are defined. Note that it should be possible to * scan this array without locking and get all of the threads - but only if * you scan downward and place a memory fence between loads. * <p> * Note further that threads remain in this array even after the Java * libraries no longer consider the thread to be active. */ public static final RVMThread[] threads = new RVMThread[MAX_THREADS]; /** * Preallocated array for use in handshakes. Protected by handshakeLock. */ public static final RVMThread[] handshakeThreads = new RVMThread[MAX_THREADS]; /** * Preallocated array for use in debug requested. Protected by debugLock. */ public static final RVMThread[] debugThreads = new RVMThread[MAX_THREADS]; /** * Number of active threads in the system. */ private static int numActiveThreads; /** * Number of active system threads. Necessary for JMX because * it's better to not count system threads to be consistent * with other VMs. */ private static int numActiveSystemThreads; /** * Number of active daemon threads. */ private static int numActiveDaemons; /* * TuningFork instrumentation support */ /** * The Feedlet instance for this thread to use to make addEvent calls. */ public Feedlet feedlet; /** * @param slot the thread's slot * @return a NoYieldpointsCondLock for a given thread slot. */ static NoYieldpointsMonitor monitorForSlot(int slot) { NoYieldpointsMonitor result = monitorBySlot[slot]; if (VM.VerifyAssertions) VM._assert(result != null); return result; } /** * @return the NoYieldpointsCondLock for this thread. */ public NoYieldpointsMonitor monitor() { return monitorForSlot(threadSlot); } public Monitor communicationLockForSlot(int slot) { Monitor result = communicationLockBySlot[slot]; if (VM.VerifyAssertions) VM._assert(result != null); return result; } public Monitor communicationLock() { return communicationLockForSlot(threadSlot); } /** * Initialize the threading subsystem for the boot image. */ @Interruptible public static void init() { // Enable us to dump a Java Stack from the C trap handler to aid in // debugging things that // show up as recursive use of hardware exception registers (eg the // long-standing lisp bug) BootRecord.the_boot_record.dumpStackAndDieOffset = Entrypoints.dumpStackAndDieMethod.getOffset(); Lock.init(); } public void assertAcceptableStates(int expected) { if (VM.VerifyAssertions) { int curStatus = getExecStatus(); if (curStatus != expected) { VM.sysWriteln("FATAL ERROR: unexpected thread state."); VM.sysWriteln("Expected: ",READABLE_EXEC_STATUS[expected]); VM.sysWriteln("Observed: ",READABLE_EXEC_STATUS[curStatus]); VM._assert(curStatus == expected); } } } public void assertAcceptableStates(int expected1,int expected2) { if (VM.VerifyAssertions) { int curStatus = getExecStatus(); if (curStatus != expected1 && curStatus != expected2) { VM.sysWriteln("FATAL ERROR: unexpected thread state."); VM.sysWriteln("Expected: ",READABLE_EXEC_STATUS[expected1]); VM.sysWriteln(" or: ",READABLE_EXEC_STATUS[expected2]); VM.sysWriteln("Observed: ",READABLE_EXEC_STATUS[curStatus]); VM._assert(curStatus == expected1 || curStatus == expected2); } } } public void assertUnacceptableStates(int unexpected) { if (VM.VerifyAssertions) { int curStatus = getExecStatus(); if (curStatus == unexpected) { VM.sysWriteln("FATAL ERROR: unexpected thread state."); VM.sysWriteln("Unexpected: ",READABLE_EXEC_STATUS[unexpected]); VM.sysWriteln(" Observed: ",READABLE_EXEC_STATUS[curStatus]); VM._assert(curStatus != unexpected); } } } public void assertUnacceptableStates(int unexpected1,int unexpected2) { if (VM.VerifyAssertions) { int curStatus = getExecStatus(); if (curStatus == unexpected1 || curStatus == unexpected2) { VM.sysWriteln("FATAL ERROR: unexpected thread state for thread", threadSlot); VM.sysWriteln("Unexpected: ",READABLE_EXEC_STATUS[unexpected1]); VM.sysWriteln(" and: ",READABLE_EXEC_STATUS[unexpected2]); VM.sysWriteln(" Observed: ",READABLE_EXEC_STATUS[curStatus]); VM._assert(curStatus != unexpected1 && curStatus != unexpected2); } } } static void bind(int cpuId) { if (VM.VerifyAssertions) VM._assert(sysCall.sysThreadBindSupported() == 1); sysCall.sysThreadBind(cpuId); } static void bindIfRequested() { if (VM.forceOneCPU >= 0) { if (traceBind) { VM.sysWriteln("binding thread to CPU: ",VM.forceOneCPU); } bind(VM.forceOneCPU); } } /** * Boot the threading subsystem. */ @Interruptible // except not really, since we don't enable yieldpoints yet public static void boot() { outOfMemoryError = new OutOfMemoryError(); dumpLock = new Monitor(); acctLock = new NoYieldpointsMonitor(); debugLock = new NoYieldpointsMonitor(); outputLock = new NoYieldpointsMonitor(); softHandshakeDataLock = new Monitor(); handshakeLock = new Monitor(); doProfileReport = new Latch(false); monitorBySlot[getCurrentThread().threadSlot] = new NoYieldpointsMonitor(); communicationLockBySlot[getCurrentThread().threadSlot] = new Monitor(); sysCall.sysStashVMThread(getCurrentThread()); if (traceAcct) { VM.sysWriteln("boot thread at ",Magic.objectAsAddress(getCurrentThread())); } bindIfRequested(); threadingInitialized = true; // Always run timer thread, so we can respond to debug requests new TimerThread().start(); if (VM.BuildForAdaptiveSystem) { ObjectHolder.boot(); } FinalizerThread.boot(); getCurrentThread().enableYieldpoints(); if (traceAcct) VM.sysWriteln("RVMThread booted"); } /** * Add this thread to the termination watchlist. Called by terminating threads * before they finish terminating. */ private void addAboutToTerminate() { monitor().lockNoHandshake(); isAboutToTerminate = true; activeMutatorContext = false; monitor().broadcast(); handleHandshakeRequest(); deinitMutator(); // WARNING! DANGER! Since we've set isAboutToTerminate to true, when we // release this lock the GC will: // 1) No longer scan the thread's stack (though it will *see* the // thread's stack and mark the stack itself as live, without scanning // it). // 2) No longer include the thread in any mutator phases ... hence the // need to ensure that the mutator context is flushed above. // 3) No longer attempt to block the thread. // Moreover, we can no longer do anything that leads to write barriers // or allocation. monitor().unlock(); softRendezvous(); acctLock.lockNoHandshake(); aboutToTerminate[aboutToTerminateN++] = threadSlot; acctLock.unlock(); } /** * Method called after processing a list of threads, or before starting a new * thread. This does two things. First, it guarantees that the thread slots * used by any dead threads are freed. Second, it guarantees that each thread * deregisters itself from GC. Thus, it is essential that after requesting * things like mutator flushes, you call this, to ensure that any threads that * had died before or during the mutator flush request do the Right Thing. */ @NoCheckStore public static void processAboutToTerminate() { if (!neverKillThreads) { restart: while (true) { int notKilled = 0; acctLock.lockNoHandshake(); for (int i = 0; i < aboutToTerminateN; ++i) { RVMThread t = threadBySlot[aboutToTerminate[i]]; if (t.getExecStatus() == TERMINATED) { aboutToTerminate[i--] = aboutToTerminate[--aboutToTerminateN]; acctLock.unlock(); t.releaseThreadSlot(); continue restart; } else { notKilled++; } } acctLock.unlock(); if (notKilled > 0 && traceAboutToTerminate) { VM.sysWriteln("didn't kill ", notKilled, " threads"); } break; } } } /** * Find a thread slot not in use by any other live thread and bind the given * thread to it. The thread's threadSlot field is set accordingly. */ @Interruptible void assignThreadSlot() { if (!VM.runningVM) { // primordial thread threadSlot = 1; threadBySlot[1] = this; threads[0] = this; threadIdx = 0; numThreads = 1; } else { processAboutToTerminate(); acctLock.lockNoHandshake(); if (freeSlotN > 0) { threadSlot = freeSlots[--freeSlotN]; } else { if (nextSlot == threads.length) { VM.sysFail("too many threads"); } threadSlot = nextSlot++; } acctLock.unlock(); // before we actually use this slot, ensure that there is a monitor // for it. note that if the slot doesn't have a monitor, then we // "own" it since we allocated it above but haven't done anything // with it (it's not assigned to a thread, so nobody else can touch // it) if (monitorBySlot[threadSlot] == null) { monitorBySlot[threadSlot] = new NoYieldpointsMonitor(); } if (communicationLockBySlot[threadSlot] == null) { Monitor m = new Monitor(); handshakeLock.lockWithHandshake(); communicationLockBySlot[threadSlot] = m; handshakeLock.unlock(); } Magic.sync(); /* * make sure that nobody sees the thread in any of the * tables until the thread slot is inited */ acctLock.lockNoHandshake(); threadBySlot[threadSlot] = this; threadIdx = numThreads++; threads[threadIdx] = this; acctLock.unlock(); } lockingId = threadSlot << TL_THREAD_ID_SHIFT; if (traceAcct) { VM.sysWriteln("Thread #", threadSlot, " at ", Magic.objectAsAddress(this)); VM.sysWriteln("stack at ", Magic.objectAsAddress(stack), " up to ", Magic.objectAsAddress(stack).plus(stack.length)); } } /** * Release a thread's slot in the threads array. */ @NoCheckStore void releaseThreadSlot() { acctLock.lockNoHandshake(); RVMThread replacementThread = threads[numThreads - 1]; threads[threadIdx] = replacementThread; replacementThread.threadIdx = threadIdx; threadIdx = -1; Magic.sync(); /* * make sure that if someone is processing the threads array * without holding the acctLock (which is definitely legal) * then they see the replacementThread moved to the new index * before they see the numThreads decremented (otherwise they * would miss replacementThread; but with the current * arrangement at worst they will see it twice) */ threads[--numThreads] = null; threadBySlot[threadSlot] = null; freeSlots[freeSlotN++] = threadSlot; acctLock.unlock(); } /** * Create a new RVM Thread * * @param stack The stack on which to execute the thread. * @param thread The corresponding java.lang.Thread. * @param name The name of the thread * @param daemon True if this is a daemon thread. * @param systemThread True if this is a system thread. * @param priority The threads execution priority. */ public RVMThread(byte[] stack, Thread thread, String name, boolean daemon, SystemThread systemThread, int priority) { this.stack = stack; this.daemon = daemon; this.priority = priority; this.systemThread = systemThread; this.contextRegisters = this.contextRegistersShadow = ArchitectureFactory.createRegisters(); this.contextRegistersSave = this.contextRegistersSaveShadow = ArchitectureFactory.createRegisters(); this.exceptionRegisters = this.exceptionRegistersShadow = ArchitectureFactory.createRegisters(); if (VM.runningVM) { feedlet = TraceEngine.engine.makeFeedlet(name, name); } if (VM.VerifyAssertions) VM._assert(stack != null); // put self in list of threads known to scheduler and garbage collector if (!VM.runningVM) { if (VM.VerifyAssertions) VM._assert(name != null); this.name = name; // create primordial thread (in boot image) assignThreadSlot(); if (trace) trace("RVMThread create: ", name); if (trace) trace("daemon: ", daemon ? "true" : "false"); if (trace) trace("RVMThread", "create"); initMutator(threadSlot); this.activeMutatorContext = true; // Remember the boot thread this.execStatus = IN_JAVA; this.waiting = Waiting.RUNNABLE; // assign final field onStackReplacementEvent = null; } else { // create a normal (ie. non-primordial) thread // set up wrapper Thread if one exists this.thread = thread; // Set thread type this.execStatus = NEW; this.waiting = Waiting.RUNNABLE; stackLimit = Magic.objectAsAddress(stack).plus(StackFrameLayout.getStackSizeGuard()); // get instructions for method to be executed as thread startoff CodeArray instructions = Entrypoints.threadStartoffMethod.getCurrentEntryCodeArray(); VM.disableGC(); // initialize thread registers Address ip = Magic.objectAsAddress(instructions); Address sp = Magic.objectAsAddress(stack).plus(stack.length); // Initialize the a thread stack as if "startoff" method had been called // by an empty baseline-compiled "sentinel" frame with one local variable. contextRegisters.initializeStack(ip, sp); VM.enableGC(); assignThreadSlot(); this.name = name == null ? "Thread-" + threadSlot : name; initMutator(threadSlot); activeMutatorContext = true; if (traceAcct) { VM.sysWriteln("registered mutator for ", threadSlot); } initializeJNIEnv(); if (VM.BuildForAdaptiveSystem) { onStackReplacementEvent = new OnStackReplacementEvent(); } else { onStackReplacementEvent = null; } if (thread == null) { // create wrapper Thread if doesn't exist this.thread = java.lang.JikesRVMSupport.createThread(this, name); } } } /** * Creates a thread with default stack and with the given name. The * thread will be a daemon thread that runs at normal priority and is not * associated with a {@link Thread} object. * * @param systemThread the associated system thread * @param name human-readable name */ public RVMThread(SystemThread systemThread, String name) { this(MemoryManager.newStack(StackFrameLayout.getStackSizeNormal()), null, // java.lang.Thread name, true, // daemon systemThread, Thread.NORM_PRIORITY); } /** * Create a thread with the given stack and name. Used by * {@link org.jikesrvm.mm.mminterface.CollectorThread} and the * boot image writer for the boot thread. * * @param systemThread the associated system thread * @param stack the thread's stack * @param name human-readable name of the thread */ public RVMThread(SystemThread systemThread, byte[] stack, String name) { this(stack, null, // java.lang.Thread name, true, // daemon systemThread, Thread.NORM_PRIORITY); } /** * Create a thread with ... called by java.lang.VMThread.create. System thread * isn't set. * * @param thread the associated Java thread * @param stacksize desired stack size in bytes, must be positive * @param name human-readable name * @param daemon whether the thread is a daemon * @param priority the priority for the thread */ public RVMThread(Thread thread, int stacksize, String name, boolean daemon, int priority) { this(MemoryManager.newStack(stacksize), thread, name, daemon, null, priority); } /** * Check if the thread has block requests (for example, for suspension and GC). If * it does, clear the requests and marked the thread as blocked for that request. * If there were any block requests, do a broadcast() on the thread's monitor(). * This is an internal method and should only be called from code that implements * thread blocking. The monitor() lock must be held for this method to work properly. */ private void acknowledgeBlockRequests() { boolean hadSome = false; if (VM.VerifyAssertions) VM._assert(blockAdapters != null); for (int i = 0; i < blockAdapters.length; ++i) { if (blockAdapters[i].hasBlockRequest(this)) { blockAdapters[i].setBlocked(this, true); blockAdapters[i].clearBlockRequest(this); hadSome = true; } } if (hadSome) { monitor().broadcast(); } } /** * Checks if the thread system has acknowledged that the thread is supposed * to be blocked. This will return true if the thread is actually blocking, or * if the thread is running native code but is guaranteed to block before * returning to Java. Only call this method when already holding the monitor(), * for two reasons: * <ol> * <li>This method does not acquire the monitor() lock even though it needs * to have it acquired given the data structures that it is accessing. * <li>You will typically want to call this method to decide if you need to * take action under the assumption that the thread is blocked (or not * blocked). So long as you hold the lock the thread cannot change state from * blocked to not blocked. * </ol> * * @return if the thread is supposed to be blocked */ public boolean isBlocked() { for (int i = 0; i < blockAdapters.length; ++i) { if (blockAdapters[i].isBlocked(this)) { return true; } } return false; } /** * Checks if the thread is executing Java code. A thread is executing Java * code if its <code>execStatus</code> is <code>IN_JAVA</code> or * <code>IN_JAVA_TO_BLOCK</code>, and if it is not * <code>aboutToTerminate</code>, and if it is not blocked. Only call this * method when already holding the monitor(), and probably only after calling * setBlockedExecStatus(), for two reasons: * <ol> * <li>This method does not acquire the monitor() lock even though it needs * to have it acquired given the data structures that it is accessing. * <li>You will typically want to call this method to decide if you need to * take action under the assumption that the thread is running Java (or not * running Java). So long as you hold the lock - and you have called * setBlockedExecStatus() - the thread cannot change state from running-Java * to not-running-Java. * </ol> * * @return if the thread is running Java */ public boolean isInJava() { return !isBlocking && !isAboutToTerminate && (getExecStatus() == IN_JAVA || getExecStatus() == IN_JAVA_TO_BLOCK); } /** * Checks whether the thread is in native code as understood by the JMX ThreadInfo. * A thread is considered in native if it is executing JNI code. * <p> * Note: this method is NOT designed for internal use by the RVMThread class and * must not be used for scheduling. For comparison see a method used for * internal scheduling decisions such as {@link #isInJava()}. * * @return if the thread is running JNI code */ boolean isInNativeAccordingToJMX() { return !isAboutToTerminate && (getExecStatus() == IN_JNI || getExecStatus() == BLOCKED_IN_JNI); } /** * Should the thread by eligible for sampling by the timer thread? * <p> * Heuristically, we use timer-based sampling the in the adaptive system * to determine where the program is spending time (and thus what to optimize). * This doesn't have to be a 100% accurate, but it must be non-blocking * and also closely approximate whether or not the thread is executing. * For now, approximate just as being in JAVA. * <p> * As a future item, we may want to actually correctly attribute time * spent in native code to the top native method on the frame when the timer * goes off. This will require work in the JNI enter/exit sequence to deal with * timer samples appropriately. * * @return whether this thread should be sampled by the timer thread. */ public boolean shouldBeSampled() { return execStatus == IN_JAVA; } /** A variant of checkBlock() that does not save the thread state. */ @NoInline @Unpreemptible("May block if the thread was asked to do so, but otherwise does no actions that would cause blocking") private void checkBlockNoSaveContext() { assertUnacceptableStates(NEW, TERMINATED); if (VM.VerifyAssertions) VM._assert(!isAboutToTerminate); if (VM.VerifyAssertions) VM._assert(!isBlocking); if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " in checkBlockNoSaveContext"); // NB: anything this method calls CANNOT change the contextRegisters // or the JNI env. as well, this code will be running concurrently // with stop-the-world GC! monitor().lockNoHandshake(); isBlocking = true; if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " acquired lock and has notified everyone that we're blocked"); // deal with requests that would require a soft handshake rendezvous handleHandshakeRequest(); // check if a soft handshake has been requested, and if so, clear the // request boolean commitSoftRendezvous = softRendezvousCheckAndClear(); if (commitSoftRendezvous) { // if a soft handshake had been requested, we need to acknowledge it. // but to acknowledge it we cannot be holding the monitor() lock. // it turns out that at this point in the code it is perfectly safe // to release it, because: // 1) callers of this method expect that it may, in all likelihood, // release the monitor() lock if they were holding it, since it // calls wait() // 2) if the block requests get cleared when we release the lock, // we won't call wait, since we reacquire the lock prior to checking // for block requests. int recCount = monitor().unlockCompletely(); softRendezvousCommit(); monitor().relockNoHandshake(recCount); } if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " has acknowledged soft handshakes"); boolean hadReallyBlocked = false; for (;;) { // deal with block requests acknowledgeBlockRequests(); // are we blocked? if (!isBlocked()) { break; } if (traceReallyBlock) { hadReallyBlocked = true; VM.sysWriteln("Thread #", threadSlot, " is really blocked with status ", READABLE_EXEC_STATUS[getExecStatus()]); VM.sysWriteln("Thread #", threadSlot, " has fp = ", Magic.getFramePointer()); if (dumpStackOnBlock) { dumpStack(); } } // what if a GC request comes while we're here for a suspend() // request? // answer: we get awoken, reloop, and acknowledge the GC block // request. monitor().waitNoHandshake(); if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " has awoken; checking if we're still blocked"); } if (traceBlock || (traceReallyBlock && hadReallyBlocked)) VM.sysWriteln("Thread #", threadSlot, " is unblocking"); // we're about to unblock, so indicate to the world that we're running // again. setExecStatus(IN_JAVA); // let everyone know that we're back to executing code isBlocking = false; // deal with requests that came up while we were blocked. handleHandshakeRequest(); monitor().unlock(); if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " is unblocked"); } /** * Check if the thread is supposed to block, and if so, block it. This method * will ensure that soft handshake requests are acknowledged or else * inhibited, that any blocking request is handled, that the execution state * of the thread (<code>execStatus</code>) is set to <code>IN_JAVA</code> * once all blocking requests are cleared, and that other threads are notified * that this thread is in the middle of blocking by setting the appropriate * flag (<code>isBlocking</code>). Note that this thread acquires the * monitor(), though it may release it completely either by calling wait() or * by calling unlockCompletely(). Thus, although it isn't generally a problem * to call this method while holding the monitor() lock, you should only do so * if the loss of atomicity is acceptable. * <p> * Generally, this method should be called from the following four places: * <ol> * <li>The block() method, if the thread is requesting to block itself. * Currently such requests only come when a thread calls suspend(). Doing so * has unclear semantics (other threads may call resume() too early causing * the well-known race) but must be supported because it's still part of the * JDK. Why it's safe: the block() method needs to hold the monitor() for the * time it takes it to make the block request, but does not need to continue * to hold it when it calls checkBlock(). Thus, the fact that checkBlock() * breaks atomicity is not a concern. * <li>The yieldpoint. One of the purposes of a yieldpoint is to periodically * check if the current thread should be blocked. This is accomplished by * calling checkBlock(). Why it's safe: the yieldpoint performs several * distinct actions, all of which individually require the monitor() lock - * but the monitor() lock does not have to be held contiguously. Thus, the * loss of atomicity from calling checkBlock() is fine. * <li>The "WithHandshake" methods of HeavyCondLock. These methods allow you to * block on a mutex or condition variable while notifying the system that you * are not executing Java code. When these blocking methods return, they check * if there had been a request to block, and if so, they call checkBlock(). * Why it's safe: This is subtle. Two cases exist. The first case is when a * WithHandshake method is called on a HeavyCondLock instance that is not a thread * monitor(). In this case, it does not matter that checkBlock() may acquire * and then completely release the monitor(), since the user was not holding * the monitor(). However, this will break if the user is <i>also</i> holding * the monitor() when calling the WithHandshake method on a different lock. This case * should never happen because no other locks should ever be acquired when the * monitor() is held. Additionally: there is the concern that some other locks * should never be held while attempting to acquire the monitor(); the * HeavyCondLock ensures that checkBlock() is only called when that lock * itself is released. The other case is when a WithHandshake method is called on the * monitor() itself. This should only be done when using <i>your own</i> * monitor() - that is the monitor() of the thread your are running on. In * this case, the WithHandshake methods work because: (i) lockWithHandshake() only calls * checkBlock() on the initial lock entry (not on recursive entry), so * atomicity is not broken, and (ii) waitWithHandshake() and friends only call * checkBlock() after wait() returns - at which point it is safe to release * and reacquire the lock, since there cannot be a race with broadcast() once * we have committed to not calling wait() again. * <li>Any code following a potentially-blocking native call. Case (3) above * is somewhat subsumed in this except that it is special due to the fact that * it's blocking on VM locks. So, this case refers specifically to JNI. The * JNI epilogues will call leaveJNIBlocked(), which calls a variant of this * method. * </ol> */ @NoInline @NoOptCompile @BaselineSaveLSRegisters @Unpreemptible("May block if asked to do so, but otherwise does not actions that would block") void checkBlock() { saveThreadState(); checkBlockNoSaveContext(); } /** * Internal method for transitioning a thread from IN_JAVA or IN_JAVA_TO_BLOCK to * either BLOCKED_IN_NATIVE or BLOCKED_IN_JNI, depending on the value of the jni * parameter. It is always safe to conservatively call this method when transitioning * to native code, though it is faster to call either enterNative(), * enterJNIFromCallIntoNative(), or enterJNIFromJNIFunctionCall(). * <p> * This method takes care of all bookkeeping and notifications required when a * a thread that has been requested to block instead decides to run native code. * Threads enter native code never need to block, since they will not be executing * any Java code. However, such threads must ensure that any system services (like * GC) that are waiting for this thread to stop are notified that the thread has * instead chosen to exit Java. As well, any requests to perform a soft handshake * must be serviced and acknowledged. * * @param jni whether this method is called for entering JNI or not */ private void enterNativeBlockedImpl(boolean jni) { if (traceReallyBlock) VM.sysWriteln("Thread #", threadSlot, " entering native blocked."); // NB: anything this method calls CANNOT change the contextRegisters // or the JNI env. as well, this code will be running concurrently // with stop-the-world GC! boolean commitSoftRendezvous; monitor().lockNoHandshake(); if (jni) { jniEnteredBlocked++; setExecStatus(BLOCKED_IN_JNI); } else { nativeEnteredBlocked++; setExecStatus(BLOCKED_IN_NATIVE); } acknowledgeBlockRequests(); handleHandshakeRequest(); commitSoftRendezvous = softRendezvousCheckAndClear(); monitor().unlock(); if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " done with the locking part of native entry."); if (commitSoftRendezvous) softRendezvousCommit(); if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " done enter native blocked."); } @Unpreemptible("May block if the thread was asked to do so, but otherwise does no actions that would cause blocking") private void leaveNativeBlockedImpl() { checkBlockNoSaveContext(); } private void enterNativeBlocked() { assertAcceptableStates(IN_JAVA,IN_JAVA_TO_BLOCK); enterNativeBlockedImpl(false); assertAcceptableStates(IN_NATIVE,BLOCKED_IN_NATIVE); } @Unpreemptible("May block if the thread was asked to do so, but otherwise does no actions that would cause blocking") private void leaveNativeBlocked() { assertAcceptableStates(IN_NATIVE,BLOCKED_IN_NATIVE); leaveNativeBlockedImpl(); assertAcceptableStates(IN_JAVA,IN_JAVA_TO_BLOCK); } private void enterJNIBlocked() { assertAcceptableStates(IN_JAVA,IN_JAVA_TO_BLOCK); enterNativeBlockedImpl(true); assertAcceptableStates(IN_JNI,BLOCKED_IN_JNI); } @Unpreemptible("May block if the thread was asked to do so, but otherwise does no actions that would cause blocking") private void leaveJNIBlocked() { assertAcceptableStates(IN_JNI,BLOCKED_IN_JNI); leaveNativeBlockedImpl(); assertAcceptableStates(IN_JAVA,IN_JAVA_TO_BLOCK); } @Entrypoint public static void enterJNIBlockedFromJNIFunctionCall() { RVMThread t = getCurrentThread(); if (traceReallyBlock) { VM.sysWriteln("Thread #",t.getThreadSlot(), " in enterJNIBlockedFromJNIFunctionCall"); VM.sysWriteln("thread address = ",Magic.objectAsAddress(t)); } t.enterJNIBlocked(); } @Entrypoint public static void enterJNIBlockedFromCallIntoNative() { RVMThread t = getCurrentThread(); if (traceReallyBlock) { VM.sysWriteln("Thread #",t.getThreadSlot(), " in enterJNIBlockedFromCallIntoNative"); VM.sysWriteln("thread address = ",Magic.objectAsAddress(t)); } t.enterJNIBlocked(); } @Entrypoint @Unpreemptible("May block if the thread was asked to do so, but otherwise will not block") static void leaveJNIBlockedFromJNIFunctionCall() { RVMThread t = getCurrentThread(); if (traceReallyBlock) { VM.sysWriteln("Thread #", t.getThreadSlot(), " in leaveJNIBlockedFromJNIFunctionCall"); VM.sysWriteln("thread address = ",Magic.objectAsAddress(t)); VM.sysWriteln("state = ", READABLE_EXEC_STATUS[t.getExecStatus()]); VM.sysWriteln("jtoc = ", Magic.getJTOC()); } t.leaveJNIBlocked(); } /** * Called when JNI code tried to transition from IN_JNI to IN_JAVA but failed */ @Entrypoint @Unpreemptible("May block if the thread was asked to do so, but otherwise will not block") public static void leaveJNIBlockedFromCallIntoNative() { RVMThread t = getCurrentThread(); if (traceReallyBlock) { VM.sysWriteln("Thread #", t.getThreadSlot(), " in leaveJNIBlockedFromCallIntoNative"); VM.sysWriteln("state = ", READABLE_EXEC_STATUS[t.getExecStatus()]); VM.sysWriteln("jtoc = ", Magic.getJTOC()); } t.leaveJNIBlocked(); } private int setBlockedExecStatus() { int oldState, newState; do { oldState = getExecStatus(); if (oldState == IN_JAVA) { newState = IN_JAVA_TO_BLOCK; } else if (oldState == IN_NATIVE) { newState = BLOCKED_IN_NATIVE; } else if (oldState == IN_JNI) { newState = BLOCKED_IN_JNI; } else { newState = oldState; } /* * use the CAS to assert that we observed what we * thought we observed */ } while (!(attemptFastExecStatusTransition(oldState,newState))); return newState; } /** * Attempts to block the thread, and return the state it is in after the * attempt. If we're blocking ourselves, this will always return IN_JAVA. If * the thread signals to us the intention to die as we are trying to block it, * this will return TERMINATED. NOTE: the thread's execStatus will not * actually be TERMINATED at that point yet. * <p> * Note that this method is ridiculously dangerous, especially if you pass * asynchronous==false. Waiting for another thread to stop is not in itself * interruptible - so if you ask another thread to block and they ask you * to block, you might deadlock. * * @param ba the adapter to block on * @param asynchronous {@code true} if the request is asynchronous (i.e. the * receiver is only notified), {@code false} if the caller waits for the * receiver to block * @return the new state of the thread */ @Unpreemptible("Only blocks if the receiver is the current thread, or if asynchronous is set to false and the thread is not already blocked") int block(BlockAdapter ba, boolean asynchronous) { int result; if (traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is requesting that thread #", threadSlot, " blocks."); monitor().lockNoHandshake(); int token = ba.requestBlock(this); if (getCurrentThread() == this) { if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " is blocking."); checkBlock(); result = getExecStatus(); } else { if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " is being told to block."); if (isAboutToTerminate) { if (traceBlock) VM.sysWriteln("Thread #", threadSlot, " is terminating, returning as if blocked in TERMINATED state."); result = TERMINATED; } else { takeYieldpoint = 1; // CAS the execStatus field int newState = setBlockedExecStatus(); result = newState; if (traceReallyBlock) VM.sysWriteln("Thread #", getCurrentThreadSlot(), " is blocking thread #", threadSlot, " which is in state ", newState); // this broadcast serves two purposes: notifies threads that are // IN_JAVA but waiting on monitor() that they should awake and // acknowledge the block request; or notifies anyone // waiting for this thread to block that the thread is // BLOCKED_IN_NATIVE or BLOCKED_IN_JNI. in the latter case the // broadcast() happens _before_ the setting of the flags that the // other threads would be awaiting, but that is fine, since we're // still holding the lock anyway. monitor().broadcast(); if (newState == IN_JAVA_TO_BLOCK) { if (!asynchronous) { if (traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is waiting for thread #", threadSlot, " to block."); while (ba.hasBlockRequest(this, token) && !ba.isBlocked(this) && !isAboutToTerminate) { if (traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is calling wait until thread #", threadSlot, " blocks."); // will this deadlock when the thread dies? if (VM.VerifyAssertions) { // do a timed wait, and assert that the thread did not disappear // into native in the meantime monitor().timedWaitRelativeNoHandshake(1000L * 1000L * 1000L); // 1 sec if (traceReallyBlock) { VM.sysWriteln("Thread #", threadSlot, "'s status is ", READABLE_EXEC_STATUS[getExecStatus()]); } assertUnacceptableStates(IN_NATIVE); } else { monitor().waitNoHandshake(); } if (traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " has returned from the wait call."); } if (isAboutToTerminate) { result = TERMINATED; } else { result = getExecStatus(); } } } else if (newState == BLOCKED_IN_NATIVE || newState == BLOCKED_IN_JNI) { // we own the thread for now - it cannot go back to executing Java // code until we release the lock. before we do so we change its // state accordingly and tell anyone who is waiting. if (traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " has seen thread #", threadSlot, " in native; changing its status accordingly."); ba.clearBlockRequest(this); ba.setBlocked(this, true); } } } monitor().unlock(); if (traceReallyBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is done telling thread #", threadSlot, " to block."); return result; } public boolean blockedFor(BlockAdapter ba) { monitor().lockNoHandshake(); boolean result = ba.isBlocked(this); monitor().unlock(); return result; } @UninterruptibleNoWarn("Never blocks; only asynchronously notifies the receiver to do so") public int asyncBlock(BlockAdapter ba) { if (VM.VerifyAssertions) VM._assert(getCurrentThread() != this); return block(ba, true); } @Unpreemptible("May block if the receiver is the current thread or if the receiver is not yet blocked; otherwise does not perform actions that lead to blocking") public int block(BlockAdapter ba) { return block(ba, false); } @Unpreemptible public void beginPairWith(RVMThread other) { if (traceBlock) VM.sysWriteln("attempting to pair ",threadSlot," with ",other.threadSlot); Monitor.lockWithHandshake( communicationLock(),Word.fromIntSignExtend(threadSlot), other.communicationLock(),Word.fromIntSignExtend(other.threadSlot)); } public void endPairWith(RVMThread other) { communicationLock().unlock(); other.communicationLock().unlock(); if (traceBlock) VM.sysWriteln("unpairing ",threadSlot," from ",other.threadSlot); } @Unpreemptible public void beginPairWithCurrent() { beginPairWith(getCurrentThread()); } public void endPairWithCurrent() { endPairWith(getCurrentThread()); } @Unpreemptible private int safeBlock(BlockAdapter ba, boolean asynchronous) { if (VM.VerifyAssertions) VM._assert(getCurrentThread() != this); beginPairWithCurrent(); int result = block(ba,asynchronous); endPairWithCurrent(); return result; } @Unpreemptible public int safeAsyncBlock(BlockAdapter ba) { return safeBlock(ba, true); } @Unpreemptible public int safeBlock(BlockAdapter ba) { if (getCurrentThread() == this) { return block(ba,false); } else { return safeBlock(ba, false); } } @Unpreemptible public void beginPairHandshake() { beginPairWithCurrent(); block(handshakeBlockAdapter); } @Uninterruptible public void endPairHandshake() { unblock(handshakeBlockAdapter); endPairWithCurrent(); } /** * Save the current thread state. Call this prior to calling enterNative(). You must * be in a method that is marked BaselineSaveLSRegisters. */ @NoInline public static void saveThreadState() { Address curFP = Magic.getFramePointer(); getCurrentThread().contextRegisters.setInnermost(Magic.getReturnAddressUnchecked(curFP), Magic.getCallerFramePointer(curFP)); } /** * Indicate that we'd like the current thread to be executing privileged code that * does not require synchronization with the GC. This call may be made on a thread * that is IN_JAVA or IN_JAVA_TO_BLOCK, and will result in the thread being either * IN_NATIVE or BLOCKED_IN_NATIVE. In the case of an * IN_JAVA_TO_BLOCK->BLOCKED_IN_NATIVE transition, this call will acquire the * thread's lock and send out a notification to any threads waiting for this thread * to reach a safepoint. This notification serves to notify them that the thread * is in GC-safe code, but will not reach an actual safepoint for an indetermined * amount of time. This is significant, because safepoints may perform additional * actions (such as handling handshake requests, which may include things like * mutator flushes and running isync) that IN_NATIVE code will not perform until * returning to IN_JAVA by way of a leaveNative() call. */ @NoInline // so we can get the fp public static void enterNative() { RVMThread t = getCurrentThread(); if (ALWAYS_LOCK_ON_STATE_TRANSITION) { t.enterNativeBlocked(); } else { int oldState, newState; do { oldState = t.getExecStatus(); if (oldState == IN_JAVA) { newState = IN_NATIVE; } else { t.assertAcceptableStates(IN_JAVA_TO_BLOCK); t.enterNativeBlocked(); return; } } while (!(t.attemptFastExecStatusTransition(oldState, newState))); } // NB this is not a correct assertion, as there is a race. we could succeed in // CASing the status to IN_NATIVE, but then someone else could asynchronosly // set it to whatever they want. //if (VM.VerifyAssertions) // VM._assert(t.execStatus == IN_NATIVE); } /** * Attempt to transition from IN_JNI or IN_NATIVE to IN_JAVA, fail if execStatus is * anything but IN_JNI or IN_NATIVE. * * @return true if thread transitioned to IN_JAVA, otherwise false */ public static boolean attemptLeaveNativeNoBlock() { if (ALWAYS_LOCK_ON_STATE_TRANSITION) return false; RVMThread t = getCurrentThread(); int oldState, newState; do { oldState = t.getExecStatus(); if (oldState == IN_NATIVE || oldState == IN_JNI) { newState = IN_JAVA; } else { t.assertAcceptableStates(BLOCKED_IN_NATIVE,BLOCKED_IN_JNI); return false; } } while (!(t.attemptFastExecStatusTransition(oldState, newState))); return true; } /** * Leave privileged code. This is valid for threads that are either IN_NATIVE, * IN_JNI, BLOCKED_IN_NATIVE, or BLOCKED_IN_JNI, and always results in the thread * being IN_JAVA. If the thread was previously BLOCKED_IN_NATIVE or BLOCKED_IN_JNI, * the thread will block until notified that it can run again. */ @Unpreemptible("May block if the thread was asked to do so; otherwise does no actions that would lead to blocking") public static void leaveNative() { if (!attemptLeaveNativeNoBlock()) { if (traceReallyBlock) { VM.sysWriteln("Thread #", getCurrentThreadSlot(), " is leaving native blocked"); } getCurrentThread().leaveNativeBlocked(); } } public static void enterJNIFromCallIntoNative() { // FIXME: call these in PPC instead of doing it in machine code... getCurrentThread().observeExecStatus(); if (!getCurrentThread().attemptFastExecStatusTransition(RVMThread.IN_JAVA, RVMThread.IN_JNI)) { RVMThread.enterJNIBlockedFromCallIntoNative(); } } @Unpreemptible public static void leaveJNIFromCallIntoNative() { // FIXME: call these in PPC instead of doing it in machine code... getCurrentThread().observeExecStatus(); if (!getCurrentThread().attemptFastExecStatusTransition(RVMThread.IN_JNI, RVMThread.IN_JAVA)) { RVMThread.leaveJNIBlockedFromCallIntoNative(); } } public static void enterJNIFromJNIFunctionCall() { // FIXME: call these instead of doing it in machine code... currently this // is never called. getCurrentThread().observeExecStatus(); if (!getCurrentThread().attemptFastExecStatusTransition(RVMThread.IN_JAVA, RVMThread.IN_JNI)) { RVMThread.enterJNIBlockedFromJNIFunctionCall(); } } @Unpreemptible public static void leaveJNIFromJNIFunctionCall() { // FIXME: call these instead of doing it in machine code... currently this // is never called. getCurrentThread().observeExecStatus(); if (!getCurrentThread().attemptFastExecStatusTransition(RVMThread.IN_JNI, RVMThread.IN_JAVA)) { RVMThread.leaveJNIBlockedFromJNIFunctionCall(); } } public void unblock(BlockAdapter ba) { if (traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is requesting that thread #", threadSlot, " unblocks."); monitor().lockNoHandshake(); ba.clearBlockRequest(this); ba.setBlocked(this, false); monitor().broadcast(); monitor().unlock(); if (traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is done requesting that thread #", threadSlot, " unblocks."); } private void handleDebugRequestForThread() { monitor().lockNoHandshake(); dumpLock.lockNoHandshake(); extDump(); if (!isAboutToTerminate) { setBlockedExecStatus(); if (isInJava()) { asyncDebugRequestedForThisThread = true; takeYieldpoint = 1; VM.sysWriteln("(stack trace will follow if thread is not lost...)"); } else { if (contextRegisters != null) { dumpStack(contextRegisters.getInnermostFramePointer()); } else { VM.sysWriteln("(cannot dump stack trace; thread is not running in Java but has no contextRegisters)"); } } } dumpLock.unlock(); monitor().unlock(); } @NoCheckStore public static void checkDebugRequest() { if (debugRequested) { debugLock.lockNoHandshake(); if (debugRequested) { debugRequested = false; VM.sysWriteln("=== Debug requested - attempting safe VM dump ==="); dumpAcct(); reportThreadTransitionCounts(); // FIXME: this code runs concurrently to GC and has no way of stopping // it. hence it is dangerous. leaving it as-is for now, since it's // only meant to be used for debugging. VM.sysWriteln("Timer ticks = ", timerTicks); doProfileReport.openNoHandshake(); // snapshot the threads acctLock.lockNoHandshake(); int numDebugThreads = numThreads; for (int i = 0; i < numThreads; ++i) { debugThreads[i] = threads[i]; } acctLock.unlock(); // do the magic for (int i = 0; i < numDebugThreads; ++i) { debugThreads[i].handleDebugRequestForThread(); debugThreads[i] = null; } } debugLock.unlock(); } } void timerTick() { if (shouldBeSampled()) { timeSliceExpired++; takeYieldpoint = 1; } } /** @return whether the thread is allowed to take yieldpoints */ @Inline public boolean yieldpointsEnabled() { return yieldpointsEnabledCount == 1; } /** Enable yieldpoints on this thread. */ public void enableYieldpoints() { ++yieldpointsEnabledCount; if (VM.VerifyAssertions) VM._assert(yieldpointsEnabledCount <= 1); if (yieldpointsEnabled() && yieldpointRequestPending) { takeYieldpoint = 1; yieldpointRequestPending = false; } } /** Disable yieldpoints on this thread. */ public void disableYieldpoints() { --yieldpointsEnabledCount; } /** * Fail if yieldpoints are disabled on this thread */ public void failIfYieldpointsDisabled() { if (!yieldpointsEnabled()) { VM.sysWrite("No yieldpoints on thread ", threadSlot); VM.sysWrite(" with addr ", Magic.objectAsAddress(this)); VM.sysWriteln(); VM.sysFail("Yieldpoints are disabled on this thread!"); } } /** * @return The currently executing thread */ @Uninterruptible public static RVMThread getCurrentThread() { if (VM.BuildForIA32) { return org.jikesrvm.ia32.ThreadLocalState.getCurrentThread(); } else { if (VM.VerifyAssertions) VM._assert(VM.BuildForPowerPC); return org.jikesrvm.ppc.ThreadLocalState.getCurrentThread(); } } /** * @return the unique slot of the currently executing thread */ public static int getCurrentThreadSlot() { return getCurrentThread().threadSlot; } /** * @return the slot of this thread */ public int getThreadSlot() { return threadSlot; } /** * Called during booting to give the boot thread a java.lang.Thread */ @Interruptible public void setupBootJavaThread() { thread = java.lang.JikesRVMSupport.createThread(this, "Jikes_RVM_Boot_Thread"); } /** * String representation of thread */ @Override public String toString() { return name; } /** * @return the current java.lang.Thread. */ public Thread getJavaLangThread() { return thread; } /** * @return current thread's JNI environment. */ public JNIEnvironment getJNIEnv() { return jniEnv; } /** @return the disable GC depth */ public int getDisableGCDepth() { return disableGCDepth; } public void setDisableGCDepth(int d) { disableGCDepth = d; } /** @return whether allocations by this thread are disallowed */ public boolean getDisallowAllocationsByThisThread() { return disallowAllocationsByThisThread; } /** Disallow allocations by this thread */ public void setDisallowAllocationsByThisThread() { disallowAllocationsByThisThread = true; } /** Allow allocations by this thread */ public void clearDisallowAllocationsByThisThread() { disallowAllocationsByThisThread = false; } /** * Initialize JNI environment for system threads. Called by VM.finishBooting */ @Interruptible public void initializeJNIEnv() { this.jniEnv = this.jniEnvShadow = new JNIEnvironment(); } /** * Indicate whether the stack of this Thread contains any C frame (used in * RuntimeEntrypoints.deliverHardwareException for stack resize) * * @return {@code false} during the prolog of the first Java to C transition, * {@code true} afterward */ public boolean hasNativeStackFrame() { return jniEnv != null && jniEnv.hasNativeStackFrame(); } /* * Starting and ending threads */ /** * Method to be executed when this thread starts running. Calls * java.lang.Thread.run but system threads can override directly. * <p> * This method will catch all uncaught throwables from the thread * and pass them to the thread's uncaught exception handler. */ @Interruptible @Entrypoint public void run() { try { synchronized (thread) { Throwable t = java.lang.JikesRVMSupport.getStillBorn(thread); if (t != null) { java.lang.JikesRVMSupport.setStillBorn(thread, null); throw t; } } thread.run(); } catch (Throwable t) { if (traceAcct) { VM.sysWriteln("Thread ",getThreadSlot()," exiting with exception."); } // Any throwable that reaches this point wasn't caught by the // thread and is therefore an uncaught exception by definition. // In order to make sure that terminate() sets the correct exit // status for this case, uncaughtExceptionCount needs to be // increased. uncaughtExceptionCount++; try { Thread.UncaughtExceptionHandler handler; handler = thread.getUncaughtExceptionHandler(); handler.uncaughtException(thread, t); } catch (Throwable ignore) { } } } /** * Begin execution of current thread by calling its "run" method. This method * is at the bottom of all created method's stacks. */ @Interruptible @SuppressWarnings({ "unused" }) @Entrypoint private static void startoff() { bindIfRequested(); RVMThread currentThread = getCurrentThread(); /* * get pthread_id from the operating system and store into RVMThread field */ currentThread.pthread_id = sysCall.sysGetThreadId(); currentThread.priority_handle = sysCall.sysGetThreadPriorityHandle(); /* * set thread priority to match stored value */ sysCall.sysSetThreadPriority(currentThread.pthread_id, currentThread.priority_handle, currentThread.priority - Thread.NORM_PRIORITY); currentThread.enableYieldpoints(); sysCall.sysStashVMThread(currentThread); if (traceAcct) { VM.sysWriteln("Thread #", currentThread.threadSlot, " with pthread id ", currentThread.pthread_id, " running!"); } if (trace) { VM.sysWriteln("Thread.startoff(): about to call ", currentThread.toString(), ".run()"); } try { if (currentThread.systemThread != null) { currentThread.systemThread.run(); } else { currentThread.run(); } } finally { if (trace) { VM.sysWriteln("Thread.startoff(): finished ", currentThread.toString(), ".run()"); } currentThread.terminate(); if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } } /** * Start execution of 'this' by creating and starting a native thread. */ @Interruptible public void start() { // N.B.: cannot hit a yieldpoint between setting execStatus and starting the // thread!! setExecStatus(IN_JAVA); acctLock.lockNoHandshake(); numActiveThreads++; if (isSystemThread()) { numActiveSystemThreads++; } JMXSupport.updatePeakThreadCount(numActiveThreads, numActiveSystemThreads); if (daemon) { numActiveDaemons++; } acctLock.unlock(); if (traceAcct) VM.sysWriteln("Thread #", threadSlot, " starting!"); sysCall.sysThreadCreate(contextRegisters.getInnermostInstructionAddress(), contextRegisters.getInnermostFramePointer(), Magic.objectAsAddress(this), Magic.getJTOC()); if (!isSystemThread()) { JMXSupport.increaseStartedThreadCount(); } } /** * Terminate execution of current thread by abandoning all references to it * and resuming execution in some other (ready) thread. */ @Interruptible public void terminate() { if (traceAcct) VM.sysWriteln("in terminate() for Thread #", threadSlot); if (VM.VerifyAssertions) VM._assert(getCurrentThread() == this); boolean terminateSystem = false; if (traceTermination) { VM.disableGC(); VM.sysWriteln("[ BEGIN Verbosely dumping stack at time of thread termination"); dumpStack(); VM.sysWriteln("END Verbosely dumping stack at time of creating thread termination ]"); VM.enableGC(); } // allow java.lang.Thread.exit() to remove this thread from ThreadGroup java.lang.JikesRVMSupport.threadDied(thread); TraceEngine.engine.removeFeedlet(feedlet); if (VM.VerifyAssertions) { if (Lock.countLocksHeldByThread(getLockingId()) > 0) { VM.sysWriteln("Error, thread terminating holding a lock"); RVMThread.dumpVirtualMachine(); } } if (traceAcct) VM.sysWriteln("doing accounting..."); acctLock.lockNoHandshake(); // if the thread terminated because of an exception, remove // the mark from the exception register object, or else the // garbage collector will attempt to relocate its ip field. exceptionRegisters.setInUse(false); numActiveThreads -= 1; if (isSystemThread()) { numActiveSystemThreads -= 1; } if (daemon) { numActiveDaemons -= 1; } if (traceAcct) VM.sysWriteln("active = ", numActiveThreads, ", daemons = ", numActiveDaemons); if ((numActiveDaemons == numActiveThreads) && (VM.mainThread != null) && VM.mainThread.launched) { // no non-daemon thread remains and the main thread was launched terminateSystem = true; } if (terminateSystem) { if (systemShuttingDown == false) { systemShuttingDown = true; } else { terminateSystem = false; } } if (traceTermination) { VM.sysWriteln("Thread.terminate: myThread.daemon = ", daemon); VM.sysWriteln(" RVMThread.numActiveThreads = ", RVMThread.numActiveThreads); VM.sysWriteln(" RVMThread.numActiveDaemons = ", RVMThread.numActiveDaemons); VM.sysWriteln(" terminateSystem = ", terminateSystem); } acctLock.unlock(); if (traceAcct) VM.sysWriteln("done with accounting."); if (terminateSystem) { if (traceAcct) VM.sysWriteln("terminating system."); if (uncaughtExceptionCount > 0) /* Use System.exit so that any shutdown hooks are run. */ { if (VM.TraceExceptionDelivery) { VM.sysWriteln("Calling sysExit due to uncaught exception."); } callSystemExit(EXIT_STATUS_DYING_WITH_UNCAUGHT_EXCEPTION); } else if (thread instanceof MainThread) { MainThread mt = (MainThread) thread; if (!mt.launched) { /* * Use System.exit so that any shutdown hooks are run. It is possible * that shutdown hooks may be installed by static initializers which * were run by classes initialized before we attempted to run the main * thread. (As of this writing, 24 January 2005, the Classpath * libraries do not do such a thing, but there is no reason why we * should not support this.) This was discussed on * jikesrvm-researchers on 23 Jan 2005 and 24 Jan 2005. */ callSystemExit(EXIT_STATUS_MAIN_THREAD_COULD_NOT_LAUNCH); } } /* Use System.exit so that any shutdown hooks are run. */ callSystemExit(0); if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } if (traceAcct) VM.sysWriteln("making joinable..."); // this works. we use synchronized because we cannot use the thread's // monitor(). see comment in join(). this is fine, because we're still // "running" from the standpoint of GC. synchronized (this) { isJoinable = true; notifyAll(); } if (traceAcct) VM.sysWriteln("Thread #", threadSlot, " is joinable."); if (traceAcct) VM.sysWriteln("making joinable..."); // Switch to uninterruptible portion of termination terminateUnpreemptible(); } /** * Calls {@link System#exit(int)} with the correct security status. * * @param exitStatus the exit status to pass on */ @Interruptible private void callSystemExit(final int exitStatus) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { System.exit(exitStatus); return null; } }); } /** * Unpreemptible portion of thread termination. Unpreemptible to avoid a dead * thread from being scheduled. */ @Unpreemptible private void terminateUnpreemptible() { // return cached free lock if (traceAcct) VM.sysWriteln("returning cached lock..."); if (cachedFreeLock != null) { if (Lock.trace) { VM.sysWriteln("Thread #", threadSlot, ": about to free lock ", Magic.objectAsAddress(cachedFreeLock)); } if (VM.VerifyAssertions) VM._assert(cachedFreeLock.mutex.latestContender != this); Lock.returnLock(cachedFreeLock); cachedFreeLock = null; } if (traceAcct) VM.sysWriteln("adding to aboutToTerminate..."); addAboutToTerminate(); // NB we can no longer do anything that would lead to write barriers or // GC if (traceAcct) { VM.sysWriteln("acquireCount for my monitor: ", monitor().acquireCount); VM.sysWriteln("timer ticks: ", timerTicks); VM.sysWriteln("yieldpoints taken: ", yieldpointsTaken); VM.sysWriteln("yieldpoints taken fully: ", yieldpointsTakenFully); } if (traceAcct) VM.sysWriteln("finishing thread termination..."); finishThreadTermination(); } /** Uninterruptible final portion of thread termination. */ void finishThreadTermination() { sysCall.sysThreadTerminate(); if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } /* * Support for yieldpoints */ /** * Yieldpoint taken in prologue. */ @BaselineSaveLSRegisters // Save all non-volatile registers in prologue @NoOptCompile @NoInline // We should also have a pragma that saves all non-volatiles in opt compiler, // BaselineExecuctionStateExtractor.java, should then restore all // non-volatiles before stack replacement // TODO fix this -- related to SaveVolatile @Entrypoint @Unpreemptible("Becoming another thread interrupts the current thread, avoid preemption in the process") public static void yieldpointFromPrologue() { Address fp = Magic.getFramePointer(); yieldpoint(PROLOGUE, fp); } /** * Yieldpoint taken on backedge. */ @BaselineSaveLSRegisters // Save all non-volatile registers in prologue @NoOptCompile @NoInline // We should also have a pragma that saves all non-volatiles in opt compiler, // BaselineExecuctionStateExtractor.java, should then restore all // non-volatiles before stack replacement // TODO fix this -- related to SaveVolatile @Entrypoint @Unpreemptible("Becoming another thread interrupts the current thread, avoid preemption in the process") public static void yieldpointFromBackedge() { Address fp = Magic.getFramePointer(); yieldpoint(BACKEDGE, fp); } /** * The return barrier. * <p> * The following code implements return barriers as described * for Lisp by Yuasa * * http://www.yuasa.kuis.kyoto-u.ac.jp/~yuasa/ilc2002/index.html * http://dx.doi.org/10.1109/ISORC.2005.45 * * and for Jikes RVM by Kumar et al * * http://dx.doi.org/10.1145/2398857.2384639 * <p> * This code is executed when a method returns into a frame that * has been hijacked by the return barrier mechanism. The return * barrier trampoline will save state, execute this method, and * then upon return from this method will transparently return into * the frame that had been hijacked. * <p> * In this default implementation, the barrier reinstalls itself * in the caller's frame thus incrementally moving the barrier down * the stack. * <p> * The execution of this method is fragile. It is generally safest * to call some other method from here that does the substantive work * of the barrier. */ @Entrypoint @Uninterruptible @Unpreemptible public static void returnBarrier() { /* reinstall the barrier in the caller's frame */ if (DEBUG_STACK_TRAMPOLINE) { if (VM.BuildForIA32) { VM.sysWriteln(getCurrentThread().getId(), " T0: ", getCurrentThread().trampolineRegisters.getGPRs(). get(org.jikesrvm.ia32.RegisterConstants.EAX.value()).toAddress()); VM.sysWriteln(getCurrentThread().getId(), " T1: ", getCurrentThread().trampolineRegisters.getGPRs(). get(org.jikesrvm.ia32.RegisterConstants.EDX.value()).toAddress()); } else { // Return barrier not yet supported on other architectures if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } VM.sysWriteln(getCurrentThread().getId(), " nf: ", getCurrentThread().hijackedReturnCallerFp); VM.sysWriteln(getCurrentThread().getId(), " lf: ", getCurrentThread().hijackedReturnCalleeFp); VM.sysWriteln(getCurrentThread().getId(), " fp: ", Magic.getFramePointer()); VM.sysWriteln(getCurrentThread().getId(), " np: ", Magic.getCallerFramePointer(Magic.getFramePointer())); } /* reinstall the barrier in the specified frame */ getCurrentThread().installStackTrampolineBridge(getCurrentThread().hijackedReturnCallerFp); } /** * Install the stack trampoline bridge at a given frame, hijacking * that frame, saving the hijacked return address and callee fp * in thread-local state to allow execution of the hijacked frame * later. * * @param targetFp The frame to be hijacked. */ @Uninterruptible public void installStackTrampolineBridge(Address targetFp) { Address trampoline = getStackTrampolineBridgeIP(); if (trampoline.isZero()) { if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); else VM.sysWriteln("Warning: attempt to install stack trampoline without bridge instructions - nothing done. See RVMThread."); } else if (trampoline.NE(Magic.getReturnAddressUnchecked(targetFp))) { /* install the trampoline at fp or the next suitable frame after fp */ while (true) { if (Magic.getCallerFramePointer(targetFp).EQ(StackFrameLayout.getStackFrameSentinelFP())) { /* if we're at the bottom of the stack, then do not install anything */ hijackedReturnAddress = Address.zero(); hijackedReturnCalleeFp = Address.zero(); return; } int cmid = Magic.getCompiledMethodID(targetFp); if (cmid == StackFrameLayout.getInvisibleMethodID()) { /* skip invisible methods */ targetFp = Magic.getCallerFramePointer(targetFp); } else { CompiledMethod calleeCM = CompiledMethods.getCompiledMethod(cmid); if (calleeCM.getCompilerType() == CompiledMethod.TRAP || calleeCM.getMethod().getDeclaringClass().hasBridgeFromNativeAnnotation()) { /* skip traps and native bridges */ targetFp = Magic.getCallerFramePointer(targetFp); } else break; } } hijackedReturnAddress = Magic.getReturnAddressUnchecked(targetFp); hijackedReturnCalleeFp = targetFp; hijackedReturnCallerFp = Magic.getCallerFramePointer(targetFp); if (VM.VerifyAssertions) VM._assert(trampoline.NE(hijackedReturnAddress)); if (DEBUG_STACK_TRAMPOLINE) dumpFrame(targetFp); Magic.setReturnAddress(targetFp, trampoline); if (DEBUG_STACK_TRAMPOLINE) { dumpFrame(targetFp); VM.sysWriteln(getId(), " Installing trampoline at: ", targetFp); VM.sysWriteln(getId(), " Trampoline: ", trampoline); VM.sysWriteln(getId(), " Hijacked return address: ", hijackedReturnAddress); VM.sysWriteln(getId(), " Callee fp: ", hijackedReturnCalleeFp); VM.sysWriteln(getId(), " Caller fp: ", hijackedReturnCallerFp); dumpStack(hijackedReturnCalleeFp); } } } /** * de-install the stack trampoline (disabling return barriers). */ @Uninterruptible public void deInstallStackTrampoline() { if (DEBUG_STACK_TRAMPOLINE) VM.sysWriteln("deinstalling trampoline: ", framePointer); if (!hijackedReturnCalleeFp.isZero()) { if (DEBUG_STACK_TRAMPOLINE) VM.sysWriteln("need to reinstall: ", hijackedReturnAddress); hijackedReturnCalleeFp.plus(StackFrameLayout.getStackFrameReturnAddressOffset()).store(hijackedReturnAddress); hijackedReturnCalleeFp = Address.zero(); hijackedReturnCallerFp = StackFrameLayout.getStackFrameSentinelFP(); } } /** @return the address of the stack trampoline bridge code */ @Inline private Address getStackTrampolineBridgeIP() { return Magic.objectAsAddress(stackTrampolineBridgeInstructions); } /** @return the hijacked return address */ @Inline public Address getTrampolineHijackedReturnAddress() { return hijackedReturnAddress; } /** * Determine whether a given method is the stack trampoline * * @param ip the code to be checked * @return <code>true</code> if the code is the stack trampoline. */ @Inline public static boolean isTrampolineIP(Address ip) { return getCurrentThread().getStackTrampolineBridgeIP().EQ(ip); } /** * Given a frame that has been hijacked by the stack trampoline, * return the real (hijacked) return address. * * @param hijackedFp a frame that has been hijacked by the stack trampoline * @return the return address for the frame that was hijacked. */ @Uninterruptible public static Address getHijackedReturnAddress(Address hijackedFp) { if (VM.VerifyAssertions) VM._assert(isTrampolineIP(Magic.getReturnAddressUnchecked(hijackedFp))); RVMThread t = getCurrentThread(); if (!t.hijackedReturnCalleeFp.EQ(hijackedFp)) { for (int tid = 0; tid < nextSlot; tid++) { t = threadBySlot[tid]; if (t != null && t.hijackedReturnCalleeFp.EQ(hijackedFp)) break; } } if (VM.VerifyAssertions) VM._assert(t.hijackedReturnCalleeFp.EQ(hijackedFp), "No matching thread found"); return t.hijackedReturnAddress; } /** * Dump the specified frame in a format useful for debugging the stack * trampoline * * @param fp The frame to be dumped. */ private static void dumpFrame(Address fp) { final Offset returnAddressOffset = StackFrameLayout.getStackFrameReturnAddressOffset(); final Offset methodIDOffset = StackFrameLayout.getStackFrameMethodIDOffset(); Address sp = fp.minus(40); VM.sysWriteln("--"); Address nextFp = Magic.getCallerFramePointer(fp); while (sp.LE(nextFp)) { VM.sysWrite("[", sp, "]"); if (sp.EQ(fp) || sp.EQ(nextFp)) { VM.sysWrite("* "); } else if (sp.EQ(fp.plus(returnAddressOffset)) || sp.EQ(nextFp.plus(returnAddressOffset))) { VM.sysWrite("R "); } else if (sp.EQ(fp.plus(methodIDOffset)) || sp.EQ(nextFp.plus(methodIDOffset))) { VM.sysWrite("M "); } else { VM.sysWrite(" "); } VM.sysWriteln(sp.loadInt()); sp = sp.plus(4); } } /** * @return the caller of the frame in which the trampoline is installed (STACKFRAME_SENTINEL_FP by default) */ public Address getNextUnencounteredFrame() { return hijackedReturnCallerFp.EQ(StackFrameLayout.getStackFrameSentinelFP()) ? hijackedReturnCallerFp : Magic.getCallerFramePointer(hijackedReturnCallerFp); } /** * Yieldpoint taken in epilogue. */ @BaselineSaveLSRegisters // Save all non-volatile registers in prologue @NoOptCompile @NoInline // We should also have a pragma that saves all non-volatiles in opt compiler, // BaselineExecutionStateExtractor.java, should then restore all non-volatiles // before stack replacement // TODO fix this -- related to SaveVolatile @Entrypoint @Unpreemptible("Becoming another thread interrupts the current thread, avoid preemption in the process") public static void yieldpointFromEpilogue() { Address fp = Magic.getFramePointer(); yieldpoint(EPILOGUE, fp); } /* * Support for suspend/resume */ /** * Suspend execution of current thread until it is resumed. Call only if * caller has appropriate security clearance. */ @UnpreemptibleNoWarn("Exceptions may possibly cause yields") public void suspend() { if (false) VM.sysWriteln("Thread #",getCurrentThreadSlot()," suspending Thread #",getThreadSlot()); ObjectModel.genericUnlock(thread); Throwable rethrow = null; try { observeExecStatus(); if (execStatus != IN_JAVA && execStatus != IN_JAVA_TO_BLOCK && execStatus != IN_NATIVE && execStatus != BLOCKED_IN_NATIVE && execStatus != BLOCKED_IN_JNI && execStatus != IN_JNI) { throw new IllegalThreadStateException( "Cannot suspend a thread that is not running."); } block(suspendBlockAdapter); } catch (Throwable t) { rethrow = t; } ObjectModel.genericLock(thread); if (rethrow != null) RuntimeEntrypoints.athrow(rethrow); } /** * Resume execution of a thread that has been suspended. Call only if caller * has appropriate security clearance. */ @Interruptible public void resume() { unblock(suspendBlockAdapter); } public static void yieldNoHandshake() { sysCall.sysThreadYield(); } @UnpreemptibleNoWarn public static void yieldWithHandshake() { getCurrentThread().checkBlock(); sysCall.sysThreadYield(); } /** * Suspend execution of current thread for specified number of seconds (or * fraction). * * @param ns the number of nanoseconds to sleep for * @throws InterruptedException when the sleep is interrupted */ @Interruptible public static void sleep(long ns) throws InterruptedException { RVMThread t = getCurrentThread(); t.waiting = Waiting.TIMED_WAITING; long atStart = sysCall.sysNanoTime(); long whenEnd = atStart + ns; t.monitor().lockNoHandshake(); while (!t.hasInterrupt && t.asyncThrowable == null && sysCall.sysNanoTime() < whenEnd) { t.monitor().timedWaitAbsoluteWithHandshake(whenEnd); } boolean throwInterrupt = false; Throwable throwThis = null; if (t.hasInterrupt) { t.hasInterrupt = false; throwInterrupt = true; } if (t.asyncThrowable != null) { throwThis = t.asyncThrowable; t.asyncThrowable = null; } t.monitor().unlock(); t.waiting = Waiting.RUNNABLE; if (throwThis != null) { RuntimeEntrypoints.athrow(throwThis); } if (throwInterrupt) { throw new InterruptedException("sleep interrupted"); } } /** * Suspend execution of current thread for specified number of seconds (or * fraction). The time from both parameters is added up. * * @param ns the number of nanoseconds to sleep for * @param millis the number of milliseconds to sleep for * @throws InterruptedException when the sleep is interrupted */ @Interruptible public static void sleep(long millis, int ns) throws InterruptedException { sleep(ns + millis * 1000L * 1000L); } /* * Wait and notify support */ @Interruptible void waitImpl(Object o, boolean hasTimeout, long whenWakeupNanos) { boolean throwInterrupt = false; Throwable throwThis = null; if (asyncThrowable != null) { throwThis = asyncThrowable; asyncThrowable = null; } else if (!ObjectModel.holdsLock(o, this)) { throw new IllegalMonitorStateException("waiting on " + o); } else if (hasInterrupt) { throwInterrupt = true; hasInterrupt = false; } else { if (STATS) { waitTimeStart = Time.currentTimeMillis(); } waiting = hasTimeout ? Waiting.TIMED_WAITING : Waiting.WAITING; if (STATS) { if (hasTimeout) { timedWaitOperations++; } else { waitOperations++; } } // get lock for object Lock l = ObjectModel.getHeavyLock(o, true); // release the lock l.mutex.lock(); // this thread is supposed to own the lock on o if (VM.VerifyAssertions) VM._assert(l.getOwnerId() == getLockingId()); RVMThread toAwaken = l.entering.dequeue(); waitObject = l.getLockedObject(); waitCount = l.getRecursionCount(); l.setOwnerId(0); l.waiting.enqueue(this); l.mutex.unlock(); // if there was a thread waiting, awaken it if (toAwaken != null) { // is this where the problem is coming from? toAwaken.monitor().lockedBroadcastNoHandshake(); } // block monitor().lockNoHandshake(); while (l.waiting.isQueued(this) && !hasInterrupt && asyncThrowable == null && (!hasTimeout || sysCall.sysNanoTime() < whenWakeupNanos)) { if (hasTimeout) { monitor().timedWaitAbsoluteWithHandshake(whenWakeupNanos); } else { monitor().waitWithHandshake(); } } // figure out if anything special happened while we were blocked if (hasInterrupt) { throwInterrupt = true; hasInterrupt = false; } if (asyncThrowable != null) { throwThis = asyncThrowable; asyncThrowable = null; } monitor().unlock(); if (l.waiting.isQueued(this)) { l.mutex.lock(); l.waiting.remove(this); /* * in case we got here due to an interrupt or a * stop() rather than a notify */ l.mutex.unlock(); // Note that the above must be done before attempting to acquire // the lock, since acquiring the lock may require queueing the thread. // But we cannot queue the thread if it is already on another // queue. } // reacquire the lock, restoring the recursion count ObjectModel.genericLock(o); waitObject = null; if (waitCount != 1) { // reset recursion count Lock l2 = ObjectModel.getHeavyLock(o, true); l2.setRecursionCount(waitCount); } waiting = Waiting.RUNNABLE; if (STATS) { totalWaitTime += (sysCall.sysCurrentTimeMillis() - waitTimeStart); } } // check if we should exit in a special way if (throwThis != null) { RuntimeEntrypoints.athrow(throwThis); } if (throwInterrupt) { RuntimeEntrypoints.athrow(new InterruptedException("sleep interrupted")); } } /** * Support for Java {@link java.lang.Object#wait()} synchronization primitive. * * @param o * the object synchronized on */ @Interruptible /* only loses control at expected points -- I think -dave */ public static void wait(Object o) { getCurrentThread().waitImpl(o, false, 0); } /** * Support for Java {@link java.lang.Object#wait()} synchronization primitive. * * @param o * the object synchronized on * @param millis * the number of milliseconds to wait for notification */ @Interruptible public static void wait(Object o, long millis) { long currentNanos = sysCall.sysNanoTime(); getCurrentThread().waitImpl(o, true, currentNanos + millis * 1000 * 1000); } long getTotalWaitingCount() { if (STATS) { return waitOperations + timedWaitOperations; } else { return -1L; } } long getTotalWaitedTime() { if (STATS) { return totalWaitTime; } else { return -1; } } /** * Support for RTSJ- and pthread-style absolute wait. * * @param o * the object synchronized on * @param whenNanos * the absolute time in nanoseconds when we should wake up */ @Interruptible public static void waitAbsoluteNanos(Object o, long whenNanos) { getCurrentThread().waitImpl(o, true, whenNanos); } @UnpreemptibleNoWarn("Possible context when generating exception") public static void raiseIllegalMonitorStateException(String msg, Object o) { throw new IllegalMonitorStateException(msg + (o == null ? "<null>" : o.toString())); } /** * Support for Java {@link java.lang.Object#notify()} synchronization * primitive. * * @param o the object synchronized on */ @Interruptible public static void notify(Object o) { if (STATS) notifyOperations++; Lock l = ObjectModel.getHeavyLock(o, false); if (l == null) return; // the reason for locking: when inflating a lock we *first* install it in the status // word and *then* initialize its state. but fortunately, we do so while holding // the lock's mutex. thus acquiring the lock's mutex is the only way to ensure that // we see the lock's state after initialization. l.mutex.lock(); int owner = l.getOwnerId(); l.mutex.unlock(); int me = getCurrentThread().getLockingId(); if (owner != me) { raiseIllegalMonitorStateException("notifying (expected lock to be held by " + me + "(" + getCurrentThread().getLockingId() + ") but was held by " + owner + "(" + l.getOwnerId() + ")) ", o); } l.mutex.lock(); RVMThread toAwaken = l.waiting.dequeue(); l.mutex.unlock(); if (toAwaken != null) { toAwaken.monitor().lockedBroadcastNoHandshake(); } } /** * Support for Java synchronization primitive. * * @param o the object synchronized on * @see java.lang.Object#notifyAll */ @Interruptible public static void notifyAll(Object o) { if (STATS) notifyAllOperations++; Lock l = ObjectModel.getHeavyLock(o, false); if (l == null) return; l.mutex.lock(); int owner = l.getOwnerId(); l.mutex.unlock(); if (owner != getCurrentThread().getLockingId()) { raiseIllegalMonitorStateException("notifying all (expected lock to be held by " + getCurrentThread().getLockingId() + " but was held by " + l.getOwnerId() + ") ", o); } for (;;) { l.mutex.lock(); RVMThread toAwaken = l.waiting.dequeue(); l.mutex.unlock(); if (toAwaken == null) break; toAwaken.monitor().lockedBroadcastNoHandshake(); } } public void stop(Throwable cause) { monitor().lockNoHandshake(); asyncThrowable = cause; takeYieldpoint = 1; monitor().broadcast(); monitor().unlock(); } /* * Park and unpark support */ @Interruptible public void park(boolean isAbsolute, long time) throws Throwable { if (parkingPermit) { // fast path parkingPermit = false; Magic.sync(); return; } // massive retardation. someone might be holding the java.lang.Thread lock. boolean holdsLock = holdsLock(thread); if (holdsLock) ObjectModel.genericUnlock(thread); boolean hasTimeout; long whenWakeupNanos; hasTimeout = (time != 0); if (isAbsolute) { whenWakeupNanos = time; } else { whenWakeupNanos = sysCall.sysNanoTime() + time; } Throwable throwThis = null; monitor().lockNoHandshake(); waiting = hasTimeout ? Waiting.TIMED_WAITING : Waiting.WAITING; while (!parkingPermit && !hasInterrupt && asyncThrowable == null && (!hasTimeout || sysCall.sysNanoTime() < whenWakeupNanos)) { if (hasTimeout) { monitor().timedWaitAbsoluteWithHandshake(whenWakeupNanos); } else { monitor().waitWithHandshake(); } } waiting = Waiting.RUNNABLE; parkingPermit = false; if (asyncThrowable != null) { throwThis = asyncThrowable; asyncThrowable = null; } monitor().unlock(); if (holdsLock) ObjectModel.genericLock(thread); if (throwThis != null) { throw throwThis; } } @Interruptible public void unpark() { monitor().lockNoHandshake(); parkingPermit = true; monitor().broadcast(); monitor().unlock(); } /** * Get this thread's id for use in lock ownership tests. This is just the * thread's slot as returned by {@link #getThreadSlot()}, shifted appropriately * so it can be directly used in the ownership tests. * * @return the thread's id for use in lock owner ship tests */ public int getLockingId() { return lockingId; } /** * Provides a skeleton implementation for use in soft handshakes. * <p> * During a soft handshake, the requesting thread waits for all mutator threads * (i.e. non-gc threads) to perform a requested action. */ @Uninterruptible public abstract static class SoftHandshakeVisitor { /** * Sets whatever flags need to be set to signal that the given thread should * perform some action when it acknowledges the soft handshake. * <p> * This method is only called for threads for which {@link #includeThread(RVMThread)} * is {@code true}. * <p> * This method is called with the thread's monitor held, but while the * thread may still be running. This method is not called on mutators that * have indicated that they are about to terminate. * * @param t the thread that will be processed * @return {@code false} if not interested in this thread, {@code true} otherwise. * Returning {@code true} will cause a soft handshake request to be put through. */ public abstract boolean checkAndSignal(RVMThread t); /** * Called when it is determined that the thread is stuck in native. While * this method is being called, the thread cannot return to running Java * code. As such, it is safe to perform actions "on the thread's behalf". * <p> * This implementation does nothing. * * @param t the thread that's stuck in native */ public void notifyStuckInNative(RVMThread t) { } /** * Checks whether to include the specified thread in the soft handshake. * <p> * This method will never see any threads from the garbage collector because * those are excluded from the soft handshake by design. * <p> * This implementation always returns {@code true}. * * @param t The thread to check for inclusion * @return {@code true} if the thread should be included. */ public boolean includeThread(RVMThread t) { return true; } } @NoCheckStore public static int snapshotHandshakeThreads(SoftHandshakeVisitor v) { // figure out which threads to consider acctLock.lockNoHandshake(); // get a consistent view of which threads are live. int numToHandshake = 0; for (int i = 0; i < numThreads; ++i) { RVMThread t = threads[i]; // We exclude the following threads from the handshake: // -the current thread (because we would deadlock if we included it) // -threads that ignore handshakes by design (e.g. the timer thread) // -collector threads (because they never yield and we would deadlock if we // tried to wait for them) // -the threads that the provided visitor does not want to include if (t != RVMThread.getCurrentThread() && !t.ignoreHandshakesAndGC() && !t.isCollectorThread() && v.includeThread(t)) { handshakeThreads[numToHandshake++] = t; } } acctLock.unlock(); return numToHandshake; } /** * Tell each thread to take a yieldpoint and wait until all of them have done * so at least once. Additionally, call the visitor on each thread when making * the yieldpoint request; the purpose of the visitor is to set any additional * fields as needed to make specific requests to the threads that yield. Note * that the visitor's <code>visit()</code> method is called with both the * thread's monitor held, and the <code>softHandshakeDataLock</code> held. * <p> * Currently we only use this mechanism for code patch isync requests on PPC, * but this mechanism is powerful enough to be used by sliding-views style * concurrent GC. * * @param v the visitor to use for the handshake */ @NoCheckStore @Unpreemptible("Does not perform actions that lead to blocking, but may wait for threads to rendezvous with the soft handshake") public static void softHandshake(SoftHandshakeVisitor v) { handshakeLock.lockWithHandshake(); /* * prevent multiple (soft or hard) handshakes * from proceeding concurrently */ int numToHandshake = snapshotHandshakeThreads(v); if (VM.VerifyAssertions) VM._assert(softHandshakeLeft == 0); // in turn, check if each thread needs a handshake, and if so, // request one for (int i = 0; i < numToHandshake; ++i) { RVMThread t = handshakeThreads[i]; handshakeThreads[i] = null; // help GC t.monitor().lockNoHandshake(); boolean waitForThisThread = false; if (!t.isAboutToTerminate && v.checkAndSignal(t)) { // CAS the execStatus field t.setBlockedExecStatus(); // Note that at this point if the thread tries to either enter or // exit Java code, it will be diverted into either // enterNativeBlocked() or checkBlock(), both of which cannot do // anything until they acquire the monitor() lock, which we now // hold. Thus, the code below can, at its leisure, examine the // thread's state and make its decision about what to do, fully // confident that the thread's state is blocked from changing. if (t.isInJava()) { // the thread is currently executing Java code, so we must ensure // that it either: // 1) takes the next yieldpoint and rendezvous with this soft // handshake request (see yieldpoint), or // 2) performs the rendezvous when leaving Java code // (see enterNativeBlocked, checkBlock, and addAboutToTerminate) // either way, we will wait for it to get there before exiting // this call, since the caller expects that after softHandshake() // returns, no thread will be running Java code without having // acknowledged. t.softHandshakeRequested = true; t.takeYieldpoint = 1; waitForThisThread = true; } else { // the thread is not in Java code (it may be blocked or it may be // in native), so we don't have to wait for it since it will // do the Right Thing before returning to Java code. essentially, // the thread cannot go back to running Java without doing whatever // was requested because: // A) we've set the execStatus to blocked, and // B) we're holding its lock. v.notifyStuckInNative(t); } } t.monitor().unlock(); // NOTE: at this point the thread may already decrement the // softHandshakeLeft counter, causing it to potentially go negative. // this is unlikely and completely harmless. if (waitForThisThread) { softHandshakeDataLock.lockNoHandshake(); softHandshakeLeft++; softHandshakeDataLock.unlock(); } } // wait for all threads to reach the handshake softHandshakeDataLock.lockNoHandshake(); if (VM.VerifyAssertions) VM._assert(softHandshakeLeft >= 0); while (softHandshakeLeft > 0) { // wait and tell the world that we're off in native land. this way // if someone tries to block us at this point (suspend() or GC), // they'll know not to wait for us. softHandshakeDataLock.waitWithHandshake(); } if (VM.VerifyAssertions) VM._assert(softHandshakeLeft == 0); softHandshakeDataLock.unlock(); processAboutToTerminate(); handshakeLock.unlock(); } /** * Checks and clears the need for a soft handshake rendezvous. This method * cannot do anything that leads to a write barrier or allocation. * * @return whether the soft handshake can be committed */ public boolean softRendezvousCheckAndClear() { boolean result = false; monitor().lockNoHandshake(); if (softHandshakeRequested) { softHandshakeRequested = false; result = true; } monitor().unlock(); return result; } /** * Commits the soft handshake rendezvous. This method cannot do anything * that leads to a write barrier or allocation. */ public void softRendezvousCommit() { softHandshakeDataLock.lockNoHandshake(); softHandshakeLeft--; if (softHandshakeLeft == 0) { softHandshakeDataLock.broadcast(); } softHandshakeDataLock.unlock(); } /** * Rendezvous with a soft handshake request. Can only be called when the * thread's monitor is held. */ public void softRendezvous() { if (softRendezvousCheckAndClear()) softRendezvousCommit(); } /** * Handle requests that required a soft handshake. May be called after we * acknowledged the soft handshake. Thus - this is for actions in which it is * sufficient for the thread to acknowledge that it plans to act upon the * request in the immediate future, rather than that the thread acts upon the * request prior to acknowledging. * <p> * This is almost always called with the monitor() lock held, but that's * not guaranteed. If you need that lock, you can grab it (since it's a * recursive lock). But you should avoid grabbing other sorts of locks since * that might cause deadlock. */ void handleHandshakeRequest() { // Process request for code-patch memory sync operation if (VM.BuildForPowerPC && codePatchSyncRequested) { codePatchSyncRequested = false; // Q: Is this sufficient? Ask Steve why we don't need to sync // icache/dcache. --dave // A: Yes, this is sufficient. We (Filip and Dave) talked about it and // agree that remote processors only need to execute isync. --Filip // make sure not get stale data Magic.isync(); } // process memory management requests if (flushRequested && activeMutatorContext) { MemoryManager.flushMutatorContext(); flushRequested = false; } // not really a "soft handshake" request but we handle it here anyway if (asyncDebugRequestedForThisThread) { asyncDebugRequestedForThisThread = false; dumpLock.lockNoHandshake(); VM.sysWriteln("Handling async stack trace request..."); dump(); VM.sysWriteln(); dumpStack(); dumpLock.unlock(); } } /** * Stop all mutator threads. This is current intended to be run by a single thread. * * Fixpoint until there are no threads that we haven't blocked. Fixpoint is needed to * catch the (unlikely) case that a thread spawns another thread while we are waiting. */ @NoCheckStore @Unpreemptible public static void blockAllMutatorsForGC() { RVMThread.handshakeLock.lockNoHandshake(); while (true) { // (1) Find all the threads that need to be blocked for GC RVMThread.acctLock.lockNoHandshake(); int numToHandshake = 0; for (int i = 0; i < RVMThread.numThreads; i++) { RVMThread t = RVMThread.threads[i]; if (!t.isCollectorThread() && !t.ignoreHandshakesAndGC()) { RVMThread.handshakeThreads[numToHandshake++] = t; } } RVMThread.acctLock.unlock(); // (2) Remove any threads that have already been blocked from the list. for (int i = 0; i < numToHandshake; i++) { RVMThread t = RVMThread.handshakeThreads[i]; t.monitor().lockNoHandshake(); if (t.blockedFor(RVMThread.gcBlockAdapter) || RVMThread.notRunning(t.asyncBlock(RVMThread.gcBlockAdapter))) { // Already blocked or not running, remove. RVMThread.handshakeThreads[i--] = RVMThread.handshakeThreads[--numToHandshake]; RVMThread.handshakeThreads[numToHandshake] = null; // help GC } t.monitor().unlock(); } // (3) Quit trying to block threads if all threads are either blocked // or not running (a thread is "not running" if it is NEW or TERMINATED; // in the former case it means that the thread has not had start() // called on it while in the latter case it means that the thread // is either in the TERMINATED state or is about to be in that state // real soon now, and will not perform any heap-related work before // terminating). if (numToHandshake == 0) break; // (4) Request a block for GC from all other threads. for (int i = 0; i < numToHandshake; i++) { if (false) VM.sysWriteln("Waiting for ", RVMThread.handshakeThreads[i].getThreadSlot(), " to block."); RVMThread t = RVMThread.handshakeThreads[i]; RVMThread.observeExecStatusAtSTW(t.block(RVMThread.gcBlockAdapter)); RVMThread.handshakeThreads[i] = null; // help GC } } RVMThread.handshakeLock.unlock(); // Deal with terminating threads to ensure that all threads are either dead to MMTk or stopped above. RVMThread.processAboutToTerminate(); } /** * Unblock all mutators blocked for GC. */ @NoCheckStore @Unpreemptible public static void unblockAllMutatorsForGC() { RVMThread.handshakeLock.lockNoHandshake(); RVMThread.acctLock.lockNoHandshake(); int numToHandshake = 0; for (int i = 0; i < RVMThread.numThreads; i++) { RVMThread t = RVMThread.threads[i]; if (!t.isCollectorThread() && !t.ignoreHandshakesAndGC()) { RVMThread.handshakeThreads[numToHandshake++] = t; } } RVMThread.acctLock.unlock(); for (int i = 0; i < numToHandshake; i++) { RVMThread.handshakeThreads[i].unblock(RVMThread.gcBlockAdapter); RVMThread.handshakeThreads[i] = null; // Help GC } RVMThread.handshakeLock.unlock(); } @Uninterruptible public static class HardHandshakeVisitor { public boolean includeThread(RVMThread t) { return true; } } @Uninterruptible @NonMoving static class AllButGCHardHandshakeVisitor extends HardHandshakeVisitor { @Override public boolean includeThread(RVMThread t) { return !t.isCollectorThread(); } } public static final AllButGCHardHandshakeVisitor allButGC = new AllButGCHardHandshakeVisitor(); static long totalSuspendTime; static long totalResumeTime; @Unpreemptible @NoCheckStore public static void hardHandshakeSuspend(BlockAdapter ba, HardHandshakeVisitor hhv) { long before = sysCall.sysNanoTime(); RVMThread current = getCurrentThread(); handshakeLock.lockWithHandshake(); int numLockedLocks = 0; for (int i = 0; i < nextSlot;++i) { Monitor l = communicationLockBySlot[i]; if (l != null) { l.lockWithHandshake(); numLockedLocks++; } } // fixpoint until there are no threads that we haven't blocked. // fixpoint is needed in case some thread spawns another thread // while we're waiting. that is unlikely but possible. for (;;) { acctLock.lockNoHandshake(); int numToHandshake = 0; for (int i = 0; i < numThreads;++i) { RVMThread t = threads[i]; if (t != current && !t.ignoreHandshakesAndGC() && hhv.includeThread(t)) { handshakeThreads[numToHandshake++] = t; } } acctLock.unlock(); for (int i = 0; i < numToHandshake;++i) { RVMThread t = handshakeThreads[i]; t.monitor().lockNoHandshake(); if (t.blockedFor(ba) || notRunning(t.asyncBlock(ba))) { // already blocked or not running, remove handshakeThreads[i--] = handshakeThreads[--numToHandshake]; handshakeThreads[numToHandshake] = null; // help GC } t.monitor().unlock(); } // quit trying to block threads if all threads are either blocked // or not running (a thread is "not running" if it is NEW or TERMINATED; // in the former case it means that the thread has not had start() // called on it while in the latter case it means that the thread // is either in the TERMINATED state or is about to be in that state // real soon now, and will not perform any heap-related stuff before // terminating). if (numToHandshake == 0) break; for (int i = 0; i < numToHandshake;++i) { RVMThread t = handshakeThreads[i]; observeExecStatusAtSTW(t.block(ba)); handshakeThreads[i] = null; // help GC } } processAboutToTerminate(); /* * ensure that any threads that died while * we were stopping the world notify us * that they had stopped. */ int numUnlockedLocks = 0; for (int i = 0; i < nextSlot;++i) { Monitor l = communicationLockBySlot[i]; if (l != null) { l.unlock(); numUnlockedLocks++; } } if (VM.VerifyAssertions) VM._assert(numLockedLocks == numUnlockedLocks); handshakeLock.unlock(); if (false) { long after = sysCall.sysNanoTime(); totalSuspendTime += after - before; VM.sysWriteln("Stopping the world took ",(after - before)," ns (",totalSuspendTime," ns total)"); } } @NoCheckStore @Unpreemptible public static void hardHandshakeResume(BlockAdapter ba, HardHandshakeVisitor hhv) { long before = sysCall.sysNanoTime(); handshakeLock.lockWithHandshake(); RVMThread current = getCurrentThread(); acctLock.lockNoHandshake(); int numToHandshake = 0; for (int i = 0; i < numThreads;++i) { RVMThread t = threads[i]; if (t != current && !t.ignoreHandshakesAndGC() && hhv.includeThread(t)) { handshakeThreads[numToHandshake++] = t; } } acctLock.unlock(); for (int i = 0; i < numToHandshake;++i) { handshakeThreads[i].unblock(ba); handshakeThreads[i] = null; // help GC } handshakeLock.unlock(); if (false) { long after = sysCall.sysNanoTime(); totalResumeTime += after - before; VM.sysWriteln("Resuming the world took ",(after - before)," ns (",totalResumeTime," ns total)"); } } @Unpreemptible public static void hardHandshakeSuspend() { hardHandshakeSuspend(handshakeBlockAdapter,allButGC); } @Unpreemptible public static void hardHandshakeResume() { hardHandshakeResume(handshakeBlockAdapter,allButGC); } /** * Process a taken yieldpoint. * * @param whereFrom source of the yieldpoint (e.g. backedge) * @param yieldpointServiceMethodFP the frame pointer of the service * method that called this method */ @Unpreemptible("May block if the thread was asked to do so but otherwise does not perform actions that may lead to blocking") public static void yieldpoint(int whereFrom, Address yieldpointServiceMethodFP) { RVMThread t = getCurrentThread(); boolean wasAtYieldpoint = t.atYieldpoint; t.atYieldpoint = true; t.yieldpointsTaken++; // If thread is in critical section we can't do anything right now, defer // until later // we do this without acquiring locks, since part of the point of disabling // yieldpoints is to ensure that locks are not "magically" acquired // through unexpected yieldpoints. As well, this makes code running with // yieldpoints disabled more predictable. Note furthermore that the only // race here is setting takeYieldpoint to 0. But this is perfectly safe, // since we are guaranteeing that a yieldpoint will run after we emerge from // the no-yieldpoints code. At worst, setting takeYieldpoint to 0 will be // lost (because some other thread sets it to non-0), but in that case we'll // just come back here and reset it to 0 again. if (!t.yieldpointsEnabled()) { if (VM.VerifyAssertions) VM._assert(!t.yieldToOSRRequested); if (traceBlock && !wasAtYieldpoint) { VM.sysWriteln("Thread #", t.threadSlot, " deferring yield!"); dumpStack(); } t.yieldpointRequestPending = true; t.takeYieldpoint = 0; t.atYieldpoint = false; return; } t.yieldpointsTakenFully++; Throwable throwThis = null; t.monitor().lockNoHandshake(); int takeYieldpointVal = t.takeYieldpoint; if (takeYieldpointVal != 0) { t.takeYieldpoint = 0; // do two things: check if we should be blocking, and act upon // handshake requests. This also has the effect of reasserting that // we are in fact IN_JAVA (as opposed to IN_JAVA_TO_BLOCK). t.checkBlock(); // Process timer interrupt event if (t.timeSliceExpired != 0) { t.timeSliceExpired = 0; if (t.yieldForCBSCall || t.yieldForCBSMethod) { /* * CBS Sampling is still active from previous quantum. Note that fact, * but leave all the other CBS parameters alone. */ } else { if (VM.CBSCallSamplesPerTick > 0) { t.yieldForCBSCall = true; t.takeYieldpoint = -1; t.firstCBSCallSample++; t.firstCBSCallSample = t.firstCBSCallSample % VM.CBSCallSampleStride; t.countdownCBSCall = t.firstCBSCallSample; t.numCBSCallSamples = VM.CBSCallSamplesPerTick; } if (VM.CBSMethodSamplesPerTick > 0) { t.yieldForCBSMethod = true; t.takeYieldpoint = -1; t.firstCBSMethodSample++; t.firstCBSMethodSample = t.firstCBSMethodSample % VM.CBSMethodSampleStride; t.countdownCBSMethod = t.firstCBSMethodSample; t.numCBSMethodSamples = VM.CBSMethodSamplesPerTick; } } if (VM.BuildForAdaptiveSystem) { RuntimeMeasurements.takeTimerSample(whereFrom, yieldpointServiceMethodFP); } if (VM.BuildForAdaptiveSystem) { OSRListener .checkForOSRPromotion(whereFrom, yieldpointServiceMethodFP); } } if (t.yieldForCBSCall) { if (!(whereFrom == BACKEDGE || whereFrom == OSROPT)) { if (--t.countdownCBSCall <= 0) { if (VM.BuildForAdaptiveSystem) { // take CBS sample RuntimeMeasurements.takeCBSCallSample(whereFrom, yieldpointServiceMethodFP); } t.countdownCBSCall = VM.CBSCallSampleStride; t.numCBSCallSamples--; if (t.numCBSCallSamples <= 0) { t.yieldForCBSCall = false; } } } if (t.yieldForCBSCall) { t.takeYieldpoint = -1; } } if (t.yieldForCBSMethod) { if (--t.countdownCBSMethod <= 0) { if (VM.BuildForAdaptiveSystem) { // take CBS sample RuntimeMeasurements.takeCBSMethodSample(whereFrom, yieldpointServiceMethodFP); } t.countdownCBSMethod = VM.CBSMethodSampleStride; t.numCBSMethodSamples--; if (t.numCBSMethodSamples <= 0) { t.yieldForCBSMethod = false; } } if (t.yieldForCBSMethod) { t.takeYieldpoint = 1; } } if (VM.BuildForAdaptiveSystem && t.yieldToOSRRequested) { t.yieldToOSRRequested = false; OSRListener.handleOSRFromOpt(yieldpointServiceMethodFP); } // what is the reason for this? and what was the reason for doing // a thread switch following the suspension in the OSR trigger code? // ... it seems that at least part of the point here is that if a // thread switch was desired for other reasons, then we need to ensure // that between when this runs and when the glue code runs there will // be no interleaved GC; obviously if we did this before the thread // switch then there would be the possibility of interleaved GC. if (VM.BuildForAdaptiveSystem && t.isWaitingForOsr) { if (VM.BuildForIA32) { org.jikesrvm.osr.ia32.PostThreadSwitch.postProcess(t); } else { if (VM.VerifyAssertions) VM._assert(VM.BuildForPowerPC); org.jikesrvm.osr.ppc.PostThreadSwitch.postProcess(t); } } if (t.asyncThrowable != null) { throwThis = t.asyncThrowable; t.asyncThrowable = null; } } t.monitor().unlock(); t.atYieldpoint = false; if (throwThis != null) { throwFromUninterruptible(throwThis); } } @Unpreemptible private static void throwFromUninterruptible(Throwable e) { RuntimeEntrypoints.athrow(e); } /** * Change the size of the currently executing thread's stack. * * @param newSize * new size (in bytes) * @param exceptionRegisters * register state at which stack overflow trap was encountered (null * --> normal method call, not a trap) */ @Unpreemptible("May block due to allocation") public static void resizeCurrentStack(int newSize, AbstractRegisters exceptionRegisters) { if (!getCurrentThread().hijackedReturnAddress.isZero()) { /* stack resizing currently unsupported with return barrier */ VM.sysFail("system error: resizing stack while return barrier enabled (currently unsupported)"); } if (traceAdjustments) VM.sysWriteln("Thread: resizeCurrentStack"); if (MemoryManager.gcInProgress()) { VM.sysFail("system error: resizing stack while GC is in progress"); } byte[] newStack = MemoryManager.newStack(newSize); getCurrentThread().disableYieldpoints(); transferExecutionToNewStack(newStack, exceptionRegisters); getCurrentThread().enableYieldpoints(); if (traceAdjustments) { RVMThread t = getCurrentThread(); VM.sysWrite("Thread: resized stack ", t.getThreadSlot()); VM.sysWrite(" to ", t.stack.length / 1024); VM.sysWriteln("k"); } } @NoInline @BaselineNoRegisters // this method does not do a normal return and hence does not execute epilogue // --> non-volatiles not restored! private static void transferExecutionToNewStack(byte[] newStack, AbstractRegisters exceptionRegisters) { // prevent opt compiler from inlining a method that contains a magic // (returnToNewStack) that it does not implement. RVMThread myThread = getCurrentThread(); byte[] myStack = myThread.stack; // initialize new stack with live portion of stack we're // currently running on // // lo-mem hi-mem // |<---myDepth----| // +----------+---------------+ // | empty | live | // +----------+---------------+ // ^myStack ^myFP ^myTop // // +-------------------+---------------+ // | empty | live | // +-------------------+---------------+ // ^newStack ^newFP ^newTop // Address myTop = Magic.objectAsAddress(myStack).plus(myStack.length); Address newTop = Magic.objectAsAddress(newStack).plus(newStack.length); Address myFP = Magic.getFramePointer(); Offset myDepth = myTop.diff(myFP); Address newFP = newTop.minus(myDepth); // The frame pointer addresses the top of the frame on powerpc and // the bottom // on intel. if we copy the stack up to the current // frame pointer in here, the // copy will miss the header of the intel frame. Thus we make another // call // to force the copy. A more explicit way would be to up to the // frame pointer // and the header for intel. Offset delta = copyStack(newStack); // fix up registers and save areas so they refer // to "newStack" rather than "myStack" // if (exceptionRegisters != null) { adjustRegisters(exceptionRegisters, delta); } adjustStack(newStack, newFP, delta); // install new stack // myThread.stack = newStack; myThread.stackLimit = Magic.objectAsAddress(newStack).plus(StackFrameLayout.getStackSizeGuard()); // return to caller, resuming execution on new stack // (original stack now abandoned) // if (VM.BuildForPowerPC) { Magic.returnToNewStack(Magic.getCallerFramePointer(newFP)); } else if (VM.BuildForIA32) { Magic.returnToNewStack(newFP); } if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } /** * This (suspended) thread's stack has been moved. Fixup register and memory * references to reflect its new position. * * @param delta * displacement to be applied to all interior references */ public void fixupMovedStack(Offset delta) { if (traceAdjustments) VM.sysWriteln("Thread: fixupMovedStack"); if (!contextRegisters.getInnermostFramePointer().isZero()) { adjustRegisters(contextRegisters, delta); } if ((exceptionRegisters.getInUse()) && (exceptionRegisters.getInnermostFramePointer().NE(Address.zero()))) { adjustRegisters(exceptionRegisters, delta); } if (!contextRegisters.getInnermostFramePointer().isZero()) { adjustStack(stack, contextRegisters.getInnermostFramePointer(), delta); } stackLimit = stackLimit.plus(delta); } /** * A thread's stack has been moved or resized. Adjust registers to reflect new * position. * * @param registers * registers to be adjusted * @param delta * displacement to be applied */ private static void adjustRegisters(AbstractRegisters registers, Offset delta) { if (traceAdjustments) VM.sysWriteln("Thread: adjustRegisters"); // adjust FP // Address newFP = registers.getInnermostFramePointer().plus(delta); Address ip = registers.getInnermostInstructionAddress(); registers.setInnermost(ip, newFP); if (traceAdjustments) { VM.sysWrite(" fp="); VM.sysWrite(registers.getInnermostFramePointer()); } // additional architecture specific adjustments // (1) frames from all compilers on IA32 need to update ESP int compiledMethodId = Magic.getCompiledMethodID(registers .getInnermostFramePointer()); if (compiledMethodId != StackFrameLayout.getInvisibleMethodID()) { if (VM.BuildForIA32) { registers.adjustESP(delta, traceAdjustments); } if (traceAdjustments) { CompiledMethod compiledMethod = CompiledMethods .getCompiledMethod(compiledMethodId); VM.sysWrite(" method="); VM.sysWrite(compiledMethod.getMethod()); VM.sysWriteln(); } } } /** * A thread's stack has been moved or resized. Adjust internal pointers to * reflect new position. * * @param stack * stack to be adjusted * @param fp * pointer to its innermost frame * @param delta * displacement to be applied to all its interior references */ private static void adjustStack(byte[] stack, Address fp, Offset delta) { if (traceAdjustments) VM.sysWriteln("Thread: adjustStack"); while (Magic.getCallerFramePointer(fp).NE(StackFrameLayout.getStackFrameSentinelFP())) { // adjust FP save area // Magic.setCallerFramePointer(fp, Magic.getCallerFramePointer(fp).plus( delta)); if (traceAdjustments) { VM.sysWrite(" fp=", fp.toWord()); } // advance to next frame // fp = Magic.getCallerFramePointer(fp); } } /** * Initialize a new stack with the live portion of the stack we're currently * running on. * * <pre> * lo-mem hi-mem * |<---myDepth----| * +----------+---------------+ * | empty | live | * +----------+---------------+ * ˆmyStack ˆmyFP ˆmyTop * +-------------------+---------------+ * | empty | live | * +-------------------+---------------+ * ˆnewStack ˆnewFP ˆnewTop * </pre> * * @param newStack space for the new stack * @return offset that needs to be applied to all interior references of * the new stack */ private static Offset copyStack(byte[] newStack) { RVMThread myThread = getCurrentThread(); byte[] myStack = myThread.stack; Address myTop = Magic.objectAsAddress(myStack).plus(myStack.length); Address newTop = Magic.objectAsAddress(newStack).plus(newStack.length); Address myFP = Magic.getFramePointer(); Offset myDepth = myTop.diff(myFP); Address newFP = newTop.minus(myDepth); // before copying, make sure new stack isn't too small // if (VM.VerifyAssertions) { VM._assert(newFP.GE(Magic.objectAsAddress(newStack).plus(StackFrameLayout.getStackSizeGuard()))); } Memory.memcopy(newFP, myFP, myDepth.toWord().toExtent()); return newFP.diff(myFP); } /** * Set the "isDaemon" status of this thread. Although a java.lang.Thread can * only have setDaemon invoked on it before it is started, Threads can become * daemons at any time. Note: making the last non daemon a daemon will * terminate the VM. * <p> * Note: This method might need to be uninterruptible so it is final, which is * why it isn't called setDaemon. * <p> * Public so that java.lang.Thread can use it. * * @param on new status for daemon flag */ public void makeDaemon(boolean on) { if (daemon == on) { // nothing to do } else { daemon = on; if (getExecStatus() == NEW) { // thread will start as a daemon } else { boolean terminateSystem = false; acctLock.lockNoHandshake(); numActiveDaemons += on ? 1 : -1; if (numActiveDaemons == numActiveThreads) { terminateSystem = true; } acctLock.unlock(); if (terminateSystem) { if (VM.TraceThreads) { trace("Thread", "last non Daemon demonized"); } VM.sysExit(0); if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } } } } /** * Dump information for all threads, via {@link VM#sysWrite(String)}. Each * thread's info is newline-terminated. * * @param verbosity Ignored. */ public static void dumpAll(int verbosity) { for (int i = 0; i < numThreads; i++) { RVMThread t = threads[i]; if (t == null) continue; VM.sysWrite("Thread "); VM.sysWriteInt(t.threadSlot); VM.sysWrite(": "); VM.sysWriteHex(Magic.objectAsAddress(t)); VM.sysWrite(" "); t.dump(verbosity); // Compensate for t.dump() not newline-terminating info. VM.sysWriteln(); } } /** @return whether this is the primordial thread, i.e. * the thread that boots the VM before starting the * main thread * */ public boolean isBootThread() { return this == bootThread; } /** @return whether this is the main thread */ private boolean isMainThread() { return thread instanceof MainThread; } /** @return whether this is a system thread */ public boolean isSystemThread() { return systemThread != null; } /** @return the collector thread this RVMTHread is running */ public CollectorThread getCollectorThread() { if (VM.VerifyAssertions) VM._assert(isCollectorThread()); return (CollectorThread)systemThread; } /** @return the value of {@link #daemon}. */ public boolean isDaemonThread() { return daemon; } /** * @return whether this thread should run concurrently with * stop-the-world garbage collection and ignore handshakes */ public boolean ignoreHandshakesAndGC() { if (systemThread == null) return false; return systemThread instanceof TimerThread; } /** @return whether the thread started and not terminated */ public boolean isAlive() { monitor().lockNoHandshake(); observeExecStatus(); boolean result = execStatus != NEW && execStatus != TERMINATED && !isAboutToTerminate; monitor().unlock(); return result; } /** * Sets the name of the thread * * @param name the new name for the thread * @see java.lang.Thread#setName(String) */ public void setName(String name) { this.name = name; } /** * Gets the name of the thread * * @see java.lang.Thread#getName() * @return name of the thread */ public String getName() { return name; } /** * Does the currently running Thread hold the lock on an obj? * * @param obj * the object to check * @return whether the thread holds the lock * @see java.lang.Thread#holdsLock(Object) */ public boolean holdsLock(Object obj) { RVMThread mine = getCurrentThread(); return ObjectModel.holdsLock(obj, mine); } /** * Was this thread interrupted? * * @return whether the thread has been interrupted * @see java.lang.Thread#isInterrupted() */ public boolean isInterrupted() { return hasInterrupt; } /** * Clear the interrupted status of this thread * * @see java.lang.Thread#interrupted() */ public void clearInterrupted() { hasInterrupt = false; } /** * Interrupt this thread * * @see java.lang.Thread#interrupt() */ @Interruptible public void interrupt() { monitor().lockNoHandshake(); hasInterrupt = true; monitor().broadcast(); monitor().unlock(); } /** * Get the priority of the thread * * @return the thread's priority * @see java.lang.Thread#getPriority() */ public int getPriority() { if (isAlive()) { // compute current priority priority = sysCall.sysGetThreadPriority(pthread_id, priority_handle) + Thread.NORM_PRIORITY; } if (tracePriority) { VM.sysWriteln("Thread #", getThreadSlot(), " get priority returning: ", priority); } return priority; } /** * Set the priority of the thread * * @param priority the thread's priority * @see java.lang.Thread#getPriority() */ public void setPriority(int priority) { if (isAlive()) { int result = sysCall.sysSetThreadPriority(pthread_id, priority_handle, priority - Thread.NORM_PRIORITY); if (result == 0) { this.priority = priority; if (tracePriority) { VM.sysWriteln("Thread #", getThreadSlot(), " set priority: ", priority); } } else { // setting priority failed if (tracePriority) { VM.sysWriteln("Thread #", getThreadSlot(), " failed to set priority: ", priority, ", result: ", result); } } } else { if (tracePriority) { VM.sysWriteln("Thread #", getThreadSlot(), " set priority: ", priority, " while not running"); } this.priority = priority; } } /** * Get the state of the thread in a manner compatible with the Java API * * @return thread state * @see java.lang.Thread#getState() */ @Interruptible public Thread.State getState() { monitor().lockNoHandshake(); try { observeExecStatus(); switch (execStatus) { case NEW: return Thread.State.NEW; case IN_JAVA: case IN_NATIVE: case IN_JNI: case IN_JAVA_TO_BLOCK: case BLOCKED_IN_NATIVE: case BLOCKED_IN_JNI: if (isAboutToTerminate) { return Thread.State.TERMINATED; } switch (waiting) { case RUNNABLE: return Thread.State.RUNNABLE; case WAITING: return Thread.State.WAITING; case TIMED_WAITING: return Thread.State.TIMED_WAITING; default: VM.sysFail("Unknown waiting value: " + waiting); return null; } case TERMINATED: return Thread.State.TERMINATED; default: VM.sysFail("Unknown value of execStatus: " + execStatus); return null; } } finally { monitor().unlock(); } } /** * Wait for the thread to die or for the timeout to occur * * @param ms * milliseconds to wait * @param ns * nanoseconds to wait * @throws InterruptedException when the thread is interrupted */ @Interruptible public void join(long ms, int ns) throws InterruptedException { RVMThread myThread = getCurrentThread(); if (VM.VerifyAssertions) VM._assert(myThread != this); if (traceBlock) VM.sysWriteln("Joining on Thread #", threadSlot); // this uses synchronized because we cannot have one thread acquire // another thread's lock using the WithHandshake scheme, as that would result // in a thread holding two threads' monitor()s. using synchronized // turns out to be just fine - see comment in terminate(). synchronized (this) { if (ms == 0 && ns == 0) { while (!isJoinable) { wait(this); if (traceBlock) VM.sysWriteln("relooping in join on Thread #", threadSlot); } } else { long startNano = Time.nanoTime(); long whenWakeup = startNano + ms * 1000L * 1000L + ns; while (!isJoinable) { waitAbsoluteNanos(this, whenWakeup); if (Time.nanoTime() >= whenWakeup) { break; } if (traceBlock) VM.sysWriteln("relooping in join on Thread #", threadSlot); } } } } /** * Gets live threads. * <p> * Note: this is an expensive operation operation because we're grabbing * the accounting lock and thus prevent the threading system from changing * the set of active threads. * * @return the live threads that ought to be user-visible, i.e. * all threads except the system threads */ @Interruptible public static Thread[] getLiveThreadsForJMX() { int threadIndex = 0; acctLock.lockNoHandshake(); Thread[] liveThreads = new Thread[numActiveThreads]; for (int i = 0; i < RVMThread.numThreads; i++) { RVMThread t = RVMThread.threads[i]; if (t.isAlive() && !t.isSystemThread()) { Thread javaLangThread = t.getJavaLangThread(); if (javaLangThread == null) { continue; } boolean enoughSpace = threadIndex < numActiveThreads; if (!enoughSpace) { // unlock because of imminent (assertion) failure acctLock.unlock(); if (VM.VerifyAssertions) { VM._assert(VM.NOT_REACHED, "Not enough space in array for all live threads"); } else { VM.sysFail("Not enough space in array for all live threads"); } } liveThreads[threadIndex] = javaLangThread; threadIndex++; } } acctLock.unlock(); return liveThreads; } /** * Counts the stack frames of this thread. * * @return the number of stack frames in this thread */ @Interruptible public int countStackFrames() { if (!isSuspended) { throw new IllegalThreadStateException( "Thread.countStackFrames called on non-suspended thread"); } throw new UnimplementedError(); } /** * @return the length of the stack */ public int getStackLength() { return stack.length; } /** * @return the stack */ public byte[] getStack() { return stack; } /** * @return the thread's exception registers */ public AbstractRegisters getExceptionRegisters() { return exceptionRegisters; } /** * @return the thread's context registers (saved registers when thread is * suspended by scheduler). */ public AbstractRegisters getContextRegisters() { return contextRegisters; } /** Set the initial attempt. */ public void reportCollectionAttempt() { collectionAttempt++; } /** @return the number of collection attempts */ public int getCollectionAttempt() { return collectionAttempt; } /** Resets the attempts. */ public void resetCollectionAttempts() { collectionAttempt = 0; } /** @return the physical allocation failed flag. */ public boolean physicalAllocationFailed() { return physicalAllocationFailed; } /** Set the physical allocation failed flag. */ public void setPhysicalAllocationFailed() { physicalAllocationFailed = true; } /** Clear the physical allocation failed flag. */ public void clearPhysicalAllocationFailed() { physicalAllocationFailed = false; } /** * @return the outstanding OutOfMemoryError. */ public static OutOfMemoryError getOutOfMemoryError() { return outOfMemoryError; } /** * @return number of active threads in the system. */ public static int getNumActiveThreads() { return numActiveThreads; } public static int getNumActiveSystemThreads() { return numActiveSystemThreads; } /** * @return number of active daemon threads. */ public static int getNumActiveDaemons() { return numActiveDaemons; } /** * Handles uncaught exceptions for subclasses of {@link SystemThread}. * Uncaught exceptions for normal threads will end up in that thread's {@link #run()} * method which will invoke the thread's uncaught exception handler. * * @param exceptionObject the exception object that wasn't caught * @see #run() run() method of application threads * @see SystemThread#run() run() method of system threads */ @Interruptible public void handleUncaughtException(Throwable exceptionObject) { uncaughtExceptionCount++; handlePossibleRecursiveException(); VM.enableGC(); if (thread == null) { VM.sysWrite("Exception in the primordial thread \"", getName(), "\" while booting: "); } else { // This is output like that of the Sun JDK. VM.sysWrite("Exception in thread \"", getName(), "\": "); } if (exceptionObject instanceof OutOfMemoryError) { VM.sysWriteln(" <<No stacktrace available>>"); } else if (VM.fullyBooted) { exceptionObject.printStackTrace(); } getCurrentThread().terminate(); if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } /** Handle the case of exception handling triggering new exceptions. */ private void handlePossibleRecursiveException() { if (uncaughtExceptionCount > 1 && uncaughtExceptionCount <= VM.maxSystemTroubleRecursionDepth + VM.maxSystemTroubleRecursionDepthBeforeWeStopVMSysWrite) { VM.sysWrite("We got an uncaught exception while (recursively) handling "); VM.sysWrite(uncaughtExceptionCount - 1); VM.sysWrite(" uncaught exception"); if (uncaughtExceptionCount - 1 != 1) { VM.sysWrite("s"); } VM.sysWriteln("."); } if (uncaughtExceptionCount > VM.maxSystemTroubleRecursionDepth) { dumpVirtualMachine(); VM.dieAbruptlyRecursiveSystemTrouble(); if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } } private static void dumpThread(RVMThread t) { if (t == null) { VM.sysWrite("none"); } else { VM.sysWrite(t.threadSlot, "(", READABLE_EXEC_STATUS[t.getExecStatus()]); if (t.isAboutToTerminate) { VM.sysWrite("T"); } if (t.isBlocking) { VM.sysWrite("B"); } if (t.isJoinable) { VM.sysWrite("J"); } if (t.atYieldpoint) { VM.sysWrite("Y"); } VM.sysWrite(")"); } } private static void dumpThreadArray(RVMThread[] array, int bound) { for (int i = 0; i < bound; ++i) { if (i != 0) { VM.sysWrite(", "); } VM.sysWrite(i, ":"); dumpThread(array[i]); } } private static void dumpThreadSlotArray(int[] array, int bound) { for (int i = 0; i < bound; ++i) { if (i != 0) { VM.sysWrite(", "); } VM.sysWrite(i, ":"); int threadSlot = array[i]; VM.sysWrite(threadSlot, ","); dumpThread(threadBySlot[array[i]]); } } private static void dumpThreadArray(String name, RVMThread[] array, int bound) { VM.sysWrite(name); VM.sysWrite(": "); dumpThreadArray(array, bound); VM.sysWriteln(); } private static void dumpThreadSlotArray(String name, int[] array, int bound) { VM.sysWrite(name); VM.sysWrite(": "); dumpThreadSlotArray(array, bound); VM.sysWriteln(); } public static void dumpAcct() { acctLock.lockNoHandshake(); dumpLock.lockNoHandshake(); VM.sysWriteln("====== Begin Thread Accounting Dump ======"); dumpThreadArray("threadBySlot", threadBySlot, nextSlot); dumpThreadSlotArray("aboutToTerminate", aboutToTerminate, aboutToTerminateN); VM.sysWrite("freeSlots: "); for (int i = 0; i < freeSlotN; ++i) { if (i != 0) { VM.sysWrite(", "); } VM.sysWrite(i, ":", freeSlots[i]); } VM.sysWriteln(); dumpThreadArray("threads", threads, numThreads); VM.sysWriteln("====== End Thread Accounting Dump ======"); dumpLock.unlock(); acctLock.unlock(); } public void extDump() { dump(); VM.sysWriteln(); VM.sysWriteln("acquireCount for my monitor: ", monitor().acquireCount); VM.sysWriteln("yieldpoints taken: ", yieldpointsTaken); VM.sysWriteln("yieldpoints taken fully: ", yieldpointsTakenFully); VM.sysWriteln("native entered blocked: ", nativeEnteredBlocked); VM.sysWriteln("JNI entered blocked: ", jniEnteredBlocked); } /** * Dump this thread's identifying information, for debugging, via * {@link VM#sysWrite(String)}. We do not use any spacing or newline * characters. Callers are responsible for space-separating or * newline-terminating output. */ public void dump() { dump(0); } /** * Dump this thread's identifying information, for debugging, via * {@link VM#sysWrite(String)}. We pad to a minimum of leftJustify * characters. We do not use any spacing characters. Callers are responsible * for space-separating or newline-terminating output. * * @param leftJustify * minimum number of characters emitted, with any extra characters * being spaces. */ public void dumpWithPadding(int leftJustify) { char[] buf = Services.grabDumpBuffer(); int len = dump(buf); VM.sysWrite(buf, len); for (int i = leftJustify - len; i > 0; i--) { VM.sysWrite(" "); } Services.releaseDumpBuffer(); } /** * Dump this thread's identifying information, for debugging, via * {@link VM#sysWrite(String)}. We do not use any spacing or newline * characters. Callers are responsible for space-separating or * newline-terminating output. * * This function avoids write barriers and allocation. * * @param verbosity * Ignored. */ public void dump(int verbosity) { char[] buf = Services.grabDumpBuffer(); int len = dump(buf); VM.sysWrite(buf, len); Services.releaseDumpBuffer(); } /** * Dump this thread's info, for debugging. Copy the info about it into a * destination char array. We do not use any spacing or newline characters. * * This function may be called during GC; it avoids write barriers and * allocation. * * For this reason, we do not throw an <code>IndexOutOfBoundsException</code>. * * @param dest * char array to copy the source info into. * @param offset * Offset into <code>dest</code> where we start copying * * @return 1 plus the index of the last character written. If we were to write * zero characters (which we won't) then we would return * <code>offset</code>. This is intended to represent the first * unused position in the array <code>dest</code>. However, it also * serves as a pseudo-overflow check: It may have the value * <code>dest.length</code>, if the array <code>dest</code> was * completely filled by the call, or it may have a value greater than * <code>dest.length</code>, if the info needs more than * <code>dest.length - offset</code> characters of space. * * -1 if <code>offset</code> is negative. */ public int dump(char[] dest, int offset) { offset = Services.sprintf(dest, offset, getThreadSlot()); // id if (daemon) { offset = Services.sprintf(dest, offset, "-daemon"); // daemon thread? } if (isBootThread()) { offset = Services.sprintf(dest, offset, "-Boot"); // Boot (Primordial) // thread } if (isSystemThread()) { offset = Services.sprintf(dest, offset, "-system"); // System Thread } if (isMainThread()) { offset = Services.sprintf(dest, offset, "-main"); // Main Thread } if (isCollectorThread()) { offset = Services.sprintf(dest, offset, "-collector"); // gc thread? } offset = Services.sprintf(dest, offset, "-"); offset = Services.sprintf(dest, offset, READABLE_EXEC_STATUS[getExecStatus()]); offset = Services.sprintf(dest, offset, "-"); offset = Services.sprintf(dest, offset, java.lang.JikesRVMSupport .getEnumName(waiting)); if (hasInterrupt || asyncThrowable != null) { offset = Services.sprintf(dest, offset, "-interrupted"); } if (isAboutToTerminate) { offset = Services.sprintf(dest, offset, "-terminating"); } return offset; } /** * Dump this thread's info, for debugging. Copy the info about it into a * destination char array. We do not use any spacing or newline characters. * <p> * This is identical to calling {@link #dump(char[],int)} with an * <code>offset</code> of zero. * * @param dest array to dump the info into * * @return see {@link #dump(char[], int)} */ public int dump(char[] dest) { return dump(dest, 0); } /** Dump statistics gather on operations */ static void dumpStats() { VM.sysWrite("FatLocks: "); VM.sysWrite(waitOperations); VM.sysWriteln(" wait operations"); VM.sysWrite("FatLocks: "); VM.sysWrite(timedWaitOperations); VM.sysWriteln(" timed wait operations"); VM.sysWrite("FatLocks: "); VM.sysWrite(notifyOperations); VM.sysWriteln(" notify operations"); VM.sysWrite("FatLocks: "); VM.sysWrite(notifyAllOperations); } /** * Prints out message in format {@code "[j] (td) who: what"}, where: * <ul> * <li>{@code j = java thread id} * <li>{@code t = numActiveThreads} * <li>{@code d = numActiveDaemon} * </ul> * The parenthetical values are printed only if {@link #traceDetails} is true. * <p> * We serialize against a mutex to avoid intermingling debug output from * multiple threads. * * @param who the string for the who parameter * @param what the string for the what parameter */ public static void trace(String who, String what) { outputLock.lockNoHandshake(); VM.sysWrite("["); RVMThread t = getCurrentThread(); t.dump(); VM.sysWrite("] "); if (traceDetails) { VM.sysWrite("("); VM.sysWriteInt(numActiveDaemons); VM.sysWrite("/"); VM.sysWriteInt(numActiveThreads); VM.sysWrite(") "); } VM.sysWrite(who); VM.sysWrite(": "); VM.sysWrite(what); VM.sysWriteln(); outputLock.unlock(); } /** * Prints out message in format {@code "[j] (td) who: what howmany"}, where: * <ul> * <li>{@code j = java thread id} * <li>{@code t = numActiveThreads} * <li>{@code d = numActiveDaemon} * </ul> * The parenthetical values are printed only if {@link #traceDetails} is true. * <p> * We serialize against a mutex to avoid intermingling debug output from * multiple threads. * * @param who the string for the who parameter * @param what the string for the what parameter * @param howmany the count for the howmany parameter */ public static void trace(String who, String what, int howmany) { _trace(who, what, howmany, false); } // same as trace, but prints integer value in hex // public static void traceHex(String who, String what, int howmany) { _trace(who, what, howmany, true); } public static void trace(String who, String what, Address addr) { outputLock.lockNoHandshake(); VM.sysWrite("["); getCurrentThread().dump(); VM.sysWrite("] "); if (traceDetails) { VM.sysWrite("("); VM.sysWriteInt(numActiveDaemons); VM.sysWrite("/"); VM.sysWriteInt(numActiveThreads); VM.sysWrite(") "); } VM.sysWrite(who); VM.sysWrite(": "); VM.sysWrite(what); VM.sysWrite(" "); VM.sysWriteHex(addr); VM.sysWriteln(); outputLock.unlock(); } private static void _trace(String who, String what, int howmany, boolean hex) { outputLock.lockNoHandshake(); VM.sysWrite("["); // VM.sysWriteInt(RVMThread.getCurrentThread().getThreadSlot()); getCurrentThread().dump(); VM.sysWrite("] "); if (traceDetails) { VM.sysWrite("("); VM.sysWriteInt(numActiveDaemons); VM.sysWrite("/"); VM.sysWriteInt(numActiveThreads); VM.sysWrite(") "); } VM.sysWrite(who); VM.sysWrite(": "); VM.sysWrite(what); VM.sysWrite(" "); if (hex) { VM.sysWriteHex(howmany); } else { VM.sysWriteInt(howmany); } VM.sysWriteln(); outputLock.unlock(); } /** * Print interesting scheduler information, starting with a stack traceback. * <p> * Note: the system could be in a fragile state when this method is called, so * we try to rely on as little runtime functionality as possible (eg. use no * bytecodes that require RuntimeEntrypoints support). * * @param message the message to write before the actual traceback */ public static void traceback(String message) { if (VM.runningVM && threadingInitialized) { outputLock.lockNoHandshake(); } VM.sysWriteln(message); tracebackWithoutLock(); if (VM.runningVM && threadingInitialized) { outputLock.unlock(); } } public static void traceback(String message, int number) { if (VM.runningVM && threadingInitialized) { outputLock.lockNoHandshake(); } VM.sysWriteln(message, number); tracebackWithoutLock(); if (VM.runningVM && threadingInitialized) { outputLock.unlock(); } } static void tracebackWithoutLock() { if (VM.runningVM) { VM.sysWriteln("Thread #", getCurrentThreadSlot()); dumpStack(Magic.getCallerFramePointer(Magic.getFramePointer())); } else { dumpStack(); } } /** * Dump stack of calling thread, starting at callers frame */ @UninterruptibleNoWarn("Never blocks") public static void dumpStack() { if (VM.runningVM) { VM.sysWriteln("Dumping stack for Thread #", getCurrentThreadSlot()); dumpStack(Magic.getFramePointer()); } else { StackTraceElement[] elements = (new Throwable( "--traceback from Jikes RVM's RVMThread class--")).getStackTrace(); for (StackTraceElement element : elements) { System.err.println(element.toString()); } } } /** * Dump state of a (stopped) thread's stack. * * @param fp address of starting frame. first frame output is the calling * frame of passed frame */ public static void dumpStack(Address fp) { if (VM.VerifyAssertions) { VM._assert(VM.runningVM); } Address ip = Magic.getReturnAddress(fp); fp = Magic.getCallerFramePointer(fp); dumpStack(ip, fp); } /** * Dump state of a (stopped) thread's stack. * * @param ip instruction pointer for first frame to dump * @param fp frame pointer for first frame to dump */ public static void dumpStack(Address ip, Address fp) { boolean b = Monitor.lockNoHandshake(dumpLock); RVMThread t = getCurrentThread(); ++t.inDumpStack; if (t.inDumpStack > 1 && t.inDumpStack <= VM.maxSystemTroubleRecursionDepth + VM.maxSystemTroubleRecursionDepthBeforeWeStopVMSysWrite) { VM.sysWrite("RVMThread.dumpStack(): in a recursive call, "); VM.sysWrite(t.inDumpStack); VM.sysWriteln(" deep."); } if (t.inDumpStack > VM.maxSystemTroubleRecursionDepth) { VM.dieAbruptlyRecursiveSystemTrouble(); if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } if (fp.EQ(StackFrameLayout.getStackFrameSentinelFP())) { VM.sysWriteln("Empty stack"); } else if (!isAddressValidFramePointer(fp)) { VM.sysWrite("Bogus looking frame pointer: ", fp); VM.sysWriteln(" not dumping stack"); } else { try { VM.sysWriteln("-- Stack --"); while (Magic.getCallerFramePointer(fp).NE( StackFrameLayout.getStackFrameSentinelFP())) { // if code is outside of RVM heap, assume it to be native code, // skip to next frame if (!MemoryManager.addressInVM(ip)) { showMethod("native frame", fp); ip = Magic.getReturnAddress(fp); fp = Magic.getCallerFramePointer(fp); } else { int compiledMethodId = Magic.getCompiledMethodID(fp); boolean idOutOfRange = compiledMethodId > CompiledMethods.numCompiledMethods() || compiledMethodId < 1; VM.sysWrite("(", fp); VM.sysWrite(" ", compiledMethodId, ")"); if (compiledMethodId == StackFrameLayout.getInvisibleMethodID()) { showMethod("invisible method", fp); } else if (idOutOfRange) { showMethod("invalid compiled method id", fp); break; } else { // normal java frame(s) CompiledMethod compiledMethod = CompiledMethods .getCompiledMethod(compiledMethodId); if (compiledMethod == null) { showMethod(compiledMethodId, fp); } else if (compiledMethod.getCompilerType() == CompiledMethod.TRAP) { showMethod("hardware trap", fp); } else if (!isAddressValidFramePointer(fp)) { VM.sysWrite("Bogus looking frame pointer: ", fp); VM.sysWriteln(" not dumping stack"); break; } else { RVMMethod method = compiledMethod.getMethod(); if (compiledMethod.containsReturnAddress(ip)) { Offset instructionOffset = compiledMethod .getInstructionOffset(ip); int lineNumber = compiledMethod .findLineNumberForInstruction(instructionOffset); boolean frameShown = false; if (VM.BuildForOptCompiler && compiledMethod.getCompilerType() == CompiledMethod.OPT) { OptCompiledMethod optInfo = (OptCompiledMethod) compiledMethod; // Opt stack frames may contain multiple inlined methods. OptMachineCodeMap map = optInfo.getMCMap(); int iei = map.getInlineEncodingForMCOffset(instructionOffset); if (iei >= 0) { int[] inlineEncoding = map.inlineEncoding; int bci = map.getBytecodeIndexForMCOffset(instructionOffset); for (; iei >= 0; iei = OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) { int mid = OptEncodedCallSiteTree.getMethodID(iei, inlineEncoding); method = MemberReference.getMethodRef(mid).getResolvedMember(); lineNumber = ((NormalMethod) method).getLineNumberForBCIndex(bci); showMethod(method, lineNumber, fp, bci, instructionOffset); if (iei > 0) { bci = OptEncodedCallSiteTree.getByteCodeOffset(iei, inlineEncoding); } } frameShown = true; } } if (!frameShown) { int bci = -1; if (compiledMethod.getCompilerType() == CompiledMethod.BASELINE) { BaselineCompiledMethod bcm = (BaselineCompiledMethod) compiledMethod; bci = bcm.findBytecodeIndexForInstruction(instructionOffset); } showMethod(method, lineNumber, fp, bci, instructionOffset); } } else { VM.sysWrite(" WARNING: Instruction pointer "); VM.sysWrite(ip); VM.sysWrite(" not in method code"); showMethod(method, -1, fp, -1, Offset.max()); } } } ip = Magic.getReturnAddress(fp); fp = Magic.getCallerFramePointer(fp); } if (!isAddressValidFramePointer(fp)) { VM.sysWrite("Bogus looking frame pointer: ", fp); VM.sysWriteln(" end of stack dump"); break; } } // end while } catch (Throwable th) { VM.sysWriteln("Something bad killed the stack dump. The last frame pointer was: ", fp); } } --t.inDumpStack; Monitor.unlock(b, dumpLock); } /** * Return true if the supplied address could be a valid frame pointer. To * check for validity we make sure the frame pointer is in one of the spaces; * <ul> * <li>LOS (For regular threads)</li> * <li>Immortal (For threads allocated in immortal space such as collectors)</li> * <li>Boot (For the boot thread)</li> * </ul> * * <p> * or it is {@link StackFrameLayout#getStackFrameSentinelFP()}. * {@code STACKFRAME_SENTINEL_FP} is possible when the thread has been created * but has yet to be scheduled. * </p> * * @param address * the address. * @return true if the address could be a frame pointer, false otherwise. */ private static boolean isAddressValidFramePointer(final Address address) { if (address.EQ(Address.zero())) return false; // Avoid hitting assertion failure in MMTk else return address.EQ(StackFrameLayout.getStackFrameSentinelFP()) || MemoryManager.mightBeFP(address); } private static void showPrologue(Address fp) { VM.sysWrite(" at "); if (SHOW_FP_IN_STACK_DUMP) { VM.sysWrite("["); VM.sysWrite(fp); VM.sysWrite(", "); VM.sysWrite(Magic.getReturnAddress(fp)); VM.sysWrite("] "); } } /** * Show a method where getCompiledMethod returns null * * @param compiledMethodId the id of the compiled method * @param fp the frame pointer of the method's frame */ private static void showMethod(int compiledMethodId, Address fp) { showPrologue(fp); VM.sysWriteln( "<unprintable normal Java frame: CompiledMethods.getCompiledMethod(", compiledMethodId, ") returned null>"); } /** * Shows a method that we can't show (ie just a text description of the stack * frame * * @param name the method's name * @param fp the frame pointer of the method's frame */ private static void showMethod(String name, Address fp) { showPrologue(fp); VM.sysWrite("<"); VM.sysWrite(name); VM.sysWriteln(">"); } /** * Helper function for {@link #dumpStack(Address,Address)}. Print a stack * frame showing the method. * * @param method the underlying method * @param lineNumber the line number for the stack trace * @param fp the frame pointer of the method's frame * @param bci byte code index (value < 0 if unknown) * @param mcOffset machine code offset for the instruction ({@code Offset.max()} if unknown) */ private static void showMethod(RVMMethod method, int lineNumber, Address fp, int bci, Offset mcOffset) { showPrologue(fp); if (method == null) { VM.sysWrite("<unknown method>"); } else { VM.sysWrite(method.getDeclaringClass().getDescriptor()); VM.sysWrite(" "); VM.sysWrite(method.getName()); VM.sysWrite(method.getDescriptor()); } if (lineNumber > 0) { VM.sysWrite(" at line "); VM.sysWriteInt(lineNumber); } if (bci >= 0) { VM.sysWrite(" at bytecode index "); VM.sysWriteInt(bci); } if (!mcOffset.isMax()) { VM.sysWrite(" at machine code offset "); VM.sysWrite(mcOffset); } VM.sysWriteln(); } /** * Dump state of a (stopped) thread's stack and exit the virtual machine. * * @param fp * address of starting frame Returned: doesn't return. This method is * called from sysSignal*.c when something goes horrifically wrong * with exception handling and we want to die with useful * diagnostics. */ @Entrypoint public static void dumpStackAndDie(Address fp) { if (!exitInProgress) { // This is the first time I've been called, attempt to exit "cleanly" exitInProgress = true; dumpStack(fp); VM.sysExit(EXIT_STATUS_DUMP_STACK_AND_DIE); } else { // Another failure occurred while attempting to exit cleanly. // Get out quick and dirty to avoid hanging. sysCall.sysExit(EXIT_STATUS_RECURSIVELY_SHUTTING_DOWN); } } /** * @return whether it is safe to start forcing garbage collects for stress testing */ public static boolean safeToForceGCs() { return gcEnabled(); } /** * @return whether garbage collection is enabled */ public static boolean gcEnabled() { return threadingInitialized && getCurrentThread().yieldpointsEnabled(); } /** * Set up the initial thread and processors as part of boot image writing * * @return the boot thread */ @Interruptible public static RVMThread setupBootThread() { if (VM.VerifyAssertions) VM._assert(bootThread == null); BootThread bt = new BootThread(); bootThread = bt.getRVMThread(); bootThread.feedlet = TraceEngine.engine.makeFeedlet( "Jikes RVM boot thread", "Thread used to execute the initial boot sequence of Jikes RVM"); numActiveThreads++; numActiveSystemThreads++; numActiveDaemons++; return bootThread; } /** * Dump state of virtual machine. */ public static void dumpVirtualMachine() { boolean b = Monitor.lockNoHandshake(dumpLock); getCurrentThread().disableYieldpoints(); VM.sysWriteln(); VM.sysWrite("-- Threads --"); VM.sysWriteln(); for (int i = 0; i < numThreads; ++i) { RVMThread t = threads[i]; if (t != null) { t.dumpWithPadding(30); VM.sysWriteln(); } } VM.sysWriteln(); VM.sysWriteln(); VM.sysWrite("-- Locks in use --"); VM.sysWriteln(); Lock.dumpLocks(); VM.sysWriteln("Dumping stack of active thread"); VM.sysWriteln(); dumpStack(); VM.sysWriteln("Attempting to dump the stack of all other live threads"); VM.sysWriteln("This is somewhat risky since if the thread is running we're going to be quite confused"); for (int i = 0; i < numThreads; ++i) { RVMThread thr = threads[i]; if (thr != null && thr != RVMThread.getCurrentThread() && thr.isAlive()) { thr.dump(); // PNT: FIXME: this won't work so well since the context registers // don't tend to have sane values if (thr.contextRegisters != null && !thr.ignoreHandshakesAndGC()) dumpStack(thr.contextRegisters.getInnermostFramePointer()); } } getCurrentThread().enableYieldpoints(); Monitor.unlock(b, dumpLock); } public static Feedlet getCurrentFeedlet() { return getCurrentThread().feedlet; } ////////////////////////// VM.countThreadTransitions support ////////////////////////// static final int[] sloppyExecStatusHistogram = new int[LAST_EXEC_STATUS]; static final int[] statusAtSTWHistogram = new int[LAST_EXEC_STATUS]; static final int[] execStatusTransitionHistogram = new int[LAST_EXEC_STATUS * LAST_EXEC_STATUS]; public static void reportThreadTransitionCounts() { VM.sysWriteln("Thread Transition Counts:"); dump1DHisto("Sloppy Exec Status Histogram",sloppyExecStatusHistogram); dump1DHisto("Status At Stop-the-world Histogram",statusAtSTWHistogram); VM.sysWriteln(" Exec Status Transition Histogram:"); for (int fromI = 0; fromI < LAST_EXEC_STATUS; ++fromI) { for (int toI = 0; toI < LAST_EXEC_STATUS; ++toI) { int val = execStatusTransitionHistogram[ transitionHistogramIndex(fromI,toI)]; if (val != 0) { VM.sysWriteln(" ",fromI,"->",toI," ",val); } } } } static void dump1DHisto(String name,int[] histo) { VM.sysWriteln(" ",name,":"); for (int i = 0; i < LAST_EXEC_STATUS; ++i) { if (histo[i] != 0) { VM.sysWriteln(" ",i," ",histo[i]); } } } void observeExecStatus() { sloppyExecStatusHistogram[execStatus]++; } public static void observeExecStatusAtSTW(int execStatus) { statusAtSTWHistogram[execStatus]++; } // FIXME: add histograms for states returned from various calls to block() // currently we just do it for the block() call in GC STW. static int transitionHistogramIndex(int oldState,int newState) { return oldState + newState * LAST_EXEC_STATUS; } static void observeStateTransition(int oldState,int newState) { execStatusTransitionHistogram[transitionHistogramIndex(oldState,newState)]++; sloppyExecStatusHistogram[oldState]++; sloppyExecStatusHistogram[newState]++; } }